From 5787f9cc384b353e2c9b420c70c9fe0008fa68a2 Mon Sep 17 00:00:00 2001 From: Bart Louwers Date: Tue, 24 Feb 2026 14:37:35 +0100 Subject: [PATCH] ios: apply clang-format to Objective-C++ sources (#4060) --- .clang-format | 1 + .pre-commit-config.yaml | 2 +- .../app/ExampleCustomDrawableStyleLayer.mm | 533 +- platform/darwin/app/PluginLayerExample.mm | 25 +- .../app/PluginLayerExampleMetalRendering.mm | 324 +- platform/darwin/core/collator.mm | 83 +- platform/darwin/core/http_file_source.mm | 668 +- platform/darwin/core/image.mm | 118 +- .../darwin/core/local_glyph_rasterizer.mm | 404 +- platform/darwin/core/logging_nslog.mm | 15 +- platform/darwin/core/nsthread.mm | 50 +- platform/darwin/core/number_format.mm | 34 +- platform/darwin/core/string_nsstring.mm | 36 +- .../darwin/src/MLNActionJournalOptions.mm | 41 +- platform/darwin/src/MLNAttributionInfo.mm | 358 +- platform/darwin/src/MLNBackendResource.mm | 20 +- platform/darwin/src/MLNComputedShapeSource.mm | 346 +- platform/darwin/src/MLNConversion.h | 6 +- .../darwin/src/MLNCustomDrawableStyleLayer.mm | 14 +- platform/darwin/src/MLNCustomStyleLayer.mm | 135 +- platform/darwin/src/MLNDisplayUtils.mm | 22 +- platform/darwin/src/MLNFeature.mm | 558 +- .../darwin/src/MLNForegroundStyleLayer.mm | 27 +- platform/darwin/src/MLNFoundation.mm | 4 +- platform/darwin/src/MLNGeometry.mm | 178 +- platform/darwin/src/MLNImageSource.mm | 120 +- .../darwin/src/MLNLoggingConfiguration.mm | 229 +- platform/darwin/src/MLNMapCamera.mm | 250 +- platform/darwin/src/MLNMapOptions.mm | 20 +- platform/darwin/src/MLNMapSnapshotter.mm | 1284 +- platform/darwin/src/MLNMultiPoint.mm | 285 +- .../darwin/src/MLNNetworkConfiguration.mm | 290 +- platform/darwin/src/MLNNetworkResponse.mm | 19 +- platform/darwin/src/MLNOfflinePack.mm | 421 +- platform/darwin/src/MLNOfflineStorage.mm | 992 +- platform/darwin/src/MLNPluginLayer.mm | 82 +- platform/darwin/src/MLNPluginStyleLayer.mm | 33 +- platform/darwin/src/MLNPointAnnotation.mm | 79 +- platform/darwin/src/MLNPointCollection.mm | 170 +- platform/darwin/src/MLNPolygon.mm | 273 +- platform/darwin/src/MLNPolyline.mm | 267 +- platform/darwin/src/MLNRasterDEMSource.mm | 12 +- platform/darwin/src/MLNRasterTileSource.mm | 92 +- platform/darwin/src/MLNRendererFrontend.h | 4 +- platform/darwin/src/MLNRenderingStats.mm | 59 +- platform/darwin/src/MLNSettings.mm | 312 +- platform/darwin/src/MLNShape.mm | 140 +- platform/darwin/src/MLNShapeCollection.mm | 82 +- platform/darwin/src/MLNShapeOfflineRegion.mm | 176 +- platform/darwin/src/MLNShapeSource.mm | 609 +- platform/darwin/src/MLNSource.mm | 135 +- platform/darwin/src/MLNStyle.mm | 916 +- platform/darwin/src/MLNStyleLayer.mm | 136 +- platform/darwin/src/MLNStyleLayerManager.mm | 124 +- platform/darwin/src/MLNStyleLayer_Private.h | 2 +- platform/darwin/src/MLNStyleValue.mm | 68 +- platform/darwin/src/MLNStyleValue_Private.h | 8 +- .../darwin/src/MLNTilePyramidOfflineRegion.mm | 196 +- platform/darwin/src/MLNTileSource.mm | 272 +- platform/darwin/src/MLNValueEvaluator.h | 3 +- platform/darwin/src/MLNVectorStyleLayer.mm | 36 +- platform/darwin/src/MLNVectorTileSource.mm | 209 +- platform/darwin/src/NSArray+MLNAdditions.mm | 83 +- platform/darwin/src/NSBundle+MLNAdditions.mm | 46 +- platform/darwin/src/NSCoder+MLNAdditions.mm | 19 +- .../src/NSComparisonPredicate+MLNAdditions.mm | 386 +- .../src/NSCompoundPredicate+MLNAdditions.mm | 55 +- platform/darwin/src/NSDate+MLNAdditions.mm | 13 +- .../darwin/src/NSDictionary+MLNAdditions.mm | 26 +- .../darwin/src/NSExpression+MLNAdditions.mm | 2603 ++-- .../darwin/src/NSPredicate+MLNAdditions.mm | 363 +- .../src/NSValue+MLNStyleAttributeAdditions.mm | 105 +- platform/darwin/src/headless_backend_cgl.mm | 168 +- platform/darwin/src/headless_backend_eagl.mm | 54 +- platform/darwin/test/MLNCodingTests.mm | 838 +- platform/darwin/test/MLNExpressionTests.mm | 3139 ++-- platform/darwin/test/MLNFeatureTests.mm | 581 +- platform/darwin/test/MLNGeometryTests.mm | 336 +- platform/darwin/test/MLNHeatmapColorTests.mm | 107 +- platform/darwin/test/MLNImageTests.mm | 193 +- .../darwin/test/MLNNSDateAdditionsTests.mm | 38 +- platform/darwin/test/MLNOfflinePackTests.mm | 79 +- .../darwin/test/MLNOfflineStorageTests.mm | 1166 +- platform/darwin/test/MLNPredicateTests.mm | 1227 +- platform/darwin/test/MLNResourceTests.mm | 111 +- platform/darwin/test/MLNShapeSourceTests.mm | 510 +- platform/darwin/test/MLNStyleTests.mm | 699 +- platform/darwin/test/MLNTileSetTests.mm | 280 +- platform/glfw/glfw_apple_init.mm | 11 +- platform/glfw/glfw_metal_backend.h | 4 +- platform/glfw/glfw_metal_backend.mm | 18 +- platform/glfw/metal_backend.mm | 201 +- .../MLNAnnotationViewIntegrationTests.mm | 1232 +- .../MLNCameraTransitionFinishTests.mm | 107 +- .../Camera Tests/MLNCameraTransitionTests.mm | 521 +- .../Integration_Tests/MBGLIntegrationTests.mm | 335 +- ...MLNNetworkConfigurationIntegrationTests.mm | 468 +- platform/ios/app/MBXViewController.mm | 5060 +++---- platform/ios/benchmark/MBXBenchAppDelegate.mm | 13 +- .../ios/benchmark/MBXBenchViewController.mm | 457 +- platform/ios/benchmark/main.mm | 9 +- platform/ios/src/MLNAnnotationView.mm | 525 +- platform/ios/src/MLNCompassButton.mm | 156 +- .../MLNFaux3DUserLocationAnnotationView.mm | 1115 +- .../ios/src/MLNMapAccessibilityElement.mm | 272 +- platform/ios/src/MLNMapProjection.mm | 143 +- platform/ios/src/MLNMapView+Impl.h | 4 +- platform/ios/src/MLNMapView+Impl.mm | 269 +- platform/ios/src/MLNMapView+Metal.h | 12 +- platform/ios/src/MLNMapView+Metal.mm | 286 +- platform/ios/src/MLNMapView+OpenGL.h | 16 +- platform/ios/src/MLNMapView+OpenGL.mm | 283 +- platform/ios/src/MLNMapView.mm | 12352 ++++++++-------- platform/ios/src/MLNScaleBar.mm | 755 +- platform/ios/src/UIColor+MLNAdditions.mm | 117 +- platform/ios/src/UIImage+MLNAdditions.mm | 177 +- .../ios/test/MLNMapViewCompassViewTests.mm | 112 +- platform/ios/test/MLNMapViewDirectionTests.mm | 182 +- .../test/MLNMapViewGestureRecognizerTests.mm | 421 +- platform/ios/test/MLNMapViewZoomTests.mm | 270 +- platform/ios/test/core/iosTestRunner.mm | 95 +- platform/macos/src/MLNAttributionButton.mm | 58 +- platform/macos/src/MLNMapView+IBAdditions.mm | 96 +- platform/macos/src/MLNMapView+Impl.h | 4 +- platform/macos/src/MLNMapView+Impl.mm | 267 +- platform/macos/src/MLNMapView+Metal.h | 14 +- platform/macos/src/MLNMapView+Metal.mm | 194 +- platform/macos/src/MLNMapView+OpenGL.mm | 79 +- platform/macos/src/MLNMapView.mm | 4719 +++--- platform/macos/src/MLNOpenGLLayer.mm | 66 +- platform/macos/src/NSColor+MLNAdditions.mm | 185 +- platform/macos/src/NSImage+MLNAdditions.mm | 123 +- render-test/ios/iosTestRunner.mm | 208 +- test/src/main.mm | 4 +- 134 files changed, 29751 insertions(+), 27896 deletions(-) diff --git a/.clang-format b/.clang-format index d0489a0ee..02976f9f8 100644 --- a/.clang-format +++ b/.clang-format @@ -20,4 +20,5 @@ Language: Cpp Language: ObjC BasedOnStyle: Google ObjCSpaceAfterProperty: true +AccessModifierOffset: -2 ... diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f63be0398..8f93cabd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: v20.1.8 hooks: - id: clang-format - files: '.*\.(hpp|cpp|h)' + files: '.*\.(hpp|cpp|h|mm)' exclude: 'vendor/.*' - repo: https://github.com/keith/pre-commit-buildifier diff --git a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm index 1a9b9a675..71ee5908f 100644 --- a/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm +++ b/platform/darwin/app/ExampleCustomDrawableStyleLayer.mm @@ -1,17 +1,17 @@ #import "ExampleCustomDrawableStyleLayer.h" -#import "MLNStyleLayer.h" #import "MLNCustomDrawableStyleLayer.h" +#import "MLNStyleLayer.h" #import -#include #include #include #include #include +#include -#include #include +#include @interface MLNCustomDrawableStyleLayer (Internal) - (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer; @@ -22,271 +22,302 @@ class ExampleCustomDrawableStyleLayerHost; @implementation ExampleCustomDrawableStyleLayer - (instancetype)initWithIdentifier:(NSString *)identifier { - auto layer = std::make_unique(identifier.UTF8String, - std::make_unique(self)); - return self = [super initWithPendingLayer:std::move(layer)]; + auto layer = std::make_unique( + identifier.UTF8String, std::make_unique(self)); + return self = [super initWithPendingLayer:std::move(layer)]; } @end class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost { public: - ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) { - layerRef = styleLayer; - layer = nil; + ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) { + layerRef = styleLayer; + layer = nil; + } + + void initialize() override { + if (layerRef == nil) + return; + else if (layer == nil) + layer = layerRef; + } + + void update(Interface &interface) override { + // if we have built our drawable(s) already, either update or skip + if (interface.getDrawableCount()) return; + + constexpr float extent = mbgl::util::EXTENT; + + // add classic polylines + { + using namespace mbgl; + + // set tile + interface.setTileID({11, 327, 792}); + + constexpr auto numLines = 6; + Interface::LineOptions options[numLines]{ + {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, + /*width=*/8.0f, /*color=*/Color::red()}, + {/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, + /*width=*/4.0f, /*color=*/Color::blue()}, + {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, + /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)}, + {/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, + /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)}, + {/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, + /*width=*/0.5f, /*color=*/Color::black()}, + {/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, + /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)}, + }; + for (auto &opt : options) { + opt.geometry.beginCap = style::LineCapType::Butt; + opt.geometry.endCap = style::LineCapType::Butt; + opt.geometry.joinType = style::LineJoinType::Miter; + } + + constexpr auto numPoints = 10; + GeometryCoordinates polyline; + for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { + polyline.emplace_back(ipoint * extent / numPoints, + std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); + } + + for (auto index{0}; index < numLines; ++index) { + for (auto &p : polyline) { + p.y += extent / numLines; + } + + // set property values + interface.setLineOptions(options[index]); + + // add polyline + interface.addPolyline(polyline, Interface::LineShaderType::Classic); + } } - void initialize() override { - if (layerRef == nil) return; - else if (layer == nil) layer = layerRef; + // add wide vector polylines with tile coordinates + { + using namespace mbgl; + + // set tile + interface.setTileID({11, 327, 792}); + + constexpr auto numLines = 6; + Interface::LineOptions options[numLines]{ + {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, + /*width=*/8.0f, /*color=*/Color::red()}, + {/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, + /*width=*/4.0f, /*color=*/Color::blue()}, + {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, + /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)}, + {/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, + /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)}, + {/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, + /*width=*/0.5f, /*color=*/Color::black()}, + {/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, + /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)}, + }; + for (auto &opt : options) { + opt.geometry.beginCap = style::LineCapType::Butt; + opt.geometry.endCap = style::LineCapType::Butt; + opt.geometry.joinType = style::LineJoinType::Miter; + } + + constexpr auto numPoints = 10; + GeometryCoordinates polyline; + for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { + polyline.emplace_back(ipoint * extent / numPoints, + std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); + } + + for (auto index{0}; index < numLines; ++index) { + for (auto &p : polyline) { + if (0 == index) p.y += 0.25f * extent / numLines; + p.y += extent / numLines; + } + + // set property values + interface.setLineOptions(options[index]); + + // add polyline + interface.addPolyline(polyline, Interface::LineShaderType::WideVector); + + // add clone + for (auto &p : polyline) { + p.y += 0.05f * extent / numLines; + } + interface.addPolyline(polyline, Interface::LineShaderType::WideVector); + for (auto &p : polyline) { + p.y -= 0.05f * extent / numLines; + } + } } - void update(Interface& interface) override { + // add fill polygon + { + using namespace mbgl; - // if we have built our drawable(s) already, either update or skip - if (interface.getDrawableCount()) - return; + // set tile + interface.setTileID({11, 327, 790}); - constexpr float extent = mbgl::util::EXTENT; + GeometryCollection geometry{ + { + // ring 1 + {static_cast(extent * 0.1f), static_cast(extent * 0.2f)}, + {static_cast(extent * 0.5f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.7f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.5f), static_cast(extent * 1.0f)}, + {static_cast(extent * 0.0f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.1f), static_cast(extent * 0.2f)}, + }, + { + // ring 2 + {static_cast(extent * 0.1f), static_cast(extent * 0.25f)}, + {static_cast(extent * 0.15f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.25f), static_cast(extent * 0.45f)}, + {static_cast(extent * 0.1f), static_cast(extent * 0.25f)}, + }, + }; - // add classic polylines - { - using namespace mbgl; + // set properties + interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f}); - // set tile - interface.setTileID({11, 327, 792}); - - constexpr auto numLines = 6; - Interface::LineOptions options[numLines] { - {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() }, - {/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() }, - {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) }, - {/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) }, - {/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() }, - {/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) }, - }; - for(auto& opt: options) { - opt.geometry.beginCap = style::LineCapType::Butt; - opt.geometry.endCap = style::LineCapType::Butt; - opt.geometry.joinType = style::LineJoinType::Miter; - } - - constexpr auto numPoints = 10; - GeometryCoordinates polyline; - for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { - polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); - } - - for (auto index {0}; index < numLines; ++index) { - for(auto &p : polyline) { - p.y += extent / numLines; - } - - // set property values - interface.setLineOptions(options[index]); - - // add polyline - interface.addPolyline(polyline, Interface::LineShaderType::Classic); - } - } - - // add wide vector polylines with tile coordinates - { - using namespace mbgl; - - // set tile - interface.setTileID({11, 327, 792}); - - constexpr auto numLines = 6; - Interface::LineOptions options[numLines] { - {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() }, - {/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() }, - {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) }, - {/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) }, - {/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() }, - {/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) }, - }; - for(auto& opt: options) { - opt.geometry.beginCap = style::LineCapType::Butt; - opt.geometry.endCap = style::LineCapType::Butt; - opt.geometry.joinType = style::LineJoinType::Miter; - } - - constexpr auto numPoints = 10; - GeometryCoordinates polyline; - for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { - polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); - } - - for (auto index {0}; index < numLines; ++index) { - for(auto &p : polyline) { - if (0 == index) p.y += 0.25f * extent / numLines; - p.y += extent / numLines; - } - - // set property values - interface.setLineOptions(options[index]); - - // add polyline - interface.addPolyline(polyline, Interface::LineShaderType::WideVector); - - // add clone - for(auto &p : polyline) { - p.y += 0.05f * extent / numLines; - } - interface.addPolyline(polyline, Interface::LineShaderType::WideVector); - for(auto &p : polyline) { - p.y -= 0.05f * extent / numLines; - } - } - } - - // add fill polygon - { - using namespace mbgl; - - // set tile - interface.setTileID({11, 327, 790}); - - GeometryCollection geometry{ - { - // ring 1 - {static_cast(extent* 0.1f), static_cast(extent* 0.2f)}, - {static_cast(extent* 0.5f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.7f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.5f), static_cast(extent* 1.0f)}, - {static_cast(extent* 0.0f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.1f), static_cast(extent* 0.2f)}, - }, - { - // ring 2 - {static_cast(extent* 0.1f), static_cast(extent* 0.25f)}, - {static_cast(extent* 0.15f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.25f), static_cast(extent* 0.45f)}, - {static_cast(extent* 0.1f), static_cast(extent* 0.25f)}, - }, - }; - - // set properties - interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f}); - - // add fill - interface.addFill(geometry); - } - - // add symbol - { - using namespace mbgl; - - // set tile - interface.setTileID({11, 327, 789}); - - GeometryCoordinate position {static_cast(extent* 0.5f), static_cast(extent* 0.5f)}; - - // load image - UIImage *assetImage = [UIImage imageNamed:@"pin"]; - assert(assetImage.CGImage != NULL); - std::shared_ptr image = std::make_shared(MLNPremultipliedImageFromCGImage(assetImage.CGImage)); - - // set symbol options - Interface::SymbolOptions options; - options.texture = interface.context.createTexture2D(); - options.texture->setImage(image); - options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - const std::array, 2> textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; - const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0]; - const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1]; - assert(xspan > 0.0f && yspan > 0.0f); - options.size = {static_cast(image->size.width * xspan), static_cast(image->size.height * yspan)}; - options.anchor = {0.5f, 0.95f}; - options.angleDegrees = 45.0f; - options.scaleWithMap = true; - options.pitchWithMap = false; - interface.setSymbolOptions(options); - - // add symbol - interface.addSymbol(position, textureCoordinates); - } - - // add polylines using wide vectors using geographic coordinates - { - using namespace mbgl; - - // add polyline with geographic coordinates - Interface::LineOptions options = {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/12.0f, /*color=*/{.0f, .0f, .0f, .5f} }; - options.geometry.beginCap = style::LineCapType::Square; - options.geometry.endCap = style::LineCapType::Square; - options.geometry.joinType = style::LineJoinType::Bevel; - options.geometry.type = FeatureType::LineString; - interface.setLineOptions(options); - - LineString polyline_geo { - // San Francisco - {-122.38186800073211, 37.77466003457463}, - {-122.3869373450997, 37.774352128895615}, - {-122.38680767979824, 37.773294612880306}, - {-122.38476465260224, 37.773350946288765}, - {-122.38146223043374, 37.77194168964067}, - {-122.6813560305925, 37.666084247570964}, - {-122.26765538866474, 37.65037232584494}, - {-122.42528413673159, 38.020443518012584}, - // Seattle - {-122.34927775216809, 47.62050663596438}, - // New York - {-74.04454331829972, 40.6892168305434}, - }; - interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector); - } - - // add polylines using wide vectors in tile coordinates - { - using namespace mbgl; - - // set tile - interface.setTileID({11, 327, 790}); - - Interface::LineOptions options - {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/7.0f, /*color=*/{1.0f, 0, 0, .5f} }; - options.geometry.beginCap = style::LineCapType::Round; - options.geometry.endCap = style::LineCapType::Round; - options.geometry.joinType = style::LineJoinType::Round; - - // add polyline with tile coordinates - GeometryCollection polyline_tile{ - { - // ring 1 - {static_cast(extent* 0.1f), static_cast(extent* 0.2f)}, - {static_cast(extent* 0.5f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.7f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.5f), static_cast(extent* 1.0f)}, - {static_cast(extent* 0.0f), static_cast(extent* 0.5f)}, - }, - { - // ring 2 - {static_cast(extent* 0.1f), static_cast(extent* 0.25f)}, - {static_cast(extent* 0.15f), static_cast(extent* 0.5f)}, - {static_cast(extent* 0.25f), static_cast(extent* 0.45f)}, - }, - }; - - options.geometry.type = FeatureType::Polygon; - interface.setLineOptions(options); - interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector); - interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector); - - } - - // finish - interface.finish(); + // add fill + interface.addFill(geometry); } - void deinitialize() override { - if (layer == nil) return; + // add symbol + { + using namespace mbgl; - layerRef = layer; - layer = nil; + // set tile + interface.setTileID({11, 327, 789}); + + GeometryCoordinate position{static_cast(extent * 0.5f), + static_cast(extent * 0.5f)}; + + // load image + UIImage *assetImage = [UIImage imageNamed:@"pin"]; + assert(assetImage.CGImage != NULL); + std::shared_ptr image = std::make_shared( + MLNPremultipliedImageFromCGImage(assetImage.CGImage)); + + // set symbol options + Interface::SymbolOptions options; + options.texture = interface.context.createTexture2D(); + options.texture->setImage(image); + options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, + gfx::TextureWrapType::Clamp, + gfx::TextureWrapType::Clamp}); + const std::array, 2> textureCoordinates = { + {{0.0f, 0.08f}, {1.0f, 0.9f}}}; + const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0]; + const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1]; + assert(xspan > 0.0f && yspan > 0.0f); + options.size = {static_cast(image->size.width * xspan), + static_cast(image->size.height * yspan)}; + options.anchor = {0.5f, 0.95f}; + options.angleDegrees = 45.0f; + options.scaleWithMap = true; + options.pitchWithMap = false; + interface.setSymbolOptions(options); + + // add symbol + interface.addSymbol(position, textureCoordinates); } + + // add polylines using wide vectors using geographic coordinates + { + using namespace mbgl; + + // add polyline with geographic coordinates + Interface::LineOptions options = {/*geometry=*/{}, + /*blur=*/0.0f, + /*opacity=*/1.0f, + /*gapWidth=*/0.0f, + /*offset=*/0.0f, + /*width=*/12.0f, + /*color=*/{.0f, .0f, .0f, .5f}}; + options.geometry.beginCap = style::LineCapType::Square; + options.geometry.endCap = style::LineCapType::Square; + options.geometry.joinType = style::LineJoinType::Bevel; + options.geometry.type = FeatureType::LineString; + interface.setLineOptions(options); + + LineString polyline_geo{ + // San Francisco + {-122.38186800073211, 37.77466003457463}, + {-122.3869373450997, 37.774352128895615}, + {-122.38680767979824, 37.773294612880306}, + {-122.38476465260224, 37.773350946288765}, + {-122.38146223043374, 37.77194168964067}, + {-122.6813560305925, 37.666084247570964}, + {-122.26765538866474, 37.65037232584494}, + {-122.42528413673159, 38.020443518012584}, + // Seattle + {-122.34927775216809, 47.62050663596438}, + // New York + {-74.04454331829972, 40.6892168305434}, + }; + interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector); + } + + // add polylines using wide vectors in tile coordinates + { + using namespace mbgl; + + // set tile + interface.setTileID({11, 327, 790}); + + Interface::LineOptions options{/*geometry=*/{}, + /*blur=*/0.0f, + /*opacity=*/1.0f, + /*gapWidth=*/0.0f, + /*offset=*/0.0f, + /*width=*/7.0f, + /*color=*/{1.0f, 0, 0, .5f}}; + options.geometry.beginCap = style::LineCapType::Round; + options.geometry.endCap = style::LineCapType::Round; + options.geometry.joinType = style::LineJoinType::Round; + + // add polyline with tile coordinates + GeometryCollection polyline_tile{ + { + // ring 1 + {static_cast(extent * 0.1f), static_cast(extent * 0.2f)}, + {static_cast(extent * 0.5f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.7f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.5f), static_cast(extent * 1.0f)}, + {static_cast(extent * 0.0f), static_cast(extent * 0.5f)}, + }, + { + // ring 2 + {static_cast(extent * 0.1f), static_cast(extent * 0.25f)}, + {static_cast(extent * 0.15f), static_cast(extent * 0.5f)}, + {static_cast(extent * 0.25f), static_cast(extent * 0.45f)}, + }, + }; + + options.geometry.type = FeatureType::Polygon; + interface.setLineOptions(options); + interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector); + interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector); + } + + // finish + interface.finish(); + } + + void deinitialize() override { + if (layer == nil) return; + + layerRef = layer; + layer = nil; + } + private: - __weak ExampleCustomDrawableStyleLayer * layerRef; - ExampleCustomDrawableStyleLayer * layer = nil; + __weak ExampleCustomDrawableStyleLayer *layerRef; + ExampleCustomDrawableStyleLayer *layer = nil; }; diff --git a/platform/darwin/app/PluginLayerExample.mm b/platform/darwin/app/PluginLayerExample.mm index 0cb01054d..8e5db57fb 100644 --- a/platform/darwin/app/PluginLayerExample.mm +++ b/platform/darwin/app/PluginLayerExample.mm @@ -2,29 +2,24 @@ @implementation PluginLayerExample - // This is the layer type in the style that is used -+(MLNPluginLayerCapabilities *)layerCapabilities { - - MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; - tempResult.layerID = @"plugin-layer-test"; - tempResult.requiresPass3D = YES; - return tempResult; - ++ (MLNPluginLayerCapabilities *)layerCapabilities { + MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; + tempResult.layerID = @"plugin-layer-test"; + tempResult.requiresPass3D = YES; + return tempResult; } // The overrides --(void)onRenderLayer { - NSLog(@"PluginLayerExample: On Render Layer"); - +- (void)onRenderLayer { + NSLog(@"PluginLayerExample: On Render Layer"); } --(void)onUpdateLayer { - +- (void)onUpdateLayer { } --(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - // NSLog(@"Layer Properties: %@", layerProperties); +- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + // NSLog(@"Layer Properties: %@", layerProperties); } @end diff --git a/platform/darwin/app/PluginLayerExampleMetalRendering.mm b/platform/darwin/app/PluginLayerExampleMetalRendering.mm index 1fe951e14..e886c88ee 100644 --- a/platform/darwin/app/PluginLayerExampleMetalRendering.mm +++ b/platform/darwin/app/PluginLayerExampleMetalRendering.mm @@ -10,224 +10,208 @@ #import #import "Mapbox.h" -typedef struct -{ - vector_float2 position; - vector_float4 color; +typedef struct { + vector_float2 position; + vector_float4 color; } Vertex; @interface PluginLayerExampleMetalRendering () { - // The render pipeline state - id _pipelineState; - id _depthStencilStateWithoutStencil; - - // Properties - float _offsetX; - float _offsetY; - float _scale; - float _r, _g, _b, _a; - + // The render pipeline state + id _pipelineState; + id _depthStencilStateWithoutStencil; + // Properties + float _offsetX; + float _offsetY; + float _scale; + float _r, _g, _b, _a; } @end - @implementation PluginLayerExampleMetalRendering - // This is a static class method that is used by the MLNMapView to create the // required marshalling classes to interface with the MapLibre core. -+(MLNPluginLayerCapabilities *)layerCapabilities { ++ (MLNPluginLayerCapabilities *)layerCapabilities { + MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; + tempResult.layerID = @"plugin-layer-metal-rendering"; + tempResult.requiresPass3D = YES; - MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; - tempResult.layerID = @"plugin-layer-metal-rendering"; - tempResult.requiresPass3D = YES; + // Define the paint properties that this layer implements and + // what types they are + tempResult.layerProperties = @[ + // The scale property + [MLNPluginLayerProperty propertyWithName:@"scale" + propertyType:MLNPluginLayerPropertyTypeSingleFloat + defaultValue:@(1.0)], - // Define the paint properties that this layer implements and - // what types they are - tempResult.layerProperties = @[ - // The scale property - [MLNPluginLayerProperty propertyWithName:@"scale" - propertyType:MLNPluginLayerPropertyTypeSingleFloat - defaultValue:@(1.0)], + // The fill color property + [MLNPluginLayerProperty propertyWithName:@"fill-color" + propertyType:MLNPluginLayerPropertyTypeColor + defaultValue:[UIColor blueColor]] - // The fill color property - [MLNPluginLayerProperty propertyWithName:@"fill-color" - propertyType:MLNPluginLayerPropertyTypeColor - defaultValue:[UIColor blueColor]] - - ]; - - return tempResult; + ]; + return tempResult; } - (void)createShaders:(MLNMapView *)mapView { - MLNBackendResource* resource = [mapView backendResource]; + MLNBackendResource *resource = [mapView backendResource]; - NSString *shaderSource = @ + NSString *shaderSource = + @ " #include \n" -" using namespace metal;\n" -" typedef struct\n" -" {\n" -" vector_float2 position;\n" -" vector_float4 color;\n" -" } Vertex;\n" -" struct RasterizerData\n" -" {\n" -" float4 position [[position]];\n" -" float4 color;\n" -" };\n" -" vertex RasterizerData\n" -" vertexShader(uint vertexID [[vertex_id]],\n" -" constant Vertex *vertices [[buffer(0)]],\n" -" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n" -" {\n" -" RasterizerData out;\n" -" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n" -" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n" -" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n" -" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n" -" out.color = vertices[vertexID].color;\n" -" return out;\n" -" }\n" -" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n" -" {\n" -" return in.color;\n" -" }\n"; + " using namespace metal;\n" + " typedef struct\n" + " {\n" + " vector_float2 position;\n" + " vector_float4 color;\n" + " } Vertex;\n" + " struct RasterizerData\n" + " {\n" + " float4 position [[position]];\n" + " float4 color;\n" + " };\n" + " vertex RasterizerData\n" + " vertexShader(uint vertexID [[vertex_id]],\n" + " constant Vertex *vertices [[buffer(0)]],\n" + " constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n" + " {\n" + " RasterizerData out;\n" + " float2 pixelSpacePosition = vertices[vertexID].position.xy;\n" + " vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n" + " out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n" + " out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n" + " out.color = vertices[vertexID].color;\n" + " return out;\n" + " }\n" + " fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n" + " {\n" + " return in.color;\n" + " }\n"; + NSError *error = nil; + id _device = resource.device; + id library = [_device newLibraryWithSource:shaderSource options:nil error:&error]; + NSAssert(library, @"Error compiling shaders: %@", error); + id vertexFunction = [library newFunctionWithName:@"vertexShader"]; + id fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; - NSError *error = nil; - id _device = resource.device; - id library = [_device newLibraryWithSource:shaderSource options:nil error:&error]; - NSAssert(library, @"Error compiling shaders: %@", error); - id vertexFunction = [library newFunctionWithName:@"vertexShader"]; - id fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; + // Configure a pipeline descriptor that is used to create a pipeline state. + MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; + pipelineStateDescriptor.label = @"Simple Pipeline"; + pipelineStateDescriptor.vertexFunction = vertexFunction; + pipelineStateDescriptor.fragmentFunction = fragmentFunction; + pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat; + pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - // Configure a pipeline descriptor that is used to create a pipeline state. - MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; - pipelineStateDescriptor.label = @"Simple Pipeline"; - pipelineStateDescriptor.vertexFunction = vertexFunction; - pipelineStateDescriptor.fragmentFunction = fragmentFunction; - pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat; - pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor + error:&error]; + NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error); - _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor - error:&error]; - NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error); - - // Notice that we don't configure the stencilTest property, leaving stencil testing disabled - MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; - depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed - depthStencilDescriptor.depthWriteEnabled = NO; - - _depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; + // Notice that we don't configure the stencilTest property, leaving stencil testing disabled + MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; + depthStencilDescriptor.depthCompareFunction = + MTLCompareFunctionAlways; // Or another value as needed + depthStencilDescriptor.depthWriteEnabled = NO; + _depthStencilStateWithoutStencil = + [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; } - // Setup the rendering environment --(void)setupRendering:(MLNMapView *)mapView { - [self createShaders:mapView]; - if (_scale == 0) { - _scale = 1; - } - // Set the initial - _a = 1; - +- (void)setupRendering:(MLNMapView *)mapView { + [self createShaders:mapView]; + if (_scale == 0) { + _scale = 1; + } + // Set the initial + _a = 1; } // The overrides --(void)onRenderLayer:(MLNMapView *)mapView - renderEncoder:(id)renderEncoder { +- (void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder { + MLNBackendResource *resource = [mapView backendResource]; - MLNBackendResource* resource = [mapView backendResource]; + if (_pipelineState == nil) { + [self setupRendering:mapView]; + } - if (_pipelineState == nil) { - [self setupRendering:mapView]; - } + if (renderEncoder == nil) { + return; + } - if (renderEncoder == nil) { - return; - } + vector_uint2 _viewportSize; + _viewportSize.x = resource.mtkView.drawableSize.width; + _viewportSize.y = resource.mtkView.drawableSize.height; + Vertex triangleVerticesWithColor[] = { + // 2D positions, RGBA colors + {{(250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}}, + {{(-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}}, + {{(0 + _offsetX) * _scale, (250 + _offsetY) * _scale}, {_r, _g, _b, _a}}, + }; - vector_uint2 _viewportSize; - _viewportSize.x = resource.mtkView.drawableSize.width; - _viewportSize.y = resource.mtkView.drawableSize.height; + [renderEncoder setRenderPipelineState:_pipelineState]; + [renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil]; - Vertex triangleVerticesWithColor[] = { - // 2D positions, RGBA colors - { { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } }, - { { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} }, - { { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } }, - }; + // Pass in the parameter data. + [renderEncoder setVertexBytes:triangleVerticesWithColor + length:sizeof(triangleVerticesWithColor) + atIndex:0]; - [renderEncoder setRenderPipelineState:_pipelineState]; - [renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil]; + [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1]; - // Pass in the parameter data. - [renderEncoder setVertexBytes:triangleVerticesWithColor - length:sizeof(triangleVerticesWithColor) - atIndex:0]; - - [renderEncoder setVertexBytes:&_viewportSize - length:sizeof(_viewportSize) - atIndex:1]; - - // Draw the triangle. - [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle - vertexStart:0 - vertexCount:3]; + // Draw the triangle. + [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3]; } --(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { - // This is called before the render call and is a place where - // any animations/etc can be updated +- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { + // This is called before the render call and is a place where + // any animations/etc can be updated } --(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - //NSLog(@"Metal Layer Rendering Properties: %@", layerProperties); +- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + // NSLog(@"Metal Layer Rendering Properties: %@", layerProperties); - // These two properties are in the "properties" section of the style sheet and - // are passed in at layer creation. These are not expression based properties - NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"]; - if (offsetX) { - _offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue]; + // These two properties are in the "properties" section of the style sheet and + // are passed in at layer creation. These are not expression based properties + NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"]; + if (offsetX) { + _offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue]; + } + + NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"]; + if (offsetY) { + _offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue]; + } + + // These are the paint properties and can be updated each frame or zoom level + // change/etc depending on if they are static or expression based/etc. + NSNumber *scale = [layerProperties objectForKey:@"scale"]; + if (scale) { + if ([scale isKindOfClass:[NSNumber class]]) { + _scale = [scale floatValue]; } + } - NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"]; - if (offsetY) { - _offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue]; + NSString *fillColor = [layerProperties objectForKey:@"fill-color"]; + if (fillColor) { + // NSLog(@"Fill Color: %@", fillColor); + fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""]; + fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""]; + NSArray *components = [fillColor componentsSeparatedByString:@","]; + if ([components count] == 4) { + // Convert to float since they come in as 0..255 + _r = [[components objectAtIndex:0] floatValue] / 255.0; + _g = [[components objectAtIndex:1] floatValue] / 255.0; + _b = [[components objectAtIndex:2] floatValue] / 255.0; + _a = [[components objectAtIndex:3] floatValue]; } - - // These are the paint properties and can be updated each frame or zoom level - // change/etc depending on if they are static or expression based/etc. - NSNumber *scale = [layerProperties objectForKey:@"scale"]; - if (scale) { - if ([scale isKindOfClass:[NSNumber class]]) { - _scale = [scale floatValue]; - } - } - - NSString *fillColor = [layerProperties objectForKey:@"fill-color"]; - if (fillColor) { - //NSLog(@"Fill Color: %@", fillColor); - fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""]; - fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""]; - NSArray *components = [fillColor componentsSeparatedByString:@","]; - if ([components count] == 4) { - // Convert to float since they come in as 0..255 - _r = [[components objectAtIndex:0] floatValue] / 255.0; - _g = [[components objectAtIndex:1] floatValue] / 255.0; - _b = [[components objectAtIndex:2] floatValue] / 255.0; - _a = [[components objectAtIndex:3] floatValue]; - } - - } - + } } @end diff --git a/platform/darwin/core/collator.mm b/platform/darwin/core/collator.mm index 8e0248860..237e32550 100644 --- a/platform/darwin/core/collator.mm +++ b/platform/darwin/core/collator.mm @@ -9,59 +9,56 @@ namespace platform { class Collator::Impl { public: - Impl(bool caseSensitive, bool diacriticSensitive, const std::optional& locale_) - : options((caseSensitive ? 0 : NSCaseInsensitiveSearch) | - (diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)) - , locale(locale_ ? - [[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())] : - [NSLocale currentLocale]) - {} + Impl(bool caseSensitive, bool diacriticSensitive, const std::optional& locale_) + : options((caseSensitive ? 0 : NSCaseInsensitiveSearch) | + (diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)), + locale(locale_ ? [[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())] + : [NSLocale currentLocale]) {} - bool operator==(const Impl& other) const { - return options == other.options && - [[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]]; - } + bool operator==(const Impl& other) const { + return options == other.options && + [[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]]; + } - int compare(const std::string& lhs, const std::string& rhs) const { - NSString* nsLhs = @(lhs.c_str()); - NSString* nsRhs = @(rhs.c_str()); - // Limiting the compare range to the length of the LHS seems weird, but - // experimentally we've checked that if LHS is a prefix of RHS compare returns -1 - // https://developer.apple.com/documentation/foundation/nsstring/1414561-compare - NSRange compareRange = NSMakeRange(0, nsLhs.length); + int compare(const std::string& lhs, const std::string& rhs) const { + NSString* nsLhs = @(lhs.c_str()); + NSString* nsRhs = @(rhs.c_str()); + // Limiting the compare range to the length of the LHS seems weird, but + // experimentally we've checked that if LHS is a prefix of RHS compare returns -1 + // https://developer.apple.com/documentation/foundation/nsstring/1414561-compare + NSRange compareRange = NSMakeRange(0, nsLhs.length); - return static_cast([nsLhs compare:nsRhs options:options range:compareRange locale:locale]); - } + return static_cast([nsLhs compare:nsRhs options:options range:compareRange locale:locale]); + } + + std::string resolvedLocale() const { + // Following documentation at: + // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9 + // We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format + // may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47 + // compliant. Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is + // used by + // `currentLocale`, we don't want to return the underscore. + return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"] + .UTF8String; + } - std::string resolvedLocale() const { - // Following documentation at: - // https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9 - // We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format - // may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47 compliant. - // Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is used by - // `currentLocale`, we don't want to return the underscore. - return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"].UTF8String; - } private: - NSStringCompareOptions options; - NSLocale* locale; + NSStringCompareOptions options; + NSLocale* locale; }; -Collator::Collator(bool caseSensitive, bool diacriticSensitive, const std::optional& locale_) - : impl(std::make_shared(caseSensitive, diacriticSensitive, locale_)) -{} +Collator::Collator(bool caseSensitive, bool diacriticSensitive, + const std::optional& locale_) + : impl(std::make_shared(caseSensitive, diacriticSensitive, locale_)) {} -bool Collator::operator==(const Collator& other) const { - return *impl == *(other.impl); -} +bool Collator::operator==(const Collator& other) const { return *impl == *(other.impl); } int Collator::compare(const std::string& lhs, const std::string& rhs) const { - return impl->compare(lhs, rhs); + return impl->compare(lhs, rhs); } -std::string Collator::resolvedLocale() const { - return impl->resolvedLocale(); -} +std::string Collator::resolvedLocale() const { return impl->resolvedLocale(); } -} // namespace platform -} // namespace mbgl +} // namespace platform +} // namespace mbgl diff --git a/platform/darwin/core/http_file_source.mm b/platform/darwin/core/http_file_source.mm index 95655f469..23c29c4b3 100644 --- a/platform/darwin/core/http_file_source.mm +++ b/platform/darwin/core/http_file_source.mm @@ -3,17 +3,17 @@ #include #include +#include +#include #include #include -#include -#include #include #import #include -#include #include +#include @interface MBGLBundleCanary : NSObject @end @@ -26,422 +26,422 @@ namespace mbgl { // Data that is shared between the requesting thread and the thread running the completion handler. class HTTPRequestShared { public: - HTTPRequestShared(Response& response_, util::AsyncTask& async_) - : response(response_), - async(async_) { - } + HTTPRequestShared(Response &response_, util::AsyncTask &async_) + : response(response_), async(async_) {} - void notify(const Response& response_) { - std::scoped_lock lock(mutex); - if (!cancelled) { - response = response_; - async.send(); - } + void notify(const Response &response_) { + std::scoped_lock lock(mutex); + if (!cancelled) { + response = response_; + async.send(); } + } - void cancel() { - std::scoped_lock lock(mutex); - cancelled = true; - } + void cancel() { + std::scoped_lock lock(mutex); + cancelled = true; + } private: - std::mutex mutex; - bool cancelled = false; + std::mutex mutex; + bool cancelled = false; - Response& response; - util::AsyncTask& async; + Response &response; + util::AsyncTask &async; }; class HTTPRequest : public AsyncRequest { public: - HTTPRequest(FileSource::Callback callback_) - : shared(std::make_shared(response, async)), - callback(callback_) { - } + HTTPRequest(FileSource::Callback callback_) + : shared(std::make_shared(response, async)), callback(callback_) {} - ~HTTPRequest() override { - shared->cancel(); - if (task) { - [task cancel]; - task = nil; - } + ~HTTPRequest() override { + shared->cancel(); + if (task) { + [task cancel]; + task = nil; } + } - std::shared_ptr shared; - NSURLSessionDataTask* task = nil; + std::shared_ptr shared; + NSURLSessionDataTask *task = nil; private: - FileSource::Callback callback; - Response response; + FileSource::Callback callback; + Response response; - util::AsyncTask async { [this] { - // Calling `callback` may result in deleting `this`. Copy data to temporaries first. - auto callback_ = callback; - auto response_ = response; - callback_(response_); - } }; + util::AsyncTask async{[this] { + // Calling `callback` may result in deleting `this`. Copy data to temporaries first. + auto callback_ = callback; + auto response_ = response; + callback_(response_); + }}; }; class HTTPFileSource::Impl { public: - Impl(const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_) - : resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) { - NSURLSessionConfiguration *sessionConfig = MLNNativeNetworkManager.sharedManager.sessionConfiguration; - session = [NSURLSession sessionWithConfiguration:sessionConfig]; + Impl(const ResourceOptions &resourceOptions_, const ClientOptions &clientOptions_) + : resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) { + NSURLSessionConfiguration *sessionConfig = + MLNNativeNetworkManager.sharedManager.sessionConfiguration; + session = [NSURLSession sessionWithConfiguration:sessionConfig]; - if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) { - userAgent = getUserAgent(); - } else { - userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"]; - } + if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) { + userAgent = getUserAgent(); + } else { + userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"]; } + } - void setResourceOptions(ResourceOptions options); - ResourceOptions getResourceOptions(); + void setResourceOptions(ResourceOptions options); + ResourceOptions getResourceOptions(); - void setClientOptions(ClientOptions options); - ClientOptions getClientOptions(); + void setClientOptions(ClientOptions options); + ClientOptions getClientOptions(); - NSURLSession* session = nil; - NSString* userAgent = nil; + NSURLSession *session = nil; + NSString *userAgent = nil; private: - NSString* getUserAgent() const; - NSBundle* getSDKBundle() const; - ResourceOptions resourceOptions; - ClientOptions clientOptions; + NSString *getUserAgent() const; + NSBundle *getSDKBundle() const; + ResourceOptions resourceOptions; + ClientOptions clientOptions; }; void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) { - resourceOptions = options; + resourceOptions = options; } -ResourceOptions HTTPFileSource::Impl::getResourceOptions() { - return resourceOptions.clone(); -} +ResourceOptions HTTPFileSource::Impl::getResourceOptions() { return resourceOptions.clone(); } -void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { - clientOptions = options; -} +void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { clientOptions = options; } -ClientOptions HTTPFileSource::Impl::getClientOptions() { - return clientOptions.clone(); -} +ClientOptions HTTPFileSource::Impl::getClientOptions() { return clientOptions.clone(); } NSString *HTTPFileSource::Impl::getUserAgent() const { - NSMutableArray *userAgentComponents = [NSMutableArray array]; + NSMutableArray *userAgentComponents = [NSMutableArray array]; - NSBundle *appBundle = [NSBundle mainBundle]; - if (appBundle) { - NSString *appName = appBundle.infoDictionary[@"CFBundleName"]; - [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", - appName.length ? appName : appBundle.infoDictionary[@"CFBundleIdentifier"], - appBundle.infoDictionary[@"CFBundleShortVersionString"]]]; - } else { - [userAgentComponents addObject:[NSProcessInfo processInfo].processName]; + NSBundle *appBundle = [NSBundle mainBundle]; + if (appBundle) { + NSString *appName = appBundle.infoDictionary[@"CFBundleName"]; + [userAgentComponents + addObject:[NSString + stringWithFormat:@"%@/%@", + appName.length + ? appName + : appBundle.infoDictionary[@"CFBundleIdentifier"], + appBundle.infoDictionary[@"CFBundleShortVersionString"]]]; + } else { + [userAgentComponents addObject:[NSProcessInfo processInfo].processName]; + } + + NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle(); + if (sdkBundle) { + NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"]; + if (versionString) { + [userAgentComponents + addObject:[NSString stringWithFormat:@"%@/%@", sdkBundle.infoDictionary[@"CFBundleName"], + versionString]]; } + } - NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle(); - if (sdkBundle) { - NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"]; - if (versionString) { - [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", - sdkBundle.infoDictionary[@"CFBundleName"], versionString]]; - } - } + // Avoid %s here because it inserts hidden bidirectional markers on macOS when the system + // language is set to a right-to-left language. + [userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)", + @(mbgl::version::revision)]]; - // Avoid %s here because it inserts hidden bidirectional markers on macOS when the system - // language is set to a right-to-left language. - [userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)", - @(mbgl::version::revision)]]; - - NSString *systemName = @"Darwin"; + NSString *systemName = @"Darwin"; #if TARGET_OS_IPHONE - systemName = @"iOS"; + systemName = @"iOS"; #elif TARGET_OS_MAC - systemName = @"macOS"; + systemName = @"macOS"; #elif TARGET_OS_WATCH - systemName = @"watchOS"; + systemName = @"watchOS"; #elif TARGET_OS_TV - systemName = @"tvOS"; + systemName = @"tvOS"; #endif #if TARGET_OS_SIMULATOR - systemName = [systemName stringByAppendingString:@" Simulator"]; + systemName = [systemName stringByAppendingString:@" Simulator"]; #endif - NSString *systemVersion = nil; - if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) { - NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; - systemVersion = [NSString stringWithFormat:@"%ld.%ld.%ld", - (long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion]; - } - if (systemVersion) { - [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]]; - } + NSString *systemVersion = nil; + if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) { + NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; + systemVersion = + [NSString stringWithFormat:@"%ld.%ld.%ld", (long)osVersion.majorVersion, + (long)osVersion.minorVersion, (long)osVersion.patchVersion]; + } + if (systemVersion) { + [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]]; + } - NSString *cpu = nil; + NSString *cpu = nil; #if TARGET_CPU_X86 - cpu = @"x86"; + cpu = @"x86"; #elif TARGET_CPU_X86_64 - cpu = @"x86_64"; + cpu = @"x86_64"; #elif TARGET_CPU_ARM - cpu = @"arm"; + cpu = @"arm"; #elif TARGET_CPU_ARM64 - cpu = @"arm64"; + cpu = @"arm64"; #endif - if (cpu) { - [userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]]; - } + if (cpu) { + [userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]]; + } - return [userAgentComponents componentsJoinedByString:@" "]; + return [userAgentComponents componentsJoinedByString:@" "]; } NSBundle *HTTPFileSource::Impl::getSDKBundle() const { - NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]]; - if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { - // For static frameworks, the class is contained in the application bundle rather than the - // framework bundle. - bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath - stringByAppendingPathComponent:@"Mapbox.framework"]]; - } - return bundle; + NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]]; + if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { + // For static frameworks, the class is contained in the application bundle rather than the + // framework bundle. + bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath + stringByAppendingPathComponent:@"Mapbox.framework"]]; + } + return bundle; } -HTTPFileSource::HTTPFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) - : impl(std::make_unique(resourceOptions, clientOptions)) { -} +HTTPFileSource::HTTPFileSource(const ResourceOptions &resourceOptions, + const ClientOptions &clientOptions) + : impl(std::make_unique(resourceOptions, clientOptions)) {} HTTPFileSource::~HTTPFileSource() = default; MLN_APPLE_EXPORT BOOL isValidMapboxEndpoint(NSURL *url) { - return ([url.host isEqualToString:@"mapbox.com"] || - [url.host hasSuffix:@".mapbox.com"] || - [url.host isEqualToString:@"mapbox.cn"] || - [url.host hasSuffix:@".mapbox.cn"]); + return ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"] || + [url.host isEqualToString:@"mapbox.cn"] || [url.host hasSuffix:@".mapbox.cn"]); } MLN_APPLE_EXPORT -NSURL *resourceURL(const Resource& resource) { - - NSURL *url = [NSURL URLWithString:@(resource.url.c_str())]; +NSURL *resourceURL(const Resource &resource) { + NSURL *url = [NSURL URLWithString:@(resource.url.c_str())]; #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - if (isValidMapboxEndpoint(url)) { - NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - NSMutableArray *queryItems = [NSMutableArray array]; + if (isValidMapboxEndpoint(url)) { + NSURLComponents *components = [NSURLComponents componentsWithURL:url + resolvingAgainstBaseURL:NO]; + NSMutableArray *queryItems = [NSMutableArray array]; - if (resource.usage == Resource::Usage::Offline) { - [queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]]; - } - - if (components.queryItems) { - [queryItems addObjectsFromArray:components.queryItems]; - } - - components.queryItems = queryItems; - url = components.URL; + if (resource.usage == Resource::Usage::Offline) { + [queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]]; } + + if (components.queryItems) { + [queryItems addObjectsFromArray:components.queryItems]; + } + + components.queryItems = queryItems; + url = components.URL; + } #endif - return url; + return url; } -std::unique_ptr HTTPFileSource::request(const Resource& resource, std::function callback) { - auto request = std::make_unique(std::move(callback)); - auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below. +std::unique_ptr HTTPFileSource::request(const Resource &resource, + std::function callback) { + auto request = std::make_unique(std::move(callback)); + auto shared = request->shared; // Explicit copy so that it also gets copied into the completion + // handler block below. - @autoreleasepool { - NSURL *url = resourceURL(resource); - [MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath]; + @autoreleasepool { + NSURL *url = resourceURL(resource); + [MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath]; - NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url]; - if (resource.priorEtag) { - [req addValue:@(resource.priorEtag->c_str()) - forHTTPHeaderField:@"If-None-Match"]; - } else if (resource.priorModified) { - [req addValue:@(util::rfc1123(*resource.priorModified).c_str()) - forHTTPHeaderField:@"If-Modified-Since"]; - } - - if (resource.dataRange) { - NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld", - static_cast(resource.dataRange->first), - static_cast(resource.dataRange->second)]; - [req setValue:rangeHeader forHTTPHeaderField:@"Range"]; - } - - [req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"]; - - const bool isTile = resource.kind == mbgl::Resource::Kind::Tile; - - if (isTile) { - [MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"]; - } - - __block NSURLSession *session = nil; - - // Use the delegate's session if there is one, otherwise use the one that - // was created when this class was constructed. - MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager; - if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) { - session = [networkManager.delegate sessionForNetworkManager:networkManager]; - } - - if (!session) { - session = impl->session; - } - - assert(session); - - if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) { - req = [networkManager.delegate willSendRequest:req]; - } - - request->task = [session - dataTaskWithRequest:req - completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { - session = nil; - - if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) { - MLNInternalNetworkResponse *networkResponse = [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error]; - networkResponse = [networkManager.delegate didReceiveResponse:networkResponse]; - data = networkResponse.data; - res = networkResponse.response; - error = networkResponse.error; - } - - - if (error && [error code] == NSURLErrorCancelled) { - [MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res]; - return; - } - [MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res]; - Response response; - using Error = Response::Error; - - if (error) { - [MLNNativeNetworkManager.sharedManager errorLog:@"Requesting: %@ failed with error: %@", req.URL, error.debugDescription]; - - if (data) { - response.data = - std::make_shared((const char*)[data bytes], [data length]); - } - - switch ([error code]) { - case NSURLErrorBadServerResponse: // 5xx errors - response.error = std::make_unique( - Error::Reason::Server, [[error localizedDescription] UTF8String]); - break; - - case NSURLErrorNetworkConnectionLost: - case NSURLErrorCannotFindHost: - case NSURLErrorCannotConnectToHost: - case NSURLErrorDNSLookupFailed: - case NSURLErrorNotConnectedToInternet: - case NSURLErrorInternationalRoamingOff: - case NSURLErrorCallIsActive: - case NSURLErrorDataNotAllowed: - case NSURLErrorTimedOut: - response.error = std::make_unique( - Error::Reason::Connection, [[error localizedDescription] UTF8String]); - break; - - default: - response.error = std::make_unique( - Error::Reason::Other, [[error localizedDescription] UTF8String]); - break; - } - } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { - const long responseCode = [(NSHTTPURLResponse *)res statusCode]; - [MLNNativeNetworkManager.sharedManager debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath, responseCode]; - - NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; - NSString *cache_control = [headers objectForKey:@"Cache-Control"]; - if (cache_control) { - const auto cc = http::CacheControl::parse([cache_control UTF8String]); - response.expires = cc.toTimePoint(); - response.mustRevalidate = cc.mustRevalidate; - } - - NSString *expires = [headers objectForKey:@"Expires"]; - if (expires) { - response.expires = util::parseTimestamp([expires UTF8String]); - } - - NSString *last_modified = [headers objectForKey:@"Last-Modified"]; - if (last_modified) { - response.modified = util::parseTimestamp([last_modified UTF8String]); - } - - NSString *etag = [headers objectForKey:@"ETag"]; - if (etag) { - response.etag = std::string([etag UTF8String]); - } - - if (responseCode == 200 || responseCode == 206) { - response.data = std::make_shared((const char *)[data bytes], [data length]); - } else if (responseCode == 204 || (responseCode == 404 && isTile)) { - response.noContent = true; - } else if (responseCode == 304) { - response.notModified = true; - } else if (responseCode == 404) { - response.error = - std::make_unique(Error::Reason::NotFound, "HTTP status code 404"); - } else if (responseCode == 429) { - // Get the standard header - std::optional retryAfter; - NSString *retryAfterHeader = headers[@"Retry-After"]; - if (retryAfterHeader) { - retryAfter = std::string([retryAfterHeader UTF8String]); - } - - // Fallback mapbox specific header - std::optional xRateLimitReset; - NSString *xReset = headers[@"x-rate-limit-reset"]; - if (xReset) { - xRateLimitReset = std::string([xReset UTF8String]); - } - - response.error = std::make_unique(Error::Reason::RateLimit, "HTTP status code 429", http::parseRetryHeaders(retryAfter, xRateLimitReset)); - } else if (responseCode >= 500 && responseCode < 600) { - response.error = - std::make_unique(Error::Reason::Server, std::string{ "HTTP status code " } + - std::to_string(responseCode)); - } else { - response.error = - std::make_unique(Error::Reason::Other, std::string{ "HTTP status code " } + - std::to_string(responseCode)); - } - } else if ([url isFileURL]) { - response.data = std::make_shared((const char *)[data bytes], [data length]); - } else { - // This should never happen. - response.error = std::make_unique(Error::Reason::Other, - "Response class is not NSHTTPURLResponse"); - } - - shared->notify(response); - }]; - - [request->task resume]; + NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url]; + if (resource.priorEtag) { + [req addValue:@(resource.priorEtag->c_str()) forHTTPHeaderField:@"If-None-Match"]; + } else if (resource.priorModified) { + [req addValue:@(util::rfc1123(*resource.priorModified).c_str()) + forHTTPHeaderField:@"If-Modified-Since"]; } - return std::move(request); + if (resource.dataRange) { + NSString *rangeHeader = [NSString + stringWithFormat:@"bytes=%lld-%lld", static_cast(resource.dataRange->first), + static_cast(resource.dataRange->second)]; + [req setValue:rangeHeader forHTTPHeaderField:@"Range"]; + } + + [req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"]; + + const bool isTile = resource.kind == mbgl::Resource::Kind::Tile; + + if (isTile) { + [MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"]; + } + + __block NSURLSession *session = nil; + + // Use the delegate's session if there is one, otherwise use the one that + // was created when this class was constructed. + MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager; + if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) { + session = [networkManager.delegate sessionForNetworkManager:networkManager]; + } + + if (!session) { + session = impl->session; + } + + assert(session); + + if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) { + req = [networkManager.delegate willSendRequest:req]; + } + + request->task = [session + dataTaskWithRequest:req + completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) { + session = nil; + + if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) { + MLNInternalNetworkResponse *networkResponse = + [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error]; + networkResponse = [networkManager.delegate didReceiveResponse:networkResponse]; + data = networkResponse.data; + res = networkResponse.response; + error = networkResponse.error; + } + + if (error && [error code] == NSURLErrorCancelled) { + [MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res]; + return; + } + [MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res]; + Response response; + using Error = Response::Error; + + if (error) { + [MLNNativeNetworkManager.sharedManager + errorLog:@"Requesting: %@ failed with error: %@", req.URL, + error.debugDescription]; + + if (data) { + response.data = + std::make_shared((const char *)[data bytes], [data length]); + } + + switch ([error code]) { + case NSURLErrorBadServerResponse: // 5xx errors + response.error = std::make_unique( + Error::Reason::Server, [[error localizedDescription] UTF8String]); + break; + + case NSURLErrorNetworkConnectionLost: + case NSURLErrorCannotFindHost: + case NSURLErrorCannotConnectToHost: + case NSURLErrorDNSLookupFailed: + case NSURLErrorNotConnectedToInternet: + case NSURLErrorInternationalRoamingOff: + case NSURLErrorCallIsActive: + case NSURLErrorDataNotAllowed: + case NSURLErrorTimedOut: + response.error = std::make_unique( + Error::Reason::Connection, [[error localizedDescription] UTF8String]); + break; + + default: + response.error = std::make_unique( + Error::Reason::Other, [[error localizedDescription] UTF8String]); + break; + } + } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { + const long responseCode = [(NSHTTPURLResponse *)res statusCode]; + [MLNNativeNetworkManager.sharedManager + debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath, + responseCode]; + + NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; + NSString *cache_control = [headers objectForKey:@"Cache-Control"]; + if (cache_control) { + const auto cc = http::CacheControl::parse([cache_control UTF8String]); + response.expires = cc.toTimePoint(); + response.mustRevalidate = cc.mustRevalidate; + } + + NSString *expires = [headers objectForKey:@"Expires"]; + if (expires) { + response.expires = util::parseTimestamp([expires UTF8String]); + } + + NSString *last_modified = [headers objectForKey:@"Last-Modified"]; + if (last_modified) { + response.modified = util::parseTimestamp([last_modified UTF8String]); + } + + NSString *etag = [headers objectForKey:@"ETag"]; + if (etag) { + response.etag = std::string([etag UTF8String]); + } + + if (responseCode == 200 || responseCode == 206) { + response.data = + std::make_shared((const char *)[data bytes], [data length]); + } else if (responseCode == 204 || (responseCode == 404 && isTile)) { + response.noContent = true; + } else if (responseCode == 304) { + response.notModified = true; + } else if (responseCode == 404) { + response.error = + std::make_unique(Error::Reason::NotFound, "HTTP status code 404"); + } else if (responseCode == 429) { + // Get the standard header + std::optional retryAfter; + NSString *retryAfterHeader = headers[@"Retry-After"]; + if (retryAfterHeader) { + retryAfter = std::string([retryAfterHeader UTF8String]); + } + + // Fallback mapbox specific header + std::optional xRateLimitReset; + NSString *xReset = headers[@"x-rate-limit-reset"]; + if (xReset) { + xRateLimitReset = std::string([xReset UTF8String]); + } + + response.error = + std::make_unique(Error::Reason::RateLimit, "HTTP status code 429", + http::parseRetryHeaders(retryAfter, xRateLimitReset)); + } else if (responseCode >= 500 && responseCode < 600) { + response.error = std::make_unique( + Error::Reason::Server, + std::string{"HTTP status code "} + std::to_string(responseCode)); + } else { + response.error = + std::make_unique(Error::Reason::Other, std::string{"HTTP status code "} + + std::to_string(responseCode)); + } + } else if ([url isFileURL]) { + response.data = + std::make_shared((const char *)[data bytes], [data length]); + } else { + // This should never happen. + response.error = std::make_unique(Error::Reason::Other, + "Response class is not NSHTTPURLResponse"); + } + + shared->notify(response); + }]; + + [request->task resume]; + } + + return std::move(request); } void HTTPFileSource::setResourceOptions(ResourceOptions options) { - impl->setResourceOptions(options.clone()); + impl->setResourceOptions(options.clone()); } -ResourceOptions HTTPFileSource::getResourceOptions() { - return impl->getResourceOptions(); -} +ResourceOptions HTTPFileSource::getResourceOptions() { return impl->getResourceOptions(); } void HTTPFileSource::setClientOptions(ClientOptions options) { - impl->setClientOptions(options.clone()); + impl->setClientOptions(options.clone()); } -ClientOptions HTTPFileSource::getClientOptions() { - return impl->getClientOptions(); -} +ClientOptions HTTPFileSource::getClientOptions() { return impl->getClientOptions(); } -} +} // namespace mbgl diff --git a/platform/darwin/core/image.mm b/platform/darwin/core/image.mm index 6b3ce3e33..19d10e9a9 100644 --- a/platform/darwin/core/image.mm +++ b/platform/darwin/core/image.mm @@ -12,85 +12,85 @@ using CGColorSpaceHandle = CFHandle; CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src) { - // We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking - // over ownership of the memory. - CGDataProviderHandle provider(CGDataProviderCreateWithData( - NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) { - delete[] reinterpret_cast(data); - })); - if (!provider) { - return nil; - } + // We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking + // over ownership of the memory. + CGDataProviderHandle provider(CGDataProviderCreateWithData( + NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) { + delete[] reinterpret_cast(data); + })); + if (!provider) { + return nil; + } - // If we successfully created the provider, it will take over management of the memory segment. - src.data.release(); + // If we successfully created the provider, it will take over management of the memory segment. + src.data.release(); - CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); - if (!colorSpace) { - return nil; - } + CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); + if (!colorSpace) { + return nil; + } - constexpr const size_t bitsPerComponent = 8; - constexpr const size_t bytesPerPixel = 4; - constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel; - const size_t bytesPerRow = bytesPerPixel * src.size.width; - const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | static_cast(kCGImageAlphaPremultipliedLast); + constexpr const size_t bitsPerComponent = 8; + constexpr const size_t bytesPerPixel = 4; + constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel; + const size_t bytesPerRow = bytesPerPixel * src.size.width; + const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | + static_cast(kCGImageAlphaPremultipliedLast); - return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel, - bytesPerRow, *colorSpace, - bitmapInfo, *provider, - NULL, false, kCGRenderingIntentDefault); + return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel, bytesPerRow, + *colorSpace, bitmapInfo, *provider, NULL, false, kCGRenderingIntentDefault); } mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) { - const size_t width = CGImageGetWidth(src); - const size_t height = CGImageGetHeight(src); + const size_t width = CGImageGetWidth(src); + const size_t height = CGImageGetHeight(src); - mbgl::PremultipliedImage image({ static_cast(width), static_cast(height) }); + mbgl::PremultipliedImage image({static_cast(width), static_cast(height)}); - CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); - if (!colorSpace) { - throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); - } + CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); + if (!colorSpace) { + throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); + } - constexpr const size_t bitsPerComponent = 8; - constexpr const size_t bytesPerPixel = 4; - const size_t bytesPerRow = bytesPerPixel * width; - const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | static_cast(kCGImageAlphaPremultipliedLast); + constexpr const size_t bitsPerComponent = 8; + constexpr const size_t bytesPerPixel = 4; + const size_t bytesPerRow = bytesPerPixel * width; + const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | + static_cast(kCGImageAlphaPremultipliedLast); - CGContextHandle context(CGBitmapContextCreate( - image.data.get(), width, height, bitsPerComponent, bytesPerRow, *colorSpace, bitmapInfo)); - if (!context) { - throw std::runtime_error("CGBitmapContextCreate failed"); - } + CGContextHandle context(CGBitmapContextCreate(image.data.get(), width, height, bitsPerComponent, + bytesPerRow, *colorSpace, bitmapInfo)); + if (!context) { + throw std::runtime_error("CGBitmapContextCreate failed"); + } - CGContextSetBlendMode(*context, kCGBlendModeCopy); - CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src); + CGContextSetBlendMode(*context, kCGBlendModeCopy); + CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src); - return image; + return image; } namespace mbgl { PremultipliedImage decodeImage(const std::string& source) { - CFDataHandle data(CFDataCreateWithBytesNoCopy( - kCFAllocatorDefault, reinterpret_cast(source.data()), source.size(), - kCFAllocatorNull)); - if (!data) { - throw std::runtime_error("CFDataCreateWithBytesNoCopy failed"); - } + CFDataHandle data(CFDataCreateWithBytesNoCopy( + kCFAllocatorDefault, reinterpret_cast(source.data()), source.size(), + kCFAllocatorNull)); + if (!data) { + throw std::runtime_error("CFDataCreateWithBytesNoCopy failed"); + } - CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL)); - if (!imageSource) { - throw std::runtime_error("CGImageSourceCreateWithData failed"); - } + CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL)); + if (!imageSource) { + throw std::runtime_error("CGImageSourceCreateWithData failed"); + } - CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL)); - if (!image) { - throw std::runtime_error("CGImageSourceCreateImageAtIndex failed"); - } + CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL)); + if (!image) { + throw std::runtime_error("CGImageSourceCreateImageAtIndex failed"); + } - return MLNPremultipliedImageFromCGImage(*image); + return MLNPremultipliedImageFromCGImage(*image); } -} // namespace mbgl +} // namespace mbgl diff --git a/platform/darwin/core/local_glyph_rasterizer.mm b/platform/darwin/core/local_glyph_rasterizer.mm index a1e0b56dc..931bf2dfb 100644 --- a/platform/darwin/core/local_glyph_rasterizer.mm +++ b/platform/darwin/core/local_glyph_rasterizer.mm @@ -1,15 +1,15 @@ #include +#include #include #include #include -#include #include #include -#import #import +#import #import #import "CFHandle.hpp" @@ -63,114 +63,117 @@ using CTLineRefHandle = CFHandle; */ class LocalGlyphRasterizer::Impl { public: - /** - Creates a new rasterizer with the given font names as a fallback. + /** + Creates a new rasterizer with the given font names as a fallback. - The fallback font names can also be specified in the style as a font stack - or in the `MLNIdeographicFontFamilyName` key of - `NSUserDefaults.standardUserDefaults`. The font stack takes precedence, - followed by the `MLNIdeographicFontFamilyName` user default, then finally - the `fallbackFontNames_` parameter as a last resort. + The fallback font names can also be specified in the style as a font stack + or in the `MLNIdeographicFontFamilyName` key of + `NSUserDefaults.standardUserDefaults`. The font stack takes precedence, + followed by the `MLNIdeographicFontFamilyName` user default, then finally + the `fallbackFontNames_` parameter as a last resort. - @param fallbackFontNames_ A list of font names, one per line. Each font - name can be the PostScript name or display name of a specific font face - or a font family name. Set this parameter to `nullptr` to disable local - glyph rasterization globally. The system font is a good default value to - pass into this constructor. - */ - Impl(const std::optional fallbackFontNames_) - { - fallbackFontNames = [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"]; - if (fallbackFontNames_) { - fallbackFontNames = [fallbackFontNames ?: @[] arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str()) componentsSeparatedByString:@"\n"]]; - } + @param fallbackFontNames_ A list of font names, one per line. Each font + name can be the PostScript name or display name of a specific font face + or a font family name. Set this parameter to `nullptr` to disable local + glyph rasterization globally. The system font is a good default value to + pass into this constructor. + */ + Impl(const std::optional fallbackFontNames_) { + fallbackFontNames = + [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"]; + if (fallbackFontNames_) { + fallbackFontNames = [fallbackFontNames ?: @[] + arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str()) + componentsSeparatedByString:@"\n"]]; + } + } + + /** + Returns whether local glyph rasterization is enabled globally. + + The developer can disable local glyph rasterization by specifying no + fallback font names. + */ + bool isEnabled() { return fallbackFontNames; } + + /** + Creates a font descriptor representing the given font stack and any global + fallback fonts. + + @param fontStack The font stack that takes precedence. + @returns A font descriptor representing the first font in the font stack + with a cascade list representing the rest of the fonts in the font stack + and any fallback fonts. The font descriptor is not cached. + + @post The caller is responsible for releasing the font descriptor. + */ + CTFontDescriptorRef createFontDescriptor(const FontStack &fontStack) { + NSMutableArray *fontNames = + [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count]; + for (auto &fontName : fontStack) { + // Per the MapLibre Style Spec, the text-font property comes + // with these last resort fonts by default, but they shouldn’t take + // precedence over any application or system fallback font that may + // be more appropriate to the current device. + if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && + fontName != util::LAST_RESORT_PAN_UNICODE_FONT) { + [fontNames addObject:@(fontName.c_str())]; + } + } + [fontNames addObjectsFromArray:fallbackFontNames]; + + if (!fontNames.count) { + NSDictionary *fontAttributes = @{ + (NSString *)kCTFontSizeAttribute : @(util::ONE_EM), + }; + return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes); } - /** - Returns whether local glyph rasterization is enabled globally. + // Apply the first font name to the returned font descriptor; apply the + // rest of the font names to the cascade list. + CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject; + CFMutableArrayRefHandle fallbackDescriptors( + CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks)); + for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) { + NSDictionary *fontAttributes = @{ + (NSString *)kCTFontSizeAttribute : @(util::ONE_EM), + // This attribute is technically supposed to be a font’s + // PostScript name, but Core Text will fall back to matching + // font display names and font family names. + (NSString *)kCTFontNameAttribute : name, + }; - The developer can disable local glyph rasterization by specifying no - fallback font names. - */ - bool isEnabled() { return fallbackFontNames; } - - /** - Creates a font descriptor representing the given font stack and any global - fallback fonts. - - @param fontStack The font stack that takes precedence. - @returns A font descriptor representing the first font in the font stack - with a cascade list representing the rest of the fonts in the font stack - and any fallback fonts. The font descriptor is not cached. - - @post The caller is responsible for releasing the font descriptor. - */ - CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack) { - NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count]; - for (auto& fontName : fontStack) { - // Per the MapLibre Style Spec, the text-font property comes - // with these last resort fonts by default, but they shouldn’t take - // precedence over any application or system fallback font that may - // be more appropriate to the current device. - if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) { - [fontNames addObject:@(fontName.c_str())]; - } - } - [fontNames addObjectsFromArray:fallbackFontNames]; - - if (!fontNames.count) { - NSDictionary *fontAttributes = @{ - (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), - }; - return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes); - } - - // Apply the first font name to the returned font descriptor; apply the - // rest of the font names to the cascade list. - CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject; - CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks)); - for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) { - NSDictionary *fontAttributes = @{ - (NSString *)kCTFontSizeAttribute: @(util::ONE_EM), - // This attribute is technically supposed to be a font’s - // PostScript name, but Core Text will fall back to matching - // font display names and font family names. - (NSString *)kCTFontNameAttribute: name, - }; - - CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); - CFArrayAppendValue(*fallbackDescriptors, *descriptor); - } - - CFStringRef keys[] = { - kCTFontSizeAttribute, - kCTFontNameAttribute, - kCTFontCascadeListAttribute, - }; - CFTypeRef values[] = { - (__bridge CFNumberRef)@(util::ONE_EM), - mainFontName, - *fallbackDescriptors, - }; - - CFDictionaryRefHandle attributes( - CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, - (const void**)&values, sizeof(keys) / sizeof(keys[0]), - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)); - return CTFontDescriptorCreateWithAttributes(*attributes); + CTFontDescriptorRefHandle descriptor( + CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); + CFArrayAppendValue(*fallbackDescriptors, *descriptor); } + CFStringRef keys[] = { + kCTFontSizeAttribute, + kCTFontNameAttribute, + kCTFontCascadeListAttribute, + }; + CFTypeRef values[] = { + (__bridge CFNumberRef) @(util::ONE_EM), + mainFontName, + *fallbackDescriptors, + }; + + CFDictionaryRefHandle attributes( + CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values, + sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + return CTFontDescriptorCreateWithAttributes(*attributes); + } + private: - NSArray *fallbackFontNames; + NSArray *fallbackFontNames; }; -LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional& fontFamily) - : impl(std::make_unique(fontFamily)) -{} +LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional &fontFamily) + : impl(std::make_unique(fontFamily)) {} -LocalGlyphRasterizer::~LocalGlyphRasterizer() -{} +LocalGlyphRasterizer::~LocalGlyphRasterizer() {} /** Returns whether the rasterizer can rasterize a glyph for the given codepoint. @@ -178,11 +181,11 @@ LocalGlyphRasterizer::~LocalGlyphRasterizer() @param glyphID A font-agnostic Unicode codepoint, not a glyph index. @returns Whether a glyph for the codepoint can be rasterized. */ -bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) { +bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack &, GlyphID glyphID) { #if MBGL_DARWIN_NO_REMOTE_FONTS - return impl->isEnabled(); + return impl->isEnabled(); #else - return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled(); + return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled(); #endif } @@ -197,91 +200,90 @@ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) @param isBold use kCTFontBoldTrait if it is true. @returns An image containing the glyph. */ -PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics, BOOL isBold) { - CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast(&glyphID), 1)); - if (!string) { - throw std::runtime_error("Unable to create string from codepoint"); - } +PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics &metrics, + BOOL isBold) { + CFStringRefHandle string( + CFStringCreateWithCharacters(NULL, reinterpret_cast(&glyphID), 1)); + if (!string) { + throw std::runtime_error("Unable to create string from codepoint"); + } - // Create a bold variant of the font - CTFontRefHandle boldFont(CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait)); - if (!boldFont) { - CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font)); - NSString* familyName = (__bridge NSString *)(*familyNameHandle); - std::string stdFamilyName(familyName.UTF8String); - Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName); - } + // Create a bold variant of the font + CTFontRefHandle boldFont( + CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait)); + if (!boldFont) { + CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font)); + NSString *familyName = (__bridge NSString *)(*familyNameHandle); + std::string stdFamilyName(familyName.UTF8String); + Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName); + } - CTFontRef drawFont = isBold && boldFont ? *boldFont : font; + CTFontRef drawFont = isBold && boldFont ? *boldFont : font; - CFStringRef keys[] = { kCTFontAttributeName }; - CFTypeRef values[] = { drawFont }; + CFStringRef keys[] = {kCTFontAttributeName}; + CFTypeRef values[] = {drawFont}; - CFDictionaryRefHandle attributes( - CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, - (const void**)&values, sizeof(keys) / sizeof(keys[0]), - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)); - if (!attributes) { - throw std::runtime_error("Unable to create attributed string attributes dictionary"); - } + CFDictionaryRefHandle attributes( + CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values, + sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + if (!attributes) { + throw std::runtime_error("Unable to create attributed string attributes dictionary"); + } - CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes)); - if (!attrString) { - throw std::runtime_error("Unable to create attributed string"); - } - CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString)); - if (!line) { - throw std::runtime_error("Unable to create line from attributed string"); - } + CFAttributedStringRefHandle attrString( + CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes)); + if (!attrString) { + throw std::runtime_error("Unable to create attributed string"); + } + CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString)); + if (!line) { + throw std::runtime_error("Unable to create line from attributed string"); + } - Size size(35, 35); - metrics.width = size.width; - metrics.height = size.height; + Size size(35, 35); + metrics.width = size.width; + metrics.height = size.height; - PremultipliedImage rgbaBitmap(size); + PremultipliedImage rgbaBitmap(size); - CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); - if (!colorSpace) { - throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); - } + CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); + if (!colorSpace) { + throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); + } - constexpr const size_t bitsPerComponent = 8; - constexpr const size_t bytesPerPixel = 4; - const size_t bytesPerRow = bytesPerPixel * size.width; - const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | static_cast(kCGImageAlphaPremultipliedLast); + constexpr const size_t bitsPerComponent = 8; + constexpr const size_t bytesPerPixel = 4; + const size_t bytesPerRow = bytesPerPixel * size.width; + const auto bitmapInfo = static_cast(kCGBitmapByteOrderDefault) | + static_cast(kCGImageAlphaPremultipliedLast); - CGContextHandle context(CGBitmapContextCreate( - rgbaBitmap.data.get(), - size.width, - size.height, - bitsPerComponent, - bytesPerRow, - *colorSpace, - bitmapInfo)); - if (!context) { - throw std::runtime_error("CGBitmapContextCreate failed"); - } + CGContextHandle context(CGBitmapContextCreate(rgbaBitmap.data.get(), size.width, size.height, + bitsPerComponent, bytesPerRow, *colorSpace, + bitmapInfo)); + if (!context) { + throw std::runtime_error("CGBitmapContextCreate failed"); + } - CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line); - CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0); - CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun)); - std::vector advances(wholeRunRange.length); - CTRunGetAdvances(glyphRun, wholeRunRange, advances.data()); - metrics.advance = std::round(advances[0].width); + CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line); + CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0); + CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun)); + std::vector advances(wholeRunRange.length); + CTRunGetAdvances(glyphRun, wholeRunRange, advances.data()); + metrics.advance = std::round(advances[0].width); - // Mimic glyph PBF metrics. - metrics.left = Glyph::borderSize; - metrics.top = -Glyph::borderSize; + // Mimic glyph PBF metrics. + metrics.left = Glyph::borderSize; + metrics.top = -Glyph::borderSize; - // Move the text upward to avoid clipping off descenders. - CGFloat descent; - CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL); - CGContextSetTextPosition(*context, 0.0, descent); + // Move the text upward to avoid clipping off descenders. + CGFloat descent; + CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL); + CGContextSetTextPosition(*context, 0.0, descent); - CTLineDraw(*line, *context); + CTLineDraw(*line, *context); - return rgbaBitmap; + return rgbaBitmap; } /** @@ -294,34 +296,36 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics @returns A glyph representation of the given codepoint with its bitmap data and metrics set. */ -Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { - Glyph manufacturedGlyph; - CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack)); - CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); - if (!font) { - return manufacturedGlyph; - } - - manufacturedGlyph.id = glyphID; - BOOL isBold = NO; - // Only check the first font name to detect if the user prefers using bold - if (!fontStack.empty()) { - std::string lowercaseFont = platform::lowercase(fontStack.front()); - if (lowercaseFont.find("bold") != std::string::npos && lowercaseFont.find("semibold") == std::string::npos) { - isBold = YES; - } - } - - PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold); - - Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height); - // Copy alpha values from RGBA bitmap into the AlphaImage output - manufacturedGlyph.bitmap = AlphaImage(size); - for (uint32_t i = 0; i < size.width * size.height; i++) { - manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3]; - } - +Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack &fontStack, GlyphID glyphID) { + Glyph manufacturedGlyph; + CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack)); + CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); + if (!font) { return manufacturedGlyph; + } + + manufacturedGlyph.id = glyphID; + BOOL isBold = NO; + // Only check the first font name to detect if the user prefers using bold + if (!fontStack.empty()) { + std::string lowercaseFont = platform::lowercase(fontStack.front()); + if (lowercaseFont.find("bold") != std::string::npos && + lowercaseFont.find("semibold") == std::string::npos) { + isBold = YES; + } + } + + PremultipliedImage rgbaBitmap = + drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold); + + Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height); + // Copy alpha values from RGBA bitmap into the AlphaImage output + manufacturedGlyph.bitmap = AlphaImage(size); + for (uint32_t i = 0; i < size.width * size.height; i++) { + manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3]; + } + + return manufacturedGlyph; } -} // namespace mbgl +} // namespace mbgl diff --git a/platform/darwin/core/logging_nslog.mm b/platform/darwin/core/logging_nslog.mm index 3f70f37b7..ccdbfeaf5 100644 --- a/platform/darwin/core/logging_nslog.mm +++ b/platform/darwin/core/logging_nslog.mm @@ -1,14 +1,15 @@ -#include #include +#include #import namespace mbgl { - void Log::platformRecord(EventSeverity severity, const std::string &msg) { - NSString *message = - [[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding]; - NSLog(@"[%s] %@", Enum::toString(severity), message); - } - +void Log::platformRecord(EventSeverity severity, const std::string &msg) { + NSString *message = [[NSString alloc] initWithBytes:msg.data() + length:msg.size() + encoding:NSUTF8StringEncoding]; + NSLog(@"[%s] %@", Enum::toString(severity), message); } + +} // namespace mbgl diff --git a/platform/darwin/core/nsthread.mm b/platform/darwin/core/nsthread.mm index bf9c09f61..b93c42d6b 100644 --- a/platform/darwin/core/nsthread.mm +++ b/platform/darwin/core/nsthread.mm @@ -1,8 +1,8 @@ #import +#include #include #include -#include #include @@ -10,43 +10,41 @@ namespace mbgl { namespace platform { std::string getCurrentThreadName() { - char name[32] = "unknown"; - pthread_getname_np(pthread_self(), name, sizeof(name)); + char name[32] = "unknown"; + pthread_getname_np(pthread_self(), name, sizeof(name)); - return name; + return name; } void setCurrentThreadName(const std::string& name) { - std::string qualifiedName = "org.maplibre.mbgl." + name; - pthread_setname_np(qualifiedName.c_str()); + std::string qualifiedName = "org.maplibre.mbgl." + name; + pthread_setname_np(qualifiedName.c_str()); } void makeThreadLowPriority() { - [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; + [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; } void setCurrentThreadPriority(double priority) { - if (priority > 1.0 || priority < 0.0) { - Log::Warning(Event::General, "Invalid thread priority was provided"); - return; - } + if (priority > 1.0 || priority < 0.0) { + Log::Warning(Event::General, "Invalid thread priority was provided"); + return; + } - if (priority < 0.25) { - [NSThread currentThread].qualityOfService = NSQualityOfServiceBackground; - } else if (priority < 0.5) { - [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; - } else if (priority < 0.75) { - [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated; - } else { - [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive; - } + if (priority < 0.25) { + [NSThread currentThread].qualityOfService = NSQualityOfServiceBackground; + } else if (priority < 0.5) { + [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; + } else if (priority < 0.75) { + [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated; + } else { + [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive; + } } -void attachThread() { -} +void attachThread() {} -void detachThread() { -} +void detachThread() {} -} -} +} // namespace platform +} // namespace mbgl diff --git a/platform/darwin/core/number_format.mm b/platform/darwin/core/number_format.mm index 5d96fe368..5f5ec333d 100644 --- a/platform/darwin/core/number_format.mm +++ b/platform/darwin/core/number_format.mm @@ -7,24 +7,24 @@ namespace platform { std::string formatNumber(double number, const std::string& localeId, const std::string& currency, uint8_t minFractionDigits, uint8_t maxFractionDigits) { + // Create a new formatter each time to avoid state pollution between calls + NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; - // Create a new formatter each time to avoid state pollution between calls - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; + numberFormatter.locale = + !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil; + numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil; - numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil; - numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil; - - if (currency.empty()) { - numberFormatter.minimumFractionDigits = minFractionDigits; - numberFormatter.maximumFractionDigits = maxFractionDigits; - numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; - } else { - numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; - } - NSString *formatted = [numberFormatter stringFromNumber:@(number)]; - std::string result = std::string([formatted UTF8String]); - return result; + if (currency.empty()) { + numberFormatter.minimumFractionDigits = minFractionDigits; + numberFormatter.maximumFractionDigits = maxFractionDigits; + numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; + } else { + numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; + } + NSString* formatted = [numberFormatter stringFromNumber:@(number)]; + std::string result = std::string([formatted UTF8String]); + return result; } -} // namespace platform -} // namespace mbgl +} // namespace platform +} // namespace mbgl diff --git a/platform/darwin/core/string_nsstring.mm b/platform/darwin/core/string_nsstring.mm index 74fedcb7f..3caff87d5 100644 --- a/platform/darwin/core/string_nsstring.mm +++ b/platform/darwin/core/string_nsstring.mm @@ -6,26 +6,26 @@ namespace mbgl { namespace platform { std::string uppercase(const std::string &string) { - NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast(string.data()) - length:string.size() - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - NSString *uppercase = [original uppercaseString]; - std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding], - [uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; - return result; + NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast(string.data()) + length:string.size() + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + NSString *uppercase = [original uppercaseString]; + std::string result{[uppercase cStringUsingEncoding:NSUTF8StringEncoding], + [uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; + return result; } std::string lowercase(const std::string &string) { - NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast(string.data()) - length:string.size() - encoding:NSUTF8StringEncoding - freeWhenDone:NO]; - NSString *lowercase = [original lowercaseString]; - std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding], - [lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; - return result; + NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast(string.data()) + length:string.size() + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + NSString *lowercase = [original lowercaseString]; + std::string result{[lowercase cStringUsingEncoding:NSUTF8StringEncoding], + [lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; + return result; } -} // namespace platform -} // namespace mbgl +} // namespace platform +} // namespace mbgl diff --git a/platform/darwin/src/MLNActionJournalOptions.mm b/platform/darwin/src/MLNActionJournalOptions.mm index f937c8a7e..605a9de79 100644 --- a/platform/darwin/src/MLNActionJournalOptions.mm +++ b/platform/darwin/src/MLNActionJournalOptions.mm @@ -2,61 +2,62 @@ #import @implementation MLNActionJournalOptions { - std::unique_ptr _actionJournalOptionsInternal; + std::unique_ptr _actionJournalOptionsInternal; } - (instancetype)init { - if (self = [super init]) { - _actionJournalOptionsInternal = std::make_unique(); + if (self = [super init]) { + _actionJournalOptionsInternal = std::make_unique(); - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - _actionJournalOptionsInternal->withPath([paths.firstObject stringByAppendingPathComponent:@""].UTF8String); - } - return self; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + _actionJournalOptionsInternal->withPath( + [paths.firstObject stringByAppendingPathComponent:@""].UTF8String); + } + return self; } - (mbgl::util::ActionJournalOptions)getCoreOptions { - return *_actionJournalOptionsInternal; + return *_actionJournalOptionsInternal; } - (void)setEnabled:(BOOL)value { - _actionJournalOptionsInternal->enable(value); + _actionJournalOptionsInternal->enable(value); } - (BOOL)enabled { - return _actionJournalOptionsInternal->enabled(); + return _actionJournalOptionsInternal->enabled(); } -- (void)setPath:(NSString*)value { - _actionJournalOptionsInternal->withPath(value.UTF8String); +- (void)setPath:(NSString *)value { + _actionJournalOptionsInternal->withPath(value.UTF8String); } -- (NSString*)path { - return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()]; +- (NSString *)path { + return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()]; } - (void)setLogFileSize:(NSInteger)value { - _actionJournalOptionsInternal->withLogFileSize(static_cast(value)); + _actionJournalOptionsInternal->withLogFileSize(static_cast(value)); } - (NSInteger)logFileSize { - return _actionJournalOptionsInternal->logFileSize(); + return _actionJournalOptionsInternal->logFileSize(); } - (void)setLogFileCount:(NSInteger)value { - _actionJournalOptionsInternal->withLogFileCount(static_cast(value)); + _actionJournalOptionsInternal->withLogFileCount(static_cast(value)); } - (NSInteger)logFileCount { - return _actionJournalOptionsInternal->logFileCount(); + return _actionJournalOptionsInternal->logFileCount(); } - (void)setRenderingStatsReportInterval:(NSInteger)value { - _actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast(value)); + _actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast(value)); } - (NSInteger)renderingStatsReportInterval { - return _actionJournalOptionsInternal->renderingStatsReportInterval(); + return _actionJournalOptionsInternal->renderingStatsReportInterval(); } @end diff --git a/platform/darwin/src/MLNAttributionInfo.mm b/platform/darwin/src/MLNAttributionInfo.mm index 0de8e407a..f7ed6c030 100644 --- a/platform/darwin/src/MLNAttributionInfo.mm +++ b/platform/darwin/src/MLNAttributionInfo.mm @@ -1,186 +1,199 @@ #import "MLNAttributionInfo_Private.h" #if TARGET_OS_IPHONE - #import +#import #else - #import +#import #endif -#import "MLNSettings.h" +#import "MLNLoggingConfiguration_Private.h" #import "MLNMapCamera.h" +#import "MLNSettings.h" #import "NSArray+MLNAdditions.h" #import "NSBundle+MLNAdditions.h" #import "NSString+MLNAdditions.h" -#import "MLNLoggingConfiguration_Private.h" #include @implementation MLNAttributionInfo -+ (NSArray *)attributionInfosFromHTMLString:(nullable NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { - if (!htmlString) { - return @[]; - } ++ (NSArray *)attributionInfosFromHTMLString:(nullable NSString *)htmlString + fontSize:(CGFloat)fontSize + linkColor:(nullable MLNColor *)linkColor { + if (!htmlString) { + return @[]; + } - NSDictionary *options = @{ - NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, - NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding), - }; - // Apply a bogus, easily detectable style rule to any feedback link, since - // NSAttributedString doesn’t preserve the class attribute. - NSMutableString *css = [NSMutableString stringWithString: - @"html { font-family: -apple-system, -apple-system-font, sans-serif; }" - @".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"]; - if (fontSize) { - NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize]; + NSDictionary *options = @{ + NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding), + }; + // Apply a bogus, easily detectable style rule to any feedback link, since + // NSAttributedString doesn’t preserve the class attribute. + NSMutableString *css = [NSMutableString + stringWithString:@"html { font-family: -apple-system, -apple-system-font, sans-serif; }" + @".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"]; + if (fontSize) { + NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize]; #if !TARGET_OS_IPHONE - if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) { - sizeRule = @"font: -webkit-mini-control"; - } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) { - sizeRule = @"font: -webkit-small-control"; - } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) { - sizeRule = @"font: -webkit-control"; - } -#endif - [css appendFormat:@"html { %@ }", sizeRule]; + if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) { + sizeRule = @"font: -webkit-mini-control"; + } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) { + sizeRule = @"font: -webkit-small-control"; + } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) { + sizeRule = @"font: -webkit-control"; } - if (linkColor) { - CGFloat red; - CGFloat green; - CGFloat blue; - CGFloat alpha; +#endif + [css appendFormat:@"html { %@ }", sizeRule]; + } + if (linkColor) { + CGFloat red; + CGFloat green; + CGFloat blue; + CGFloat alpha; #if !TARGET_OS_IPHONE - // CSS uses the sRGB color space. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; - } else { - linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; - } -#endif - [linkColor getRed:&red green:&green blue:&blue alpha:&alpha]; - [css appendFormat: - @"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", - red * 100, green * 100, blue * 100, alpha]; + // CSS uses the sRGB color space. + if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { + linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } else { + linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; } - NSString *styledHTML = [NSString stringWithFormat:@"%@", css, htmlString]; - NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding]; +#endif + [linkColor getRed:&red green:&green blue:&blue alpha:&alpha]; + [css appendFormat:@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", red * 100, green * 100, + blue * 100, alpha]; + } + NSString *styledHTML = + [NSString stringWithFormat:@"%@", css, htmlString]; + NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding]; #if TARGET_OS_IPHONE - __block NSMutableAttributedString *attributedString; - dispatch_block_t initialization = ^{ - // This initializer should be called from a global or main queue. https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata - attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData - options:options - documentAttributes:nil - error:NULL]; - }; + __block NSMutableAttributedString *attributedString; + dispatch_block_t initialization = ^{ + // This initializer should be called from a global or main queue. + // https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata + attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData + options:options + documentAttributes:nil + error:NULL]; + }; - if (![[NSThread currentThread] isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), initialization); - } else { - initialization(); - } + if (![[NSThread currentThread] isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), initialization); + } else { + initialization(); + } #else - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData - options:options - documentAttributes:nil]; + NSMutableAttributedString *attributedString = + [[NSMutableAttributedString alloc] initWithHTML:htmlData + options:options + documentAttributes:nil]; #endif - NSMutableArray *infos = [NSMutableArray array]; - [attributedString enumerateAttribute:NSLinkAttributeName - inRange:attributedString.mgl_wholeRange - options:0 - usingBlock: - ^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - MLNCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL."); + NSMutableArray *infos = [NSMutableArray array]; + [attributedString + enumerateAttribute:NSLinkAttributeName + inRange:attributedString.mgl_wholeRange + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + MLNCAssert(!value || [value isKindOfClass:[NSURL class]], + @"If present, URL attribute must be an NSURL."); - // Detect feedback links by the bogus style rule applied above. - NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName - atIndex:range.location - effectiveRange:NULL]; - BOOL isFeedbackLink = NO; - if ([strokeWidth floatValue] > 100) { - isFeedbackLink = YES; - [attributedString removeAttribute:NSStrokeWidthAttributeName range:range]; - } + // Detect feedback links by the bogus style rule applied above. + NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName + atIndex:range.location + effectiveRange:NULL]; + BOOL isFeedbackLink = NO; + if ([strokeWidth floatValue] > 100) { + isFeedbackLink = YES; + [attributedString removeAttribute:NSStrokeWidthAttributeName range:range]; + } - // Clean up strings by stripping punctuation and whitespace (often present for the web). - NSMutableCharacterSet *charset = [NSMutableCharacterSet whitespaceAndNewlineCharacterSet]; - [charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; - NSAttributedString *title = [[attributedString attributedSubstringFromRange:range] - mgl_attributedStringByTrimmingCharactersInSet:charset]; + // Clean up strings by stripping punctuation and whitespace (often present for the + // web). + NSMutableCharacterSet *charset = + [NSMutableCharacterSet whitespaceAndNewlineCharacterSet]; + [charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; + NSAttributedString *title = [[attributedString attributedSubstringFromRange:range] + mgl_attributedStringByTrimmingCharactersInSet:charset]; - // Omit strings that are empty after cleaning. - if (!title.length) { - return; - } + // Omit strings that are empty after cleaning. + if (!title.length) { + return; + } - // Remove the link, because it forces the text to be blue on macOS 10.12 - // and above. - NSMutableAttributedString *unlinkedTitle = [title mutableCopy]; - [unlinkedTitle removeAttribute:NSLinkAttributeName range:unlinkedTitle.mgl_wholeRange]; + // Remove the link, because it forces the text to be blue on macOS 10.12 + // and above. + NSMutableAttributedString *unlinkedTitle = [title mutableCopy]; + [unlinkedTitle removeAttribute:NSLinkAttributeName + range:unlinkedTitle.mgl_wholeRange]; - MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle URL:value]; - info.feedbackLink = isFeedbackLink; - [infos addObject:info]; - }]; - return infos; + MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle + URL:value]; + info.feedbackLink = isFeedbackLink; + [infos addObject:info]; + }]; + return infos; } -+ (NSAttributedString *)attributedStringForAttributionInfos:(NSArray *)attributionInfos { - NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count]; - for (MLNAttributionInfo *info in attributionInfos) { - NSMutableAttributedString *title = info.title.mutableCopy; - if (info.URL) { - [title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange]; - } - [titles addObject:title]; ++ (NSAttributedString *)attributedStringForAttributionInfos: + (NSArray *)attributionInfos { + NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count]; + for (MLNAttributionInfo *info in attributionInfos) { + NSMutableAttributedString *title = info.title.mutableCopy; + if (info.URL) { + [title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange]; } - return [titles mgl_attributedComponentsJoinedByString:@" "]; + [titles addObject:title]; + } + return [titles mgl_attributedComponentsJoinedByString:@" "]; } - (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL { - if (self = [super init]) { - _title = title; - _URL = URL; - } - return self; + if (self = [super init]) { + _title = title; + _URL = URL; + } + return self; } -- (id)copyWithZone:(nullable NSZone *)zone -{ - MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title - URL:_URL]; - info.feedbackLink = _feedbackLink; +- (id)copyWithZone:(nullable NSZone *)zone { + MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title URL:_URL]; + info.feedbackLink = _feedbackLink; - return info; + return info; } -- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style -{ - NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); - NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", @"OpenStreetMap short name attribution"); +- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style { + NSString *openStreetMap = + NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", + @"OpenStreetMap full name attribution"); + NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", + @"OpenStreetMap short name attribution"); - NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title]; - [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; + NSMutableAttributedString *title = + [[NSMutableAttributedString alloc] initWithAttributedString:self.title]; + [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; - BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort); + BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort); - if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { - [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [title.mutableString length])]; - } + if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { + [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" + withString:isAbbreviated ? OSM : openStreetMap + options:NSCaseInsensitiveSearch + range:NSMakeRange(0, [title.mutableString length])]; + } - return title; + return title; } - (BOOL)isEqual:(id)object { - return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && [[object URL] isEqual:self.URL]; + return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && + [[object URL] isEqual:self.URL]; } - (NSUInteger)hash { - return self.title.hash + self.URL.hash; + return self.title.hash + self.URL.hash; } /** @@ -192,15 +205,15 @@ if there is no overlap. */ - (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo { - NSString *title = self.title.string; - NSString *otherTitle = otherInfo.title.string; - if ([title containsString:otherTitle]) { - return NSOrderedDescending; - } - if ([otherTitle containsString:title]) { - return NSOrderedAscending; - } - return NSOrderedSame; + NSString *title = self.title.string; + NSString *otherTitle = otherInfo.title.string; + if ([title containsString:otherTitle]) { + return NSOrderedDescending; + } + if ([otherTitle containsString:title]) { + return NSOrderedAscending; + } + return NSOrderedSame; } @end @@ -208,43 +221,44 @@ @implementation NSMutableArray (MLNAttributionInfoAdditions) - (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info { - __block BOOL didInsertInfo = NO; - __block BOOL shouldAddInfo = YES; - [self enumerateObjectsUsingBlock:^(MLNAttributionInfo * _Nonnull existingInfo, NSUInteger idx, BOOL * _Nonnull stop) { - switch ([info subsetCompare:existingInfo]) { - case NSOrderedDescending: - // The existing info object is a subset of the one we’re adding. - // Replace the existing object the first time we find a subset; - // remove the existing object every time after that. - if (didInsertInfo) { - [self removeObjectAtIndex:idx]; - } else { - [self replaceObjectAtIndex:idx withObject:info]; - didInsertInfo = YES; - } - break; - - case NSOrderedAscending: - // The info object we’re adding is a subset of the existing one. - // Don’t add the object and stop looking. - shouldAddInfo = NO; - *stop = YES; - break; - - default: - break; + __block BOOL didInsertInfo = NO; + __block BOOL shouldAddInfo = YES; + [self enumerateObjectsUsingBlock:^(MLNAttributionInfo *_Nonnull existingInfo, NSUInteger idx, + BOOL *_Nonnull stop) { + switch ([info subsetCompare:existingInfo]) { + case NSOrderedDescending: + // The existing info object is a subset of the one we’re adding. + // Replace the existing object the first time we find a subset; + // remove the existing object every time after that. + if (didInsertInfo) { + [self removeObjectAtIndex:idx]; + } else { + [self replaceObjectAtIndex:idx withObject:info]; + didInsertInfo = YES; } - }]; - if (shouldAddInfo && !didInsertInfo) { - // No overlapping infos were found, so append the info object. - [self addObject:info]; + break; + + case NSOrderedAscending: + // The info object we’re adding is a subset of the existing one. + // Don’t add the object and stop looking. + shouldAddInfo = NO; + *stop = YES; + break; + + default: + break; } + }]; + if (shouldAddInfo && !didInsertInfo) { + // No overlapping infos were found, so append the info object. + [self addObject:info]; + } } - (void)growArrayByAddingAttributionInfosFromArray:(NSArray *)infos { - for (MLNAttributionInfo *info in infos) { - [self growArrayByAddingAttributionInfo:info]; - } + for (MLNAttributionInfo *info in infos) { + [self growArrayByAddingAttributionInfo:info]; + } } @end diff --git a/platform/darwin/src/MLNBackendResource.mm b/platform/darwin/src/MLNBackendResource.mm index d13abd590..6a7ee22fe 100644 --- a/platform/darwin/src/MLNBackendResource.mm +++ b/platform/darwin/src/MLNBackendResource.mm @@ -8,14 +8,14 @@ device:(id)device renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor commandBuffer:(id)commandBuffer { - self = [super init]; - if (self) { - _mtkView = mtkView; - _device = device; - _renderPassDescriptor = renderPassDescriptor; - _commandBuffer = commandBuffer; - } - return self; + self = [super init]; + if (self) { + _mtkView = mtkView; + _device = device; + _renderPassDescriptor = renderPassDescriptor; + _commandBuffer = commandBuffer; + } + return self; } @end @@ -27,8 +27,8 @@ @implementation MLNBackendResource - (instancetype)init { - self = [super init]; - return self; + self = [super init]; + return self; } @end diff --git a/platform/darwin/src/MLNComputedShapeSource.mm b/platform/darwin/src/MLNComputedShapeSource.mm index df148590f..1a4afc24e 100644 --- a/platform/darwin/src/MLNComputedShapeSource.mm +++ b/platform/darwin/src/MLNComputedShapeSource.mm @@ -1,10 +1,10 @@ #import "MLNComputedShapeSource_Private.h" -#import "MLNMapView_Private.h" -#import "MLNSource_Private.h" -#import "MLNShape_Private.h" #import "MLNGeometry_Private.h" +#import "MLNMapView_Private.h" #import "MLNShapeCollection.h" +#import "MLNShape_Private.h" +#import "MLNSource_Private.h" #include #include @@ -13,65 +13,68 @@ const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException"; -const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates = @"MLNShapeSourceOptionWrapsCoordinates"; -const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates = @"MLNShapeSourceOptionClipsCoordinates"; +const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates = + @"MLNShapeSourceOptionWrapsCoordinates"; +const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates = + @"MLNShapeSourceOptionClipsCoordinates"; -mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NSDictionary *options) { - mbgl::style::CustomGeometrySource::Options sourceOptions; +mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary( + NSDictionary *options) { + mbgl::style::CustomGeometrySource::Options sourceOptions; - if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."]; - } - sourceOptions.zoomRange.min = value.integerValue; + if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."]; } + sourceOptions.zoomRange.min = value.integerValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; - } - sourceOptions.zoomRange.max = value.integerValue; + if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; } + sourceOptions.zoomRange.max = value.integerValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; - } - sourceOptions.tileOptions.buffer = value.integerValue; + if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; } + sourceOptions.tileOptions.buffer = value.integerValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; - } - sourceOptions.tileOptions.tolerance = value.doubleValue; + if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; } + sourceOptions.tileOptions.tolerance = value.doubleValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."]; - } - sourceOptions.tileOptions.wrap = value.boolValue; + if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."]; } + sourceOptions.tileOptions.wrap = value.boolValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."]; - } - sourceOptions.tileOptions.clip = value.boolValue; + if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."]; } + sourceOptions.tileOptions.clip = value.boolValue; + } - return sourceOptions; + return sourceOptions; } @interface MLNComputedShapeSource () { - std::unique_ptr _pendingSource; + std::unique_ptr _pendingSource; } @property (nonatomic, readwrite) NSDictionary *options; @@ -90,164 +93,181 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi @property (nonatomic, weak, nullable) id dataSource; @property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource; -- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileId; +- (instancetype)initForSource:(MLNComputedShapeSource *)source + tile:(const mbgl::CanonicalTileID &)tileId; @end @implementation MLNComputedShapeSourceFetchOperation -- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID { - self = [super init]; - _z = tileID.z; - _x = tileID.x; - _y = tileID.y; - _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; - _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; - _dataSource = source.dataSource; - mbgl::style::CustomGeometrySource *rawSource = static_cast(source.rawSource); - _rawSource = rawSource; - return self; +- (instancetype)initForSource:(MLNComputedShapeSource *)source + tile:(const mbgl::CanonicalTileID &)tileID { + self = [super init]; + _z = tileID.z; + _x = tileID.x; + _y = tileID.y; + _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; + _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; + _dataSource = source.dataSource; + mbgl::style::CustomGeometrySource *rawSource = + static_cast(source.rawSource); + _rawSource = rawSource; + return self; } - (void)main { - if ([self isCancelled]) { - return; - } + if ([self isCancelled]) { + return; + } - NSArray *> *data; - if(!self.dataSource) { - data = nil; - } else if(self.dataSourceImplementsFeaturesForTile) { - data = [self.dataSource featuresInTileAtX:self.x - y:self.y - zoomLevel:self.z]; - } else { - mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); - mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); - data = [self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds) - zoomLevel:self.z]; - } + NSArray *> *data; + if (!self.dataSource) { + data = nil; + } else if (self.dataSourceImplementsFeaturesForTile) { + data = [self.dataSource featuresInTileAtX:self.x y:self.y zoomLevel:self.z]; + } else { + mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); + mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); + data = + [self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds) + zoomLevel:self.z]; + } - if(![self isCancelled]) { - mbgl::FeatureCollection featureCollection; - featureCollection.reserve(data.count); - for (MLNShape * feature in data) { - if ([feature isMemberOfClass:[MLNShapeCollection class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." - @"Use MLNShapeCollectionFeature to retain attributes instead." - @"This will be logged only once."); - }); - } - mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get(); - featureCollection.push_back(geoJsonObject); - } - const auto geojson = mbgl::GeoJSON{featureCollection}; - - // Note: potential race condition with `cancel` - if(![self isCancelled]) { - if (auto *rawSource = self.rawSource) { - rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson); - } - } + if (![self isCancelled]) { + mbgl::FeatureCollection featureCollection; + featureCollection.reserve(data.count); + for (MLNShape *feature in data) { + if ([feature isMemberOfClass:[MLNShapeCollection class]]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." + @"Use MLNShapeCollectionFeature to retain attributes instead." + @"This will be logged only once."); + }); + } + mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get(); + featureCollection.push_back(geoJsonObject); } + const auto geojson = mbgl::GeoJSON{featureCollection}; + + // Note: potential race condition with `cancel` + if (![self isCancelled]) { + if (auto *rawSource = self.rawSource) { + rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson); + } + } + } } - (void)cancel { - [super cancel]; - self.rawSource = NULL; + [super cancel]; + self.rawSource = NULL; } @end @implementation MLNComputedShapeSource -- (instancetype)initWithIdentifier:(NSString *)identifier options:(NSDictionary *)options { - NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init]; - requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier]; - requestQueue.qualityOfService = NSQualityOfServiceUtility; - requestQueue.maxConcurrentOperationCount = 4; +- (instancetype)initWithIdentifier:(NSString *)identifier + options:(NSDictionary *)options { + NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init]; + requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier]; + requestQueue.qualityOfService = NSQualityOfServiceUtility; + requestQueue.maxConcurrentOperationCount = 4; - auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options); - sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { - NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID]; - [requestQueue addOperation:operation]; - }; + auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options); + sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID &tileID) { + NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self + tile:tileID]; + [requestQueue addOperation:operation]; + }; - sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { - for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) { - if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) { - [operation cancel]; - } - } - }; - - auto source = std::make_unique(identifier.UTF8String, sourceOptions); - - if (self = [super initWithPendingSource:std::move(source)]) { - _requestQueue = requestQueue; + sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID &tileID) { + for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) { + if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) { + [operation cancel]; + } } - return self; + }; + + auto source = + std::make_unique(identifier.UTF8String, sourceOptions); + + if (self = [super initWithPendingSource:std::move(source)]) { + _requestQueue = requestQueue; + } + return self; } -- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id)dataSource options:(NSDictionary *)options { - if (self = [self initWithIdentifier:identifier options:options]) { - [self setDataSource:dataSource]; - } - return self; +- (instancetype)initWithIdentifier:(NSString *)identifier + dataSource:(id)dataSource + options:(NSDictionary *)options { + if (self = [self initWithIdentifier:identifier options:options]) { + [self setDataSource:dataSource]; + } + return self; } - (void)dealloc { - [self.requestQueue cancelAllOperations]; + [self.requestQueue cancelAllOperations]; } -- (void)setFeatures:(NSArray *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel { - MLNAssertStyleSourceIsValid(); - mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); - mbgl::FeatureCollection featureCollection; - featureCollection.reserve(features.count); - for (MLNShape * feature in features) { - mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get(); - featureCollection.push_back(geoJsonObject); - if ([feature isMemberOfClass:[MLNShapeCollection class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." - @"Use MLNShapeCollectionFeature to retain attributes instead." - @"This will be logged only once."); - }); - } +- (void)setFeatures:(NSArray *> *)features + inTileAtX:(NSUInteger)x + y:(NSUInteger)y + zoomLevel:(NSUInteger)zoomLevel { + MLNAssertStyleSourceIsValid(); + mbgl::CanonicalTileID tileID = + mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); + mbgl::FeatureCollection featureCollection; + featureCollection.reserve(features.count); + for (MLNShape *feature in features) { + mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get(); + featureCollection.push_back(geoJsonObject); + if ([feature isMemberOfClass:[MLNShapeCollection class]]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." + @"Use MLNShapeCollectionFeature to retain attributes instead." + @"This will be logged only once."); + }); } - const auto geojson = mbgl::GeoJSON{featureCollection}; - static_cast(self.rawSource)->setTileData(tileID, geojson); + } + const auto geojson = mbgl::GeoJSON{featureCollection}; + static_cast(self.rawSource)->setTileData(tileID, geojson); } - (void)setDataSource:(id)dataSource { - [self.requestQueue cancelAllOperations]; - // Check which method the datasource implements, to avoid having to check for each tile - self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)]; - self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)]; + [self.requestQueue cancelAllOperations]; + // Check which method the datasource implements, to avoid having to check for each tile + self.dataSourceImplementsFeaturesForTile = + [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)]; + self.dataSourceImplementsFeaturesForBounds = + [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)]; - if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) { - [NSException raise:MLNInvalidDatasourceException - format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"]; - } else if (self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) { - [NSException raise:MLNInvalidDatasourceException - format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"]; - } + if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) { + [NSException + raise:MLNInvalidDatasourceException + format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"]; + } else if (self.dataSourceImplementsFeaturesForBounds && + self.dataSourceImplementsFeaturesForTile) { + [NSException raise:MLNInvalidDatasourceException + format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"]; + } - _dataSource = dataSource; + _dataSource = dataSource; } -- (void) invalidateBounds:(MLNCoordinateBounds)bounds { - MLNAssertStyleSourceIsValid(); - ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds)); +- (void)invalidateBounds:(MLNCoordinateBounds)bounds { + MLNAssertStyleSourceIsValid(); + ((mbgl::style::CustomGeometrySource *)self.rawSource) + ->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds)); } -- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z { - MLNAssertStyleSourceIsValid(); - ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y)); +- (void)invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z { + MLNAssertStyleSourceIsValid(); + ((mbgl::style::CustomGeometrySource *)self.rawSource) + ->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y)); } @end diff --git a/platform/darwin/src/MLNConversion.h b/platform/darwin/src/MLNConversion.h index 5d7cf2ed0..6e8615482 100644 --- a/platform/darwin/src/MLNConversion.h +++ b/platform/darwin/src/MLNConversion.h @@ -8,14 +8,14 @@ namespace conversion { // A wrapper class for `id`, so as not to confuse ARC. class Holder { - public: +public: Holder(const id v) : value(v) {} const id value; }; template <> class ConversionTraits { - public: +public: static bool isUndefined(const Holder& holder) { const id value = holder.value; return !value || value == [NSNull null]; @@ -146,7 +146,7 @@ class ConversionTraits { } } - private: +private: static bool _isBool(const id value) { if (![value isKindOfClass:[NSNumber class]]) return false; // char: 32-bit boolean diff --git a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm index f91fd0190..b9810fc6b 100644 --- a/platform/darwin/src/MLNCustomDrawableStyleLayer.mm +++ b/platform/darwin/src/MLNCustomDrawableStyleLayer.mm @@ -2,15 +2,15 @@ #import "MLNStyleLayer.h" #import "MLNCustomDrawableStyleLayer_Private.h" -#import "MLNStyle_Private.h" -#import "MLNStyleLayer_Private.h" #import "MLNGeometry_Private.h" +#import "MLNStyleLayer_Private.h" +#import "MLNStyle_Private.h" #include #include -#include #include +#include #include @@ -20,12 +20,12 @@ @implementation MLNCustomDrawableStyleLayer -- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { - return [super initWithRawLayer:rawLayer]; +- (instancetype)initWithRawLayer:(mbgl::style::Layer*)rawLayer { + return [super initWithRawLayer:rawLayer]; } - (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer { - return [super initWithPendingLayer:std::move(pendingLayer)]; + return [super initWithPendingLayer:std::move(pendingLayer)]; } @end @@ -33,7 +33,7 @@ namespace mbgl { MLNStyleLayer* CustomDrawableStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { - return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer]; + return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer]; } } // namespace mbgl diff --git a/platform/darwin/src/MLNCustomStyleLayer.mm b/platform/darwin/src/MLNCustomStyleLayer.mm index 5e3107bb4..15cbc0e7f 100644 --- a/platform/darwin/src/MLNCustomStyleLayer.mm +++ b/platform/darwin/src/MLNCustomStyleLayer.mm @@ -1,17 +1,17 @@ #import "MLNCustomStyleLayer.h" #import "MLNCustomStyleLayer_Private.h" -#import "MLNMapView_Private.h" -#import "MLNStyle_Private.h" -#import "MLNStyleLayer_Private.h" #import "MLNGeometry_Private.h" +#import "MLNMapView_Private.h" +#import "MLNStyleLayer_Private.h" +#import "MLNStyle_Private.h" #if MLN_RENDER_BACKEND_METAL #import #endif -#include #include +#include #if MLN_RENDER_BACKEND_METAL #include @@ -28,44 +28,42 @@ class MLNCustomLayerHost; @implementation MLNCustomStyleLayer - (instancetype)initWithIdentifier:(NSString *)identifier { - auto layer = std::make_unique( - identifier.UTF8String, - std::make_unique(self) - ); - return self = [super initWithPendingLayer:std::move(layer)]; + auto layer = std::make_unique( + identifier.UTF8String, std::make_unique(self)); + return self = [super initWithPendingLayer:std::move(layer)]; } - (mbgl::style::CustomLayer *)rawLayer { - return (mbgl::style::CustomLayer *)super.rawLayer; + return (mbgl::style::CustomLayer *)super.rawLayer; } - (MLNMapView *)mapView { - if ([self.style.stylable isKindOfClass:[MLNMapView class]]) { - return (MLNMapView *)self.style.stylable; - } - return nil; + if ([self.style.stylable isKindOfClass:[MLNMapView class]]) { + return (MLNMapView *)self.style.stylable; + } + return nil; } #if TARGET_OS_IPHONE - (EAGLContext *)context { - return self.mapView.context; + return self.mapView.context; } #else - (CGLContextObj)context { - return self.mapView.context; + return self.mapView.context; } #endif - (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer { - self.style = style; - self.style.customLayers[self.identifier] = self; - [super addToStyle:style belowLayer:otherLayer]; + self.style = style; + self.style.customLayers[self.identifier] = self; + [super addToStyle:style belowLayer:otherLayer]; } - (void)removeFromStyle:(MLNStyle *)style { - [super removeFromStyle:style]; - self.style.customLayers[self.identifier] = nil; - self.style = nil; + [super removeFromStyle:style]; + self.style.customLayers[self.identifier] = nil; + self.style = nil; } - (void)didMoveToMapView:(MLNMapView *)mapView { @@ -78,74 +76,75 @@ class MLNCustomLayerHost; } - (void)setNeedsDisplay { - [self.mapView setNeedsRerender]; + [self.mapView setNeedsRerender]; } @end class MLNCustomLayerHost : public mbgl::style::CustomLayerHost { public: - MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) { - layerRef = styleLayer; - layer = nil; + MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) { + layerRef = styleLayer; + layer = nil; + } + + void initialize() { + if (layerRef == nil) + return; + else if (layer == nil) + layer = layerRef; + + if (layer.mapView) { + [layer didMoveToMapView:layer.mapView]; } + } - void initialize() { - if (layerRef == nil) return; - else if (layer == nil) layer = layerRef; - - if (layer.mapView) { - [layer didMoveToMapView:layer.mapView]; - } - } - - void render(const mbgl::style::CustomLayerRenderParameters& parameters) { - if (!layer) return; + void render(const mbgl::style::CustomLayerRenderParameters ¶meters) { + if (!layer) return; #if MLN_RENDER_BACKEND_METAL - MTL::RenderCommandEncoder* ptr = - static_cast(parameters).encoder.get(); - id encoder = (__bridge id)ptr; - layer.renderEncoder = encoder; + MTL::RenderCommandEncoder *ptr = + static_cast(parameters) + .encoder.get(); + id encoder = (__bridge id)ptr; + layer.renderEncoder = encoder; #endif - MLNStyleLayerDrawingContext drawingContext = { - .size = CGSizeMake(parameters.width, parameters.height), - .centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude), - .zoomLevel = parameters.zoom, - .direction = mbgl::util::wrap(parameters.bearing, 0., 360.), - .pitch = static_cast(parameters.pitch), - .fieldOfView = static_cast(parameters.fieldOfView), - .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix) - }; + MLNStyleLayerDrawingContext drawingContext = { + .size = CGSizeMake(parameters.width, parameters.height), + .centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude), + .zoomLevel = parameters.zoom, + .direction = mbgl::util::wrap(parameters.bearing, 0., 360.), + .pitch = static_cast(parameters.pitch), + .fieldOfView = static_cast(parameters.fieldOfView), + .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)}; - if (layer.mapView) { - [layer drawInMapView:layer.mapView withContext:drawingContext]; - } + if (layer.mapView) { + [layer drawInMapView:layer.mapView withContext:drawingContext]; } + } - void contextLost() { - } - - void deinitialize() { - if (layer == nil) return; - - if (layer.mapView) { - [layer willMoveFromMapView:layer.mapView]; - } - layerRef = layer; - layer = nil; + void contextLost() {} + + void deinitialize() { + if (layer == nil) return; + + if (layer.mapView) { + [layer willMoveFromMapView:layer.mapView]; } + layerRef = layer; + layer = nil; + } private: - __weak MLNCustomStyleLayer * layerRef; - MLNCustomStyleLayer * layer = nil; + __weak MLNCustomStyleLayer *layerRef; + MLNCustomStyleLayer *layer = nil; }; namespace mbgl { -MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { - return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer]; +MLNStyleLayer *CustomStyleLayerPeerFactory::createPeer(style::Layer *rawLayer) { + return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer]; } } // namespace mbgl diff --git a/platform/darwin/src/MLNDisplayUtils.mm b/platform/darwin/src/MLNDisplayUtils.mm index fb4a50310..a13842c7c 100644 --- a/platform/darwin/src/MLNDisplayUtils.mm +++ b/platform/darwin/src/MLNDisplayUtils.mm @@ -3,25 +3,25 @@ CGFloat MLNEffectiveScaleFactorForView(id viewOrNil) { #if TARGET_OS_IPHONE || TARGET_OS_TV - UIView *view = (UIView *)viewOrNil; - UIScreen *screen = view.window.screen ?: [UIScreen mainScreen]; + UIView *view = (UIView *)viewOrNil; + UIScreen *screen = view.window.screen ?: [UIScreen mainScreen]; - CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width; - if (logicalToPixelRatio > 0.0) { - return logicalToPixelRatio; - } + CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width; + if (logicalToPixelRatio > 0.0) { + return logicalToPixelRatio; + } - return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale; + return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale; #elif TARGET_OS_OSX - NSView *view = (NSView *)viewOrNil; - NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen]; - return screen ? screen.backingScaleFactor : 1.0; + NSView *view = (NSView *)viewOrNil; + NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen]; + return screen ? screen.backingScaleFactor : 1.0; #else - return 1.0; + return 1.0; #endif } diff --git a/platform/darwin/src/MLNFeature.mm b/platform/darwin/src/MLNFeature.mm index 749836239..0a5102b1a 100644 --- a/platform/darwin/src/MLNFeature.mm +++ b/platform/darwin/src/MLNFeature.mm @@ -1,29 +1,29 @@ -#import "MLNFoundation_Private.h" -#import "MLNFeature_Private.h" #import "MLNCluster.h" +#import "MLNFeature_Private.h" +#import "MLNFoundation_Private.h" #import "MLNPointAnnotation.h" -#import "MLNPolyline.h" #import "MLNPolygon.h" +#import "MLNPolyline.h" #import "MLNValueEvaluator.h" -#import "MLNShape_Private.h" #import "MLNPointCollection_Private.h" -#import "MLNPolyline_Private.h" #import "MLNPolygon_Private.h" +#import "MLNPolyline_Private.h" +#import "MLNShape_Private.h" -#import "NSDictionary+MLNAdditions.h" -#import "NSArray+MLNAdditions.h" -#import "NSExpression+MLNPrivateAdditions.h" #import "MLNLoggingConfiguration_Private.h" +#import "NSArray+MLNAdditions.h" +#import "NSDictionary+MLNAdditions.h" +#import "NSExpression+MLNPrivateAdditions.h" -#import -#import #import +#import +#import // Cluster constants -static NSString * const MLNClusterIdentifierKey = @"cluster_id"; -static NSString * const MLNClusterCountKey = @"point_count"; +static NSString *const MLNClusterIdentifierKey = @"cluster_id"; +static NSString *const MLNClusterCountKey = @"point_count"; const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax; @interface MLNEmptyFeature () @@ -40,28 +40,29 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry(@{@"type": @"Empty"}, self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry(@{@"type" : @"Empty"}, self.attributes, self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } - (mbgl::Geometry)geometryObject { - return mbgl::EmptyGeometry(); + return mbgl::EmptyGeometry(); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier + ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + self.attributes.count ? self.attributes : @"none"]; } @end @@ -80,25 +81,27 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - self.coordinate.latitude, self.coordinate.longitude, - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + self.coordinate.latitude, self.coordinate.longitude, + self.attributes.count ? self.attributes : @"none"]; } @end @@ -106,28 +109,28 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); @implementation MLNPointFeatureCluster - (NSUInteger)clusterIdentifier { - NSNumber *clusterNumber = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber); - MLNAssert(clusterNumber, @"Clusters should have a cluster_id"); + NSNumber *clusterNumber = + MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber); + MLNAssert(clusterNumber, @"Clusters should have a cluster_id"); - if (!clusterNumber) { - return MLNClusterIdentifierInvalid; - } + if (!clusterNumber) { + return MLNClusterIdentifierInvalid; + } - NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue]; - MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit"); + NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue]; + MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit"); - return clusterIdentifier; + return clusterIdentifier; } - (NSUInteger)clusterPointCount { - NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber); - MLNAssert(count, @"Clusters should have a point_count"); + NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber); + MLNAssert(count, @"Clusters should have a point_count"); - return [count unsignedIntegerValue]; + return [count unsignedIntegerValue]; } @end - @interface MLNPolylineFeature () @end @@ -142,26 +145,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - (unsigned long)[self pointCount], - MLNStringFromCoordinateBounds(self.overlayBounds), - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + (unsigned long)[self pointCount], + MLNStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; } @end @@ -180,26 +185,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - (unsigned long)[self pointCount], - MLNStringFromCoordinateBounds(self.overlayBounds), - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + (unsigned long)[self pointCount], + MLNStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; } @end @@ -218,16 +225,17 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } @end @@ -246,26 +254,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - (unsigned long)self.polylines.count, - MLNStringFromCoordinateBounds(self.overlayBounds), - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + (unsigned long)self.polylines.count, + MLNStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; } @end @@ -284,26 +294,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - MLNLogDebug(@"Retrieving attributeForKey: %@", key); - return self.attributes[key]; + MLNLogDebug(@"Retrieving attributeForKey: %@", key); + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - return mbglFeature({[self geometryObject]}, identifier, self.attributes); + return mbglFeature({[self geometryObject]}, identifier, self.attributes); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", - NSStringFromClass([self class]), (void *)self, - self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, - (unsigned long)self.polygons.count, - MLNStringFromCoordinateBounds(self.overlayBounds), - self.attributes.count ? self.attributes : @"none"]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] + : self.identifier, + (unsigned long)self.polygons.count, + MLNStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; } @end @@ -319,7 +331,7 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); @dynamic shapes; + (instancetype)shapeCollectionWithShapes:(NSArray *> *)shapes { - return [super shapeCollectionWithShapes:shapes]; + return [super shapeCollectionWithShapes:shapes]; } MLN_DEFINE_FEATURE_INIT_WITH_CODER(); @@ -328,22 +340,24 @@ MLN_DEFINE_FEATURE_IS_EQUAL(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); - (id)attributeForKey:(NSString *)key { - return self.attributes[key]; + return self.attributes[key]; } - (NSDictionary *)geoJSONDictionary { - return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); + return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, + self.identifier); } - (mbgl::GeoJSON)geoJSONObject { - mbgl::FeatureCollection featureCollection; - featureCollection.reserve(self.shapes.count); - for (MLNShape *feature in self.shapes) { - auto geoJSONObject = feature.geoJSONObject; - MLNAssert(geoJSONObject.is(), @"Feature collection must only contain features."); - featureCollection.push_back(geoJSONObject.get()); - } - return featureCollection; + mbgl::FeatureCollection featureCollection; + featureCollection.reserve(self.shapes.count); + for (MLNShape *feature in self.shapes) { + auto geoJSONObject = feature.geoJSONObject; + MLNAssert(geoJSONObject.is(), + @"Feature collection must only contain features."); + featureCollection.push_back(geoJSONObject.get()); + } + return featureCollection; } @end @@ -355,199 +369,211 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); template class GeometryEvaluator { private: - const mbgl::PropertyMap *shared_properties; - const bool is_in_feature; + const mbgl::PropertyMap *shared_properties; + const bool is_in_feature; public: - GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false): - shared_properties(properties), - is_in_feature(isInFeature) - {} + GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false) + : shared_properties(properties), is_in_feature(isInFeature) {} - MLNShape * operator()(const mbgl::EmptyGeometry &) const { - return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init]; - } + MLNShape *operator()(const mbgl::EmptyGeometry &) const { + return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init]; + } - MLNShape * operator()(const mbgl::Point &geometry) const { - Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class]; + MLNShape *operator()(const mbgl::Point &geometry) const { + Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation 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()) { - shapeClass = [MLNPointFeatureCluster 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()) { + shapeClass = [MLNPointFeatureCluster class]; + } } - - MLNPointAnnotation *shape = [[shapeClass alloc] init]; - shape.coordinate = toLocationCoordinate2D(geometry); - return shape; + } } - MLNShape * operator()(const mbgl::LineString &geometry) const { - std::vector coordinates = toLocationCoordinates2D(geometry); - Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class]; - return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; + MLNPointAnnotation *shape = [[shapeClass alloc] init]; + shape.coordinate = toLocationCoordinate2D(geometry); + return shape; + } + + MLNShape *operator()(const mbgl::LineString &geometry) const { + std::vector coordinates = toLocationCoordinates2D(geometry); + Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class]; + return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; + } + + MLNShape *operator()(const mbgl::Polygon &geometry) const { + return toShape(geometry, is_in_feature); + } + + MLNShape *operator()(const mbgl::MultiPoint &geometry) const { + std::vector coordinates = toLocationCoordinates2D(geometry); + Class shapeClass = + is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class]; + return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; + } + + MLNShape *operator()(const mbgl::MultiLineString &geometry) const { + NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; + for (auto &lineString : geometry) { + std::vector coordinates = toLocationCoordinates2D(lineString); + MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0] + count:coordinates.size()]; + [polylines addObject:polyline]; } - MLNShape * operator()(const mbgl::Polygon &geometry) const { - return toShape(geometry, is_in_feature); + Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class]; + return [shapeClass multiPolylineWithPolylines:polylines]; + } + + MLNShape *operator()(const mbgl::MultiPolygon &geometry) const { + NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; + for (auto &polygon : geometry) { + [polygons addObject:toShape(polygon, false)]; } - MLNShape * operator()(const mbgl::MultiPoint &geometry) const { - std::vector coordinates = toLocationCoordinates2D(geometry); - Class shapeClass = is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class]; - return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; - } - - MLNShape * operator()(const mbgl::MultiLineString &geometry) const { - NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; - for (auto &lineString : geometry) { - std::vector coordinates = toLocationCoordinates2D(lineString); - MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; - [polylines addObject:polyline]; - } - - Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class]; - return [shapeClass multiPolylineWithPolylines:polylines]; - } - - MLNShape * operator()(const mbgl::MultiPolygon &geometry) const { - NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; - for (auto &polygon : geometry) { - [polygons addObject:toShape(polygon, false)]; - } - - Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class]; - return [shapeClass multiPolygonWithPolygons:polygons]; - } - - MLNShape * operator()(const mapbox::geometry::geometry_collection &collection) const { - NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; - for (auto &geometry : collection) { - // This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(), but these are raw geometries with no associated feature IDs or attributes. - MLNShape *shape = mapbox::geometry::geometry::visit(geometry, *this); - [shapes addObject:shape]; - } - Class shapeClass = is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class]; - return [shapeClass shapeCollectionWithShapes:shapes]; + Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class]; + return [shapeClass multiPolygonWithPolygons:polygons]; + } + + MLNShape *operator()(const mapbox::geometry::geometry_collection &collection) const { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; + for (auto &geometry : collection) { + // This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(), + // but these are raw geometries with no associated feature IDs or attributes. + MLNShape *shape = mapbox::geometry::geometry::visit(geometry, *this); + [shapes addObject:shape]; } + Class shapeClass = + is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class]; + return [shapeClass shapeCollectionWithShapes:shapes]; + } private: - static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point) { - return CLLocationCoordinate2DMake(point.y, point.x); + static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point &point) { + return CLLocationCoordinate2DMake(point.y, point.x); + } + + static std::vector toLocationCoordinates2D( + const std::vector> &points) { + std::vector coordinates; + coordinates.reserve(points.size()); + std::transform(points.begin(), points.end(), std::back_inserter(coordinates), + toLocationCoordinate2D); + return coordinates; + } + + template + static U *toShape(const mbgl::Polygon &geometry, const bool isInFeature) { + auto &linearRing = geometry.front(); + std::vector coordinates = toLocationCoordinates2D(linearRing); + NSMutableArray *innerPolygons; + if (geometry.size() > 1) { + innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1]; + for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) { + auto &innerRing = *iter; + std::vector innerCoordinates = toLocationCoordinates2D(innerRing); + MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0] + count:innerCoordinates.size()]; + [innerPolygons addObject:innerPolygon]; + } } - static std::vector toLocationCoordinates2D(const std::vector> &points) { - std::vector coordinates; - coordinates.reserve(points.size()); - std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D); - return coordinates; - } - - template - static U *toShape(const mbgl::Polygon &geometry, const bool isInFeature) { - auto &linearRing = geometry.front(); - std::vector coordinates = toLocationCoordinates2D(linearRing); - NSMutableArray *innerPolygons; - if (geometry.size() > 1) { - innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1]; - for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) { - auto &innerRing = *iter; - std::vector innerCoordinates = toLocationCoordinates2D(innerRing); - MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0] count:innerCoordinates.size()]; - [innerPolygons addObject:innerPolygon]; - } - } - - Class shapeClass = isInFeature ? [V class] : [U class]; - return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; - } + Class shapeClass = isInFeature ? [V class] : [U class]; + return [shapeClass polygonWithCoordinates:&coordinates[0] + count:coordinates.size() + interiorPolygons:innerPolygons]; + } }; template class GeoJSONEvaluator { public: - MLNShape * operator()(const mbgl::Geometry &geometry) const { - GeometryEvaluator evaluator; - MLNShape *shape = mapbox::geometry::geometry::visit(geometry, evaluator); - return shape; - } + MLNShape *operator()(const mbgl::Geometry &geometry) const { + GeometryEvaluator evaluator; + MLNShape *shape = mapbox::geometry::geometry::visit(geometry, evaluator); + return shape; + } - MLNShape * operator()(const mbgl::GeoJSONFeature &feature) const { - MLNShape *shape = (MLNShape *)MLNFeatureFromMBGLFeature(feature); - return shape; - } + MLNShape *operator()(const mbgl::GeoJSONFeature &feature) const { + MLNShape *shape = (MLNShape *)MLNFeatureFromMBGLFeature(feature); + return shape; + } - MLNShape * operator()(const mbgl::FeatureCollection &collection) const { - NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; - for (const auto &feature : collection) { - [shapes addObject:MLNFeatureFromMBGLFeature(feature)]; - } - return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes]; + MLNShape *operator()(const mbgl::FeatureCollection &collection) const { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; + for (const auto &feature : collection) { + [shapes addObject:MLNFeatureFromMBGLFeature(feature)]; } + return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes]; + } }; -NSArray *> *MLNFeaturesFromMBGLFeatures(const std::vector &features) { - NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; - for (const auto &feature : features) { - [shapes addObject:MLNFeatureFromMBGLFeature(static_cast(feature))]; - } - return shapes; +NSArray *> *MLNFeaturesFromMBGLFeatures( + const std::vector &features) { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; + for (const auto &feature : features) { + [shapes addObject:MLNFeatureFromMBGLFeature(static_cast(feature))]; + } + return shapes; } -NSArray *> *MLNFeaturesFromMBGLFeatures(const std::vector &features) { - NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; - for (const auto &feature : features) { - [shapes addObject:MLNFeatureFromMBGLFeature(feature)]; - } - return shapes; +NSArray *> *MLNFeaturesFromMBGLFeatures( + const std::vector &features) { + NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; + for (const auto &feature : features) { + [shapes addObject:MLNFeatureFromMBGLFeature(feature)]; + } + return shapes; } -id MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) { - NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()]; - for (auto &pair : feature.properties) { - auto &value = pair.second; - ValueEvaluator evaluator; - attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); - } - GeometryEvaluator evaluator(&feature.properties, true); - MLNShape *shape = (MLNShape *)mapbox::geometry::geometry::visit(feature.geometry, evaluator); - if (!feature.id.is()) { - shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); - } - shape.attributes = attributes; +id MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) { + NSMutableDictionary *attributes = + [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()]; + for (auto &pair : feature.properties) { + auto &value = pair.second; + ValueEvaluator evaluator; + attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); + } + GeometryEvaluator evaluator(&feature.properties, true); + MLNShape *shape = (MLNShape *)mapbox::geometry::geometry::visit( + feature.geometry, evaluator); + if (!feature.id.is()) { + shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); + } + shape.attributes = attributes; - return shape; + return shape; } -MLNShape* MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) { - GeoJSONEvaluator evaluator; - MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator); - return shape; +MLNShape *MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) { + GeoJSONEvaluator evaluator; + MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator); + return shape; } -mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary *attributes) -{ - if (identifier) { - NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier]; - feature.id = [identifierExpression mgl_featureIdentifier]; - } - feature.properties = [attributes mgl_propertyMap]; - return feature; +mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, + NSDictionary *attributes) { + if (identifier) { + NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier]; + feature.id = [identifierExpression mgl_featureIdentifier]; + } + feature.properties = [attributes mgl_propertyMap]; + return feature; } -NSDictionary *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier) { - NSMutableDictionary *feature = [@{@"type": @"Feature", - @"properties": attributes, - @"geometry": geometry} mutableCopy]; - feature[@"id"] = identifier; - return [feature copy]; +NSDictionary *NSDictionaryFeatureForGeometry(NSDictionary *geometry, + NSDictionary *attributes, + id identifier) { + NSMutableDictionary *feature = + [@{@"type" : @"Feature", @"properties" : attributes, @"geometry" : geometry} mutableCopy]; + feature[@"id"] = identifier; + return [feature copy]; } diff --git a/platform/darwin/src/MLNForegroundStyleLayer.mm b/platform/darwin/src/MLNForegroundStyleLayer.mm index c22d351b4..320994518 100644 --- a/platform/darwin/src/MLNForegroundStyleLayer.mm +++ b/platform/darwin/src/MLNForegroundStyleLayer.mm @@ -4,23 +4,22 @@ @implementation MLNForegroundStyleLayer - (NSString *)sourceIdentifier { - [NSException raise:MLNAbstractClassException - format:@"MLNForegroundStyleLayer is an abstract class"]; - return nil; + [NSException raise:MLNAbstractClassException + format:@"MLNForegroundStyleLayer is an abstract class"]; + return nil; } - (NSString *)description { - if (self.rawLayer) { - return [NSString stringWithFormat: - @"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, - self.sourceIdentifier, self.visible ? @"YES" : @"NO"]; - } - else { - return [NSString stringWithFormat: - @"<%@: %p; identifier = %@; sourceIdentifier = ; visible = NO>", - NSStringFromClass([self class]), (void *)self, self.identifier]; - } + if (self.rawLayer) { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>", + NSStringFromClass([self class]), (void *)self, self.identifier, + self.sourceIdentifier, self.visible ? @"YES" : @"NO"]; + } else { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = ; visible = NO>", + NSStringFromClass([self class]), (void *)self, self.identifier]; + } } @end diff --git a/platform/darwin/src/MLNFoundation.mm b/platform/darwin/src/MLNFoundation.mm index 17727bd62..df7f89f06 100644 --- a/platform/darwin/src/MLNFoundation.mm +++ b/platform/darwin/src/MLNFoundation.mm @@ -1,6 +1,4 @@ #import "MLNFoundation_Private.h" /// Initializes the run loop shim that lives on the main thread. -void MLNInitializeRunLoop() { - static mbgl::util::RunLoop runLoop; -} +void MLNInitializeRunLoop() { static mbgl::util::RunLoop runLoop; } diff --git a/platform/darwin/src/MLNGeometry.mm b/platform/darwin/src/MLNGeometry.mm index fa8775308..03c43b7fd 100644 --- a/platform/darwin/src/MLNGeometry.mm +++ b/platform/darwin/src/MLNGeometry.mm @@ -19,113 +19,141 @@ const CLLocationDegrees MLNAngularFieldOfView = 30; const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0}; CGRect MLNExtendRect(CGRect rect, CGPoint point) { - if (point.x < rect.origin.x) { - rect.size.width += rect.origin.x - point.x; - rect.origin.x = point.x; - } - if (point.x > rect.origin.x + rect.size.width) { - rect.size.width += point.x - (rect.origin.x + rect.size.width); - } - if (point.y < rect.origin.y) { - rect.size.height += rect.origin.y - point.y; - rect.origin.y = point.y; - } - if (point.y > rect.origin.y + rect.size.height) { - rect.size.height += point.y - (rect.origin.y + rect.size.height); - } - return rect; + if (point.x < rect.origin.x) { + rect.size.width += rect.origin.x - point.x; + rect.origin.x = point.x; + } + if (point.x > rect.origin.x + rect.size.width) { + rect.size.width += point.x - (rect.origin.x + rect.size.width); + } + if (point.y < rect.origin.y) { + rect.size.height += rect.origin.y - point.y; + rect.origin.y = point.y; + } + if (point.y > rect.origin.y + rect.size.height) { + rect.size.height += point.y - (rect.origin.y + rect.size.height); + } + return rect; } mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) { - try { - return mbgl::LatLng(coordinate.latitude, coordinate.longitude); - } catch (std::domain_error &error) { - [NSException raise:NSInvalidArgumentException format:@"%s", error.what()]; - return {}; - } + try { + return mbgl::LatLng(coordinate.latitude, coordinate.longitude); + } catch (std::domain_error &error) { + [NSException raise:NSInvalidArgumentException format:@"%s", error.what()]; + return {}; + } } -CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) { - CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); - CLLocationDistance metersTall = metersPerPixel * size.height; - CLLocationDistance altitude = metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); - return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2); +CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, + CLLocationDegrees latitude, CGSize size) { + CLLocationDistance metersPerPixel = + mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); + CLLocationDistance metersTall = metersPerPixel * size.height; + CLLocationDistance altitude = + metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); + return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2); } -double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) { - CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2); - CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); - CLLocationDistance metersPerPixel = metersTall / size.height; - CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel; - return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D); +double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, + CLLocationDegrees latitude, CGSize size) { + CLLocationDistance eyeAltitude = + altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2); + CLLocationDistance metersTall = + eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); + CLLocationDistance metersPerPixel = metersTall / size.height; + CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * + mbgl::util::EARTH_RADIUS_M / metersPerPixel; + return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D); } -MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) { - double a = pow(sin((to.latitude - from.latitude) / 2), 2) - + pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude); +MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, + MLNRadianCoordinate2D to) { + double a = + pow(sin((to.latitude - from.latitude) / 2), 2) + + pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude); - return 2 * atan2(sqrt(a), sqrt(1 - a)); + return 2 * atan2(sqrt(a), sqrt(1 - a)); } -MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) { - double a = sin(to.longitude - from.longitude) * cos(to.latitude); - double b = cos(from.latitude) * sin(to.latitude) - - sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude); - return atan2(a, b); +MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from, + MLNRadianCoordinate2D to) { + double a = sin(to.longitude - from.longitude) * cos(to.latitude); + double b = cos(from.latitude) * sin(to.latitude) - + sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude); + return atan2(a, b); } MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate, MLNRadianDistance distance, MLNRadianDirection direction) { - double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) - + cos(coordinate.latitude) * sin(distance) * cos(direction)); - double otherLongitude = coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude), - cos(distance) - sin(coordinate.latitude) * sin(otherLatitude)); - return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude); + double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) + + cos(coordinate.latitude) * sin(distance) * cos(direction)); + double otherLongitude = + coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude), + cos(distance) - sin(coordinate.latitude) * sin(otherLatitude)); + return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude); } -CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, CLLocationCoordinate2D secondCoordinate) { - if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) && MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) { - return 0; - } +CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, + CLLocationCoordinate2D secondCoordinate) { + if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) && + MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) { + return 0; + } + // Ported from + // https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31 + MLNRadianCoordinate2D firstRadianCoordinate = + MLNRadianCoordinateFromLocationCoordinate(firstCoordinate); + MLNRadianCoordinate2D secondRadianCoordinate = + MLNRadianCoordinateFromLocationCoordinate(secondCoordinate); - // Ported from https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31 - MLNRadianCoordinate2D firstRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(firstCoordinate); - MLNRadianCoordinate2D secondRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(secondCoordinate); - - CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) * cos(secondRadianCoordinate.latitude); - CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude) - - sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude)); - MLNRadianDirection radianDirection = atan2(a, b); - return radianDirection * 180 / M_PI; + CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) * + cos(secondRadianCoordinate.latitude); + CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude) - + sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * + cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude)); + MLNRadianDirection radianDirection = atan2(a, b); + return radianDirection * 180 / M_PI; } CGPoint MLNPointRounded(CGPoint point) { #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - CGFloat scaleFactor = [UIScreen mainScreen].nativeScale; + CGFloat scaleFactor = [UIScreen mainScreen].nativeScale; #elif TARGET_OS_MAC - CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; + CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; #endif - return CGPointMake(round(point.x * scaleFactor) / scaleFactor, round(point.y * scaleFactor) / scaleFactor); + return CGPointMake(round(point.x * scaleFactor) / scaleFactor, + round(point.y * scaleFactor) / scaleFactor); } MLNMapPoint MLNMapPointForCoordinate(CLLocationCoordinate2D coordinate, double zoomLevel) { - mbgl::Point projectedCoordinate = mbgl::Projection::project(MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel)); - return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel); + mbgl::Point projectedCoordinate = mbgl::Projection::project( + MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel)); + return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel); } -MLNMatrix4 MLNMatrix4Make(std::array array) { - MLNMatrix4 mat4 = { - .m00 = array[0], .m01 = array[1], .m02 = array[2], .m03 = array[3], - .m10 = array[4], .m11 = array[5], .m12 = array[6], .m13 = array[7], - .m20 = array[8], .m21 = array[9], .m22 = array[10], .m23 = array[11], - .m30 = array[12], .m31 = array[13], .m32 = array[14], .m33 = array[15] - }; - return mat4; +MLNMatrix4 MLNMatrix4Make(std::array array) { + MLNMatrix4 mat4 = {.m00 = array[0], + .m01 = array[1], + .m02 = array[2], + .m03 = array[3], + .m10 = array[4], + .m11 = array[5], + .m12 = array[6], + .m13 = array[7], + .m20 = array[8], + .m21 = array[9], + .m22 = array[10], + .m23 = array[11], + .m30 = array[12], + .m31 = array[13], + .m32 = array[14], + .m33 = array[15]}; + return mat4; } -BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) -{ - return MAX(left, right) - MIN(left, right) <= accuracy; +BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) { + return MAX(left, right) - MIN(left, right) <= accuracy; } diff --git a/platform/darwin/src/MLNImageSource.mm b/platform/darwin/src/MLNImageSource.mm index 667c54386..90ee4c0f1 100644 --- a/platform/darwin/src/MLNImageSource.mm +++ b/platform/darwin/src/MLNImageSource.mm @@ -6,16 +6,17 @@ #import "MLNTileSource_Private.h" #import "NSURL+MLNAdditions.h" #if TARGET_OS_IPHONE - #import "UIImage+MLNAdditions.h" +#import "UIImage+MLNAdditions.h" #else - #import "NSImage+MLNAdditions.h" +#import "NSImage+MLNAdditions.h" #endif #include #include @interface MLNImageSource () -- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithIdentifier:(NSString *)identifier + coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER; @property (nonatomic, readonly) mbgl::style::ImageSource *rawSource; @@ -23,90 +24,93 @@ @implementation MLNImageSource -- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad { - - const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad); - auto source = std::make_unique(identifier.UTF8String, coordsArray); - return self = [super initWithPendingSource:std::move(source)]; +- (instancetype)initWithIdentifier:(NSString *)identifier + coordinateQuad:(MLNCoordinateQuad)coordinateQuad { + const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad); + auto source = std::make_unique(identifier.UTF8String, coordsArray); + return self = [super initWithPendingSource:std::move(source)]; } - -- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad URL:(NSURL *)url { - self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; - self.URL = url; - return self; +- (instancetype)initWithIdentifier:(NSString *)identifier + coordinateQuad:(MLNCoordinateQuad)coordinateQuad + URL:(NSURL *)url { + self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad]; + self.URL = url; + return self; } +- (instancetype)initWithIdentifier:(NSString *)identifier + coordinateQuad:(MLNCoordinateQuad)coordinateQuad + image:(MLNImage *)image { + self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad]; + self.image = image; -- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad image:(MLNImage *)image { - self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; - self.image = image; - - return self; + return self; } - (NSURL *)URL { - MLNAssertStyleSourceIsValid(); - auto url = self.rawSource->getURL(); - return url ? [NSURL URLWithString:@(url->c_str())] : nil; + MLNAssertStyleSourceIsValid(); + auto url = self.rawSource->getURL(); + return url ? [NSURL URLWithString:@(url->c_str())] : nil; } - (void)setURL:(NSURL *)url { - MLNAssertStyleSourceIsValid(); - if (url) { - self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); - _image = nil; - } else { - self.image = nullptr; - } + MLNAssertStyleSourceIsValid(); + if (url) { + self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); + _image = nil; + } else { + self.image = nullptr; + } } - (void)setImage:(MLNImage *)image { - MLNAssertStyleSourceIsValid(); - if (image != nullptr) { - self.rawSource->setImage(image.mgl_premultipliedImage); - } else { - self.rawSource->setImage(mbgl::PremultipliedImage({0,0})); - } - _image = image; + MLNAssertStyleSourceIsValid(); + if (image != nullptr) { + self.rawSource->setImage(image.mgl_premultipliedImage); + } else { + self.rawSource->setImage(mbgl::PremultipliedImage({0, 0})); + } + _image = image; } - (MLNCoordinateQuad)coordinates { - MLNAssertStyleSourceIsValid(); - return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates()); + MLNAssertStyleSourceIsValid(); + return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates()); } -- (void)setCoordinates: (MLNCoordinateQuad)coordinateQuad { - MLNAssertStyleSourceIsValid(); - self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad)); +- (void)setCoordinates:(MLNCoordinateQuad)coordinateQuad { + MLNAssertStyleSourceIsValid(); + self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad)); } - (NSString *)description { - if (self.rawSource) { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, - MLNStringFromCoordinateQuad(self.coordinates), - self.URL, - self.image]; - } - else { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = ; URL = ; image = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, self.image]; - } + if (self.rawSource) { + return [NSString + stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>", + NSStringFromClass([self class]), (void *)self, self.identifier, + MLNStringFromCoordinateQuad(self.coordinates), self.URL, self.image]; + } else { + return [NSString + stringWithFormat: + @"<%@: %p; identifier = %@; coordinates = ; URL = ; image = %@>", + NSStringFromClass([self class]), (void *)self, self.identifier, self.image]; + } } - (mbgl::style::ImageSource *)rawSource { - return (mbgl::style::ImageSource *)super.rawSource; + return (mbgl::style::ImageSource *)super.rawSource; } - (NSString *)attributionHTMLString { - if (!self.rawSource) { - MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); - return nil; - } + if (!self.rawSource) { + MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", + self.identifier); + return nil; + } - auto attribution = self.rawSource->getAttribution(); - return attribution ? @(attribution->c_str()) : nil; + auto attribution = self.rawSource->getAttribution(); + return attribution ? @(attribution->c_str()) : nil; } @end diff --git a/platform/darwin/src/MLNLoggingConfiguration.mm b/platform/darwin/src/MLNLoggingConfiguration.mm index a80ad45a7..8bf8ed1a2 100644 --- a/platform/darwin/src/MLNLoggingConfiguration.mm +++ b/platform/darwin/src/MLNLoggingConfiguration.mm @@ -1,5 +1,5 @@ -#include #include +#include #import "MLNLoggingConfiguration_Private.h" @@ -10,154 +10,155 @@ namespace mbgl { -class MLNCoreLoggingObserver : public Log :: Observer { +class MLNCoreLoggingObserver : public Log ::Observer { public: - //Return true not print messages at core level, and filter at platform level. - bool onRecord(EventSeverity severity, Event event, int64_t code, const std::string& msg) override{ - - NSString *message = [NSString stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum::toString(event), code, [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]]; - switch (severity) { - case EventSeverity::Debug: - MLNLogDebug(message); - break; - case EventSeverity::Info: - MLNLogInfo(message); - break; - case EventSeverity::Warning: - MLNLogWarning(message); - break; - case EventSeverity::Error: - MLNLogError(message); - break; - default: - assert(false); - } - return true; + // Return true not print messages at core level, and filter at platform level. + bool onRecord(EventSeverity severity, Event event, int64_t code, + const std::string &msg) override { + NSString *message = [NSString + stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum::toString(event), code, + [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]]; + switch (severity) { + case EventSeverity::Debug: + MLNLogDebug(message); + break; + case EventSeverity::Info: + MLNLogInfo(message); + break; + case EventSeverity::Warning: + MLNLogWarning(message); + break; + case EventSeverity::Error: + MLNLogError(message); + break; + default: + assert(false); } + return true; + } }; -} +} // namespace mbgl -@implementation MLNLoggingConfiguration -{ - std::unique_ptr _coreLoggingObserver; +@implementation MLNLoggingConfiguration { + std::unique_ptr _coreLoggingObserver; } + (instancetype)sharedConfiguration { - static dispatch_once_t once; - static id sharedConfiguration; - dispatch_once(&once, ^{ - sharedConfiguration = [[self alloc] init]; - ((MLNLoggingConfiguration *)sharedConfiguration).handler = nil; - }); - return sharedConfiguration; + static dispatch_once_t once; + static id sharedConfiguration; + dispatch_once(&once, ^{ + sharedConfiguration = [[self alloc] init]; + ((MLNLoggingConfiguration *)sharedConfiguration).handler = nil; + }); + return sharedConfiguration; } -- (id)init{ - if(self = [super init]){ - mbgl::Log::setObserver(std::make_unique()); - } - return self; +- (id)init { + if (self = [super init]) { + mbgl::Log::setObserver(std::make_unique()); + } + return self; } - (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler { - - if (!handler) { - _handler = [self defaultBlockHandler]; - } else { - _handler = handler; - } + if (!handler) { + _handler = [self defaultBlockHandler]; + } else { + _handler = handler; + } } -- (void)logCallingFunction:(const char *)callingFunction functionLine:(NSUInteger)functionLine messageType:(MLNLoggingLevel)type format:(id)messageFormat, ... { - va_list formatList; - va_start(formatList, messageFormat); - NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList]; - va_end(formatList); - - _handler(type, @(callingFunction), functionLine, formattedMessage); +- (void)logCallingFunction:(const char *)callingFunction + functionLine:(NSUInteger)functionLine + messageType:(MLNLoggingLevel)type + format:(id)messageFormat, ... { + va_list formatList; + va_start(formatList, messageFormat); + NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList]; + va_end(formatList); + _handler(type, @(callingFunction), functionLine, formattedMessage); } - (MLNLoggingBlockHandler)defaultBlockHandler { - MLNLoggingBlockHandler maplibreHandler = ^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) { - + MLNLoggingBlockHandler maplibreHandler = + ^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) { if (@available(iOS 10.0, macOS 10.12.0, *)) { - static dispatch_once_t once; - static os_log_t info_log; + static dispatch_once_t once; + static os_log_t info_log; #if MLN_LOGGING_ENABLE_DEBUG - static os_log_t debug_log; + static os_log_t debug_log; #endif - static os_log_t error_log; - static os_log_t fault_log; - static os_log_type_t log_types[] = { OS_LOG_TYPE_DEFAULT, - OS_LOG_TYPE_INFO, + static os_log_t error_log; + static os_log_t fault_log; + static os_log_type_t log_types[] = {OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_INFO, #if MLN_LOGGING_ENABLE_DEBUG - OS_LOG_TYPE_DEBUG, + OS_LOG_TYPE_DEBUG, #endif - OS_LOG_TYPE_ERROR, - OS_LOG_TYPE_FAULT }; - constexpr const char* const subsystem = "org.maplibre.Native"; - dispatch_once(&once, ^ { - info_log = os_log_create(subsystem, "INFO"); + OS_LOG_TYPE_ERROR, OS_LOG_TYPE_FAULT}; + constexpr const char *const subsystem = "org.maplibre.Native"; + dispatch_once(&once, ^{ + info_log = os_log_create(subsystem, "INFO"); #if MLN_LOGGING_ENABLE_DEBUG - debug_log = os_log_create(subsystem, "DEBUG"); + debug_log = os_log_create(subsystem, "DEBUG"); #endif - error_log = os_log_create(subsystem, "ERROR"); - fault_log = os_log_create(subsystem, "FAULT"); - }); + error_log = os_log_create(subsystem, "ERROR"); + fault_log = os_log_create(subsystem, "FAULT"); + }); - os_log_t maplibre_log; - switch (level) { - case MLNLoggingLevelInfo: - case MLNLoggingLevelWarning: - maplibre_log = info_log; - break; + os_log_t maplibre_log; + switch (level) { + case MLNLoggingLevelInfo: + case MLNLoggingLevelWarning: + maplibre_log = info_log; + break; #if MLN_LOGGING_ENABLE_DEBUG - case MLNLoggingLevelDebug: - maplibre_log = debug_log; - break; + case MLNLoggingLevelDebug: + maplibre_log = debug_log; + break; #endif - case MLNLoggingLevelError: - maplibre_log = error_log; - break; - case MLNLoggingLevelFault: - maplibre_log = fault_log; - break; - case MLNLoggingLevelNone: - default: - break; - } + case MLNLoggingLevelError: + maplibre_log = error_log; + break; + case MLNLoggingLevelFault: + maplibre_log = fault_log; + break; + case MLNLoggingLevelNone: + default: + break; + } - os_log_type_t logType = log_types[level]; - os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line, message); + os_log_type_t logType = log_types[level]; + os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line, + message); } else { - NSString *category; - switch (level) { - case MLNLoggingLevelInfo: - case MLNLoggingLevelWarning: - category = @"INFO"; - break; + NSString *category; + switch (level) { + case MLNLoggingLevelInfo: + case MLNLoggingLevelWarning: + category = @"INFO"; + break; #if MLN_LOGGING_ENABLE_DEBUG - case MLNLoggingLevelDebug: - category = @"DEBUG"; - break; + case MLNLoggingLevelDebug: + category = @"DEBUG"; + break; #endif - case MLNLoggingLevelError: - category = @"ERROR"; - break; - case MLNLoggingLevelFault: - category = @"FAULT"; - break; - case MLNLoggingLevelNone: - default: - break; - } + case MLNLoggingLevelError: + category = @"ERROR"; + break; + case MLNLoggingLevelFault: + category = @"FAULT"; + break; + case MLNLoggingLevelNone: + default: + break; + } - NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message); + NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message); } - }; + }; - return maplibreHandler; + return maplibreHandler; } @end diff --git a/platform/darwin/src/MLNMapCamera.mm b/platform/darwin/src/MLNMapCamera.mm index ebe8cdb27..d978e86bc 100644 --- a/platform/darwin/src/MLNMapCamera.mm +++ b/platform/darwin/src/MLNMapCamera.mm @@ -8,191 +8,179 @@ @implementation MLNMapCamera -+ (BOOL)supportsSecureCoding -{ - return YES; ++ (BOOL)supportsSecureCoding { + return YES; } -+ (instancetype)camera -{ - return [[self alloc] init]; ++ (instancetype)camera { + return [[self alloc] init]; } + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate - eyeAltitude:(CLLocationDistance)eyeAltitude -{ - CLLocationDirection heading = -1; - CGFloat pitch = -1; - if (CLLocationCoordinate2DIsValid(centerCoordinate) && CLLocationCoordinate2DIsValid(eyeCoordinate)) { - heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate); + eyeAltitude:(CLLocationDistance)eyeAltitude { + CLLocationDirection heading = -1; + CGFloat pitch = -1; + if (CLLocationCoordinate2DIsValid(centerCoordinate) && + CLLocationCoordinate2DIsValid(eyeCoordinate)) { + heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate); - CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude - longitude:centerCoordinate.longitude]; - CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude - longitude:eyeCoordinate.longitude]; - CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation]; - CGFloat radianPitch = atan2(eyeAltitude, groundDistance); - pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0); - } + CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude + longitude:centerCoordinate.longitude]; + CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude + longitude:eyeCoordinate.longitude]; + CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation]; + CGFloat radianPitch = atan2(eyeAltitude, groundDistance); + pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0); + } - return [[self alloc] initWithCenterCoordinate:centerCoordinate - altitude:eyeAltitude - pitch:pitch - heading:heading]; + return [[self alloc] initWithCenterCoordinate:centerCoordinate + altitude:eyeAltitude + pitch:pitch + heading:heading]; } + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate acrossDistance:(CLLocationDistance)distance pitch:(CGFloat)pitch - heading:(CLLocationDirection)heading -{ - // Angle at the viewpoint formed by the straight lines running perpendicular - // to the ground and toward the center coordinate. - CLLocationDirection eyeAngle = 90 - pitch; - CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle)); + heading:(CLLocationDirection)heading { + // Angle at the viewpoint formed by the straight lines running perpendicular + // to the ground and toward the center coordinate. + CLLocationDirection eyeAngle = 90 - pitch; + CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle)); - return [[self alloc] initWithCenterCoordinate:centerCoordinate - altitude:altitude - pitch:pitch - heading:heading]; + return [[self alloc] initWithCenterCoordinate:centerCoordinate + altitude:altitude + pitch:pitch + heading:heading]; } + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate altitude:(CLLocationDistance)altitude pitch:(CGFloat)pitch - heading:(CLLocationDirection)heading -{ - return [[self alloc] initWithCenterCoordinate:centerCoordinate - altitude:altitude - pitch:pitch - heading:heading]; + heading:(CLLocationDirection)heading { + return [[self alloc] initWithCenterCoordinate:centerCoordinate + altitude:altitude + pitch:pitch + heading:heading]; } + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate fromDistance:(CLLocationDistance)distance pitch:(CGFloat)pitch - heading:(CLLocationDirection)heading -{ - // TODO: Remove this compatibility shim in favor of `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:. - // https://github.com/mapbox/mapbox-gl-native/issues/12943 - return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate - altitude:distance - pitch:pitch - heading:heading]; + heading:(CLLocationDirection)heading { + // TODO: Remove this compatibility shim in favor of + // `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:. + // https://github.com/mapbox/mapbox-gl-native/issues/12943 + return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + altitude:distance + pitch:pitch + heading:heading]; } - (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate altitude:(CLLocationDistance)altitude pitch:(CGFloat)pitch - heading:(CLLocationDirection)heading -{ - MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°", MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading); - if (self = [super init]) - { - _centerCoordinate = centerCoordinate; - _altitude = altitude; - _pitch = pitch; - _heading = heading; - _roll = 0; - } - return self; + heading:(CLLocationDirection)heading { + MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°", + MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading); + if (self = [super init]) { + _centerCoordinate = centerCoordinate; + _altitude = altitude; + _pitch = pitch; + _heading = heading; + _roll = 0; + } + return self; } -- (nullable instancetype)initWithCoder:(NSCoder *)coder -{ - MLNLogInfo(@"Initialiazing with coder."); - if (self = [super init]) - { - _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"], - [coder decodeDoubleForKey:@"centerLongitude"]); - _altitude = [coder decodeDoubleForKey:@"altitude"]; - _pitch = [coder decodeDoubleForKey:@"pitch"]; - _heading = [coder decodeDoubleForKey:@"heading"]; - _roll = [coder decodeDoubleForKey:@"roll"]; - } - return self; +- (nullable instancetype)initWithCoder:(NSCoder *)coder { + MLNLogInfo(@"Initialiazing with coder."); + if (self = [super init]) { + _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"], + [coder decodeDoubleForKey:@"centerLongitude"]); + _altitude = [coder decodeDoubleForKey:@"altitude"]; + _pitch = [coder decodeDoubleForKey:@"pitch"]; + _heading = [coder decodeDoubleForKey:@"heading"]; + _roll = [coder decodeDoubleForKey:@"roll"]; + } + return self; } -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"]; - [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"]; - [coder encodeDouble:_altitude forKey:@"altitude"]; - [coder encodeDouble:_pitch forKey:@"pitch"]; - [coder encodeDouble:_heading forKey:@"heading"]; - [coder encodeDouble:_roll forKey:@"roll"]; +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"]; + [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"]; + [coder encodeDouble:_altitude forKey:@"altitude"]; + [coder encodeDouble:_pitch forKey:@"pitch"]; + [coder encodeDouble:_heading forKey:@"heading"]; + [coder encodeDouble:_roll forKey:@"roll"]; } -- (id)copyWithZone:(nullable NSZone *)zone -{ - MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate - altitude:_altitude - pitch:_pitch - heading:_heading]; - copy.roll = _roll; - return copy; +- (id)copyWithZone:(nullable NSZone *)zone { + MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate + altitude:_altitude + pitch:_pitch + heading:_heading]; + copy.roll = _roll; + return copy; } + (NSSet *)keyPathsForValuesAffectingViewingDistance { - return [NSSet setWithObjects:@"altitude", @"pitch", nil]; + return [NSSet setWithObjects:@"altitude", @"pitch", nil]; } - (CLLocationDistance)viewingDistance { - CLLocationDirection eyeAngle = 90 - self.pitch; - return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle)); + CLLocationDirection eyeAngle = 90 - self.pitch; + return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle)); } - (void)setViewingDistance:(CLLocationDistance)distance { - MLNLogDebug(@"Setting viewingDistance: %f", distance); - CLLocationDirection eyeAngle = 90 - self.pitch; - self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle)); + MLNLogDebug(@"Setting viewingDistance: %f", distance); + CLLocationDirection eyeAngle = 90 - self.pitch; + self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle)); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; heading = %.0f°; pitch = %.0f°; roll = %.0f°>", - NSStringFromClass([self class]), (void *)self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch, _roll]; +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; " + @"heading = %.0f°; pitch = %.0f°; roll = %.0f°>", + NSStringFromClass([self class]), (void *)self, + _centerCoordinate.latitude, _centerCoordinate.longitude, + _altitude, _heading, _pitch, _roll]; } -- (BOOL)isEqual:(id)other -{ - if ( ! [other isKindOfClass:[self class]]) - { - return NO; - } - if (other == self) - { - return YES; - } +- (BOOL)isEqual:(id)other { + if (![other isKindOfClass:[self class]]) { + return NO; + } + if (other == self) { + return YES; + } - MLNMapCamera *otherCamera = other; - return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude - && _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude - && _altitude == otherCamera.altitude - && _pitch == otherCamera.pitch && _heading == otherCamera.heading - && _roll == otherCamera.roll); + MLNMapCamera *otherCamera = other; + return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude && + _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude && + _altitude == otherCamera.altitude && _pitch == otherCamera.pitch && + _heading == otherCamera.heading && _roll == otherCamera.roll); } -- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera -{ - if (otherCamera == self) - { - return YES; - } +- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera { + if (otherCamera == self) { + return YES; + } - return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-8) - && MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-8) - && MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) - && MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) - && MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) - && MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2)); + return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, + otherCamera.centerCoordinate.latitude, 1e-8) && + MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, + otherCamera.centerCoordinate.longitude, 1e-8) && + MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) && + MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) && + MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) && + MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2)); } -- (NSUInteger)hash -{ - return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash - + @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash); +- (NSUInteger)hash { + return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash + + @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash); } @end diff --git a/platform/darwin/src/MLNMapOptions.mm b/platform/darwin/src/MLNMapOptions.mm index 6c24047cd..c91640917 100644 --- a/platform/darwin/src/MLNMapOptions.mm +++ b/platform/darwin/src/MLNMapOptions.mm @@ -6,19 +6,17 @@ @implementation MLNMapOptions --(instancetype _Nonnull)init -{ - self = [super init]; - if (self) - { - _styleURL = nil; - _styleJSON = nil; - _pluginLayers = nil; +- (instancetype _Nonnull)init { + self = [super init]; + if (self) { + _styleURL = nil; + _styleJSON = nil; + _pluginLayers = nil; - _actionJournalOptions = [[MLNActionJournalOptions alloc] init]; - } + _actionJournalOptions = [[MLNActionJournalOptions alloc] init]; + } - return self; + return self; } @end diff --git a/platform/darwin/src/MLNMapSnapshotter.mm b/platform/darwin/src/MLNMapSnapshotter.mm index 3f0c2c5be..079882352 100644 --- a/platform/darwin/src/MLNMapSnapshotter.mm +++ b/platform/darwin/src/MLNMapSnapshotter.mm @@ -2,139 +2,161 @@ #import #import -#import +#import #import #import -#import #import #import +#import #import -#import "MLNOfflineStorage_Private.h" -#import "MLNGeometry_Private.h" -#import "MLNStyle_Private.h" +#import "MLNAnnotationImage_Private.h" #import "MLNAttributionInfo_Private.h" +#import "MLNGeometry_Private.h" #import "MLNLoggingConfiguration_Private.h" -#import "MLNRendererConfiguration.h" #import "MLNMapSnapshotter_Private.h" +#import "MLNMultiPoint_Private.h" +#import "MLNOfflineStorage_Private.h" +#import "MLNPointCollection.h" +#import "MLNPolygon.h" +#import "MLNPolyline.h" +#import "MLNRendererConfiguration.h" #import "MLNSettings_Private.h" #import "MLNShape.h" -#import "MLNPolyline.h" -#import "MLNPolygon.h" -#import "MLNMultiPoint_Private.h" #import "MLNShapeCollection.h" -#import "MLNPointCollection.h" -#import "MLNAnnotationImage_Private.h" +#import "MLNStyle_Private.h" #if TARGET_OS_IPHONE -#import "UIImage+MLNAdditions.h" #import "UIColor+MLNAdditions.h" +#import "UIImage+MLNAdditions.h" #else -#import "NSImage+MLNAdditions.h" -#import "NSColor+MLNAdditions.h" #import #import #import #import +#import "NSColor+MLNAdditions.h" +#import "NSImage+MLNAdditions.h" #endif #import "NSBundle+MLNAdditions.h" const CGPoint MLNLogoImagePosition = CGPointMake(8, 8); const CGFloat MLNSnapshotterMinimumPixelSize = 64; -NSString * const MLNSnapshotterAnnotationSpritePrefix = @"org.maplibre.sprites."; +NSString *const MLNSnapshotterAnnotationSpritePrefix = @"org.maplibre.sprites."; -MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOptions *options, MLNMapSnapshotOverlayHandler overlayHandler); -MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError); -NSArray *MLNAttributionInfosFromAttributions(mbgl::MapSnapshotter::Attributions attributions); +MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, + mbgl::MapSnapshotter::PointForFn pointForFn, + mbgl::MapSnapshotter::LatLngForFn latLngForFn, + MLNMapSnapshotOptions *options, + MLNMapSnapshotOverlayHandler overlayHandler); +MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, + mbgl::MapSnapshotter::Attributions attributions, + mbgl::MapSnapshotter::PointForFn pointForFn, + mbgl::MapSnapshotter::LatLngForFn latLngForFn, + MLNMapSnapshotOverlayHandler overlayHandler, + NSError *_Nullable *outError); +NSArray *MLNAttributionInfosFromAttributions( + mbgl::MapSnapshotter::Attributions attributions); -class MLNMapSnapshotterDelegateHost: public mbgl::MapSnapshotterObserver { +class MLNMapSnapshotterDelegateHost : public mbgl::MapSnapshotterObserver { public: - MLNMapSnapshotterDelegateHost(MLNMapSnapshotter *snapshotter_) : snapshotter(snapshotter_) {} + MLNMapSnapshotterDelegateHost(MLNMapSnapshotter *snapshotter_) : snapshotter(snapshotter_) {} - void onDidFailLoadingStyle(const std::string& errorMessage) { - MLNMapSnapshotter *strongSnapshotter = snapshotter; - if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotterDidFail:withError:)]) { - NSString *description = @(errorMessage.c_str()); - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: NSLocalizedStringWithDefaultValue(@"SNAPSHOT_LOAD_STYLE_FAILED_DESC", nil, nil, @"The snapshot failed because the style can’t be loaded.", @"User-friendly error description"), - NSLocalizedFailureReasonErrorKey: description, - }; - NSError *error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeLoadStyleFailed userInfo:userInfo]; - [strongSnapshotter.delegate mapSnapshotterDidFail:snapshotter withError:error]; - } + void onDidFailLoadingStyle(const std::string &errorMessage) { + MLNMapSnapshotter *strongSnapshotter = snapshotter; + if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotterDidFail: + withError:)]) { + NSString *description = @(errorMessage.c_str()); + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey : NSLocalizedStringWithDefaultValue( + @"SNAPSHOT_LOAD_STYLE_FAILED_DESC", nil, nil, + @"The snapshot failed because the style can’t be loaded.", + @"User-friendly error description"), + NSLocalizedFailureReasonErrorKey : description, + }; + NSError *error = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeLoadStyleFailed + userInfo:userInfo]; + [strongSnapshotter.delegate mapSnapshotterDidFail:snapshotter withError:error]; } + } - void onDidFinishLoadingStyle() { - MLNMapSnapshotter *strongSnapshotter = snapshotter; - if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFinishLoadingStyle:)]) { - [strongSnapshotter.delegate mapSnapshotter:snapshotter didFinishLoadingStyle:snapshotter.style]; - } + void onDidFinishLoadingStyle() { + MLNMapSnapshotter *strongSnapshotter = snapshotter; + if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter: + didFinishLoadingStyle:)]) { + [strongSnapshotter.delegate mapSnapshotter:snapshotter + didFinishLoadingStyle:snapshotter.style]; } + } - void onStyleImageMissing(const std::string& imageName) { - MLNMapSnapshotter *strongSnapshotter = snapshotter; - if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFailLoadingImageNamed:)]) { - [strongSnapshotter.delegate mapSnapshotter:snapshotter didFailLoadingImageNamed:@(imageName.c_str())]; - } + void onStyleImageMissing(const std::string &imageName) { + MLNMapSnapshotter *strongSnapshotter = snapshotter; + if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter: + didFailLoadingImageNamed:)]) { + [strongSnapshotter.delegate mapSnapshotter:snapshotter + didFailLoadingImageNamed:@(imageName.c_str())]; } + } private: - __weak MLNMapSnapshotter *snapshotter; + __weak MLNMapSnapshotter *snapshotter; }; -@interface MLNMapSnapshotOverlay() +@interface MLNMapSnapshotOverlay () @property (nonatomic, assign) CGFloat scale; -- (instancetype)initWithContext:(CGContextRef)context scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; +- (instancetype)initWithContext:(CGContextRef)context + scale:(CGFloat)scale + pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn + latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; @end @implementation MLNMapSnapshotOverlay { - mbgl::MapSnapshotter::PointForFn _pointForFn; - mbgl::MapSnapshotter::LatLngForFn _latLngForFn; + mbgl::MapSnapshotter::PointForFn _pointForFn; + mbgl::MapSnapshotter::LatLngForFn _latLngForFn; } -- (instancetype) initWithContext:(CGContextRef)context scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn { - self = [super init]; - if (self) { - _context = context; - _scale = scale; - _pointForFn = pointForFn; - _latLngForFn = latLngForFn; - } +- (instancetype)initWithContext:(CGContextRef)context + scale:(CGFloat)scale + pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn + latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn { + self = [super init]; + if (self) { + _context = context; + _scale = scale; + _pointForFn = pointForFn; + _latLngForFn = latLngForFn; + } - return self; + return self; } #if TARGET_OS_IPHONE -- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate -{ - mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); - return CGPointMake(sc.x, sc.y); +- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate { + mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); + return CGPointMake(sc.x, sc.y); } -- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point -{ - mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); - return MLNLocationCoordinate2DFromLatLng(latLng); +- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point { + mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); + return MLNLocationCoordinate2DFromLatLng(latLng); } #else -- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate -{ - mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); - CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale; - return NSMakePoint(sc.x, height - sc.y); +- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate { + mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); + CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context)) / self.scale; + return NSMakePoint(sc.x, height - sc.y); } -- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point -{ - CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale; - auto screenCoord = mbgl::ScreenCoordinate(point.x, height - point.y); - mbgl::LatLng latLng = _latLngForFn(screenCoord); - return MLNLocationCoordinate2DFromLatLng(latLng); +- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point { + CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context)) / self.scale; + auto screenCoord = mbgl::ScreenCoordinate(point.x, height - point.y); + mbgl::LatLng latLng = _latLngForFn(screenCoord); + return MLNLocationCoordinate2DFromLatLng(latLng); } #endif @@ -142,471 +164,525 @@ private: @implementation MLNMapSnapshotOptions -- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MLNMapCamera *)camera size:(CGSize)size -{ - MLNLogDebug(@"Initializing withStyleURL: %@ camera: %@ size: %@", styleURL, camera, MLNStringFromSize(size)); - self = [super init]; - if (self) - { - if ( !styleURL) - { - styleURL = [MLNStyle defaultStyleURL]; - } - _styleURL = styleURL; - _size = size; - _camera = camera; - _showsLogo = YES; - _showsAttribution = YES; -#if TARGET_OS_IPHONE - _scale = [UIScreen mainScreen].scale; -#else - _scale = [NSScreen mainScreen].backingScaleFactor; -#endif +- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL + camera:(MLNMapCamera *)camera + size:(CGSize)size { + MLNLogDebug(@"Initializing withStyleURL: %@ camera: %@ size: %@", styleURL, camera, + MLNStringFromSize(size)); + self = [super init]; + if (self) { + if (!styleURL) { + styleURL = [MLNStyle defaultStyleURL]; } - return self; + _styleURL = styleURL; + _size = size; + _camera = camera; + _showsLogo = YES; + _showsAttribution = YES; +#if TARGET_OS_IPHONE + _scale = [UIScreen mainScreen].scale; +#else + _scale = [NSScreen mainScreen].backingScaleFactor; +#endif + } + return self; } - (nonnull id)copyWithZone:(nullable NSZone *)zone { - __typeof__(self) copy = [[[self class] alloc] initWithStyleURL:_styleURL camera:_camera size:_size]; - copy.zoomLevel = _zoomLevel; - copy.coordinateBounds = _coordinateBounds; - copy.scale = _scale; - copy.showsLogo = _showsLogo; - copy.showsAttribution = _showsAttribution; - return copy; + __typeof__(self) copy = [[[self class] alloc] initWithStyleURL:_styleURL + camera:_camera + size:_size]; + copy.zoomLevel = _zoomLevel; + copy.coordinateBounds = _coordinateBounds; + copy.scale = _scale; + copy.showsLogo = _showsLogo; + copy.showsAttribution = _showsAttribution; + return copy; } @end -@interface MLNMapSnapshot() -- (instancetype)initWithImage:(nullable MLNImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; +@interface MLNMapSnapshot () +- (instancetype)initWithImage:(nullable MLNImage *)image + scale:(CGFloat)scale + pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn + latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; @property (nonatomic) CGFloat scale; @end @implementation MLNMapSnapshot { - mbgl::MapSnapshotter::PointForFn _pointForFn; - mbgl::MapSnapshotter::LatLngForFn _latLngForFn; + mbgl::MapSnapshotter::PointForFn _pointForFn; + mbgl::MapSnapshotter::LatLngForFn _latLngForFn; } -- (instancetype)initWithImage:(nullable MLNImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn -{ - self = [super init]; - if (self) { - _pointForFn = pointForFn; - _latLngForFn = latLngForFn; - _scale = scale; - _image = image; - } - return self; +- (instancetype)initWithImage:(nullable MLNImage *)image + scale:(CGFloat)scale + pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn + latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn { + self = [super init]; + if (self) { + _pointForFn = pointForFn; + _latLngForFn = latLngForFn; + _scale = scale; + _image = image; + } + return self; } #if TARGET_OS_IPHONE -- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate -{ - mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); - return CGPointMake(sc.x, sc.y); +- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate { + mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); + return CGPointMake(sc.x, sc.y); } -- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point -{ - mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); - return MLNLocationCoordinate2DFromLatLng(latLng); +- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point { + mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); + return MLNLocationCoordinate2DFromLatLng(latLng); } #else -- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate -{ - mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); - return NSMakePoint(sc.x, self.image.size.height - sc.y); +- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate { + mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); + return NSMakePoint(sc.x, self.image.size.height - sc.y); } -- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point -{ - auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y); - mbgl::LatLng latLng = _latLngForFn(screenCoord); - return MLNLocationCoordinate2DFromLatLng(latLng); +- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point { + auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y); + mbgl::LatLng latLng = _latLngForFn(screenCoord); + return MLNLocationCoordinate2DFromLatLng(latLng); } #endif @end -@interface MLNMapSnapshotter() +@interface MLNMapSnapshotter () @property (nonatomic) BOOL loading; @property (nonatomic) BOOL terminated; @end @implementation MLNMapSnapshotter { - std::unique_ptr _mbglMapSnapshotter; - std::unique_ptr _delegateHost; - NSMutableArray> *_annotationsToInstall; - NSMutableSet *_installedImages; + std::unique_ptr _mbglMapSnapshotter; + std::unique_ptr _delegateHost; + NSMutableArray> *_annotationsToInstall; + NSMutableSet *_installedImages; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self cancel]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self cancel]; } - (instancetype)init { - NSAssert(NO, @"Please use -[MLNMapSnapshotter initWithOptions:]"); - [super doesNotRecognizeSelector:_cmd]; - return nil; + NSAssert(NO, @"Please use -[MLNMapSnapshotter initWithOptions:]"); + [super doesNotRecognizeSelector:_cmd]; + return nil; } -- (instancetype)initWithOptions:(MLNMapSnapshotOptions *)options -{ - MLNLogDebug(@"Initializing withOptions: %@", options); - self = [super init]; - if (self) { - self.options = options; - _installedImages = [NSMutableSet set]; +- (instancetype)initWithOptions:(MLNMapSnapshotOptions *)options { + MLNLogDebug(@"Initializing withOptions: %@", options); + self = [super init]; + if (self) { + self.options = options; + _installedImages = [NSMutableSet set]; #if TARGET_OS_IPHONE - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:UIApplicationWillTerminateNotification + object:nil]; #else - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate:) + name:NSApplicationWillTerminateNotification + object:nil]; #endif - } - return self; + } + return self; } -- (void)applicationWillTerminate:(NSNotification *)notification -{ - [self cancel]; - self.terminated = YES; +- (void)applicationWillTerminate:(NSNotification *)notification { + [self cancel]; + self.terminated = YES; } -- (void)addAnnotation:(id)annotation -{ - MLNLogDebug(@"Adding annotation: %@", annotation); - if ( ! annotation) return; - return [self addAnnotations:@[annotation]]; +- (void)addAnnotation:(id)annotation { + MLNLogDebug(@"Adding annotation: %@", annotation); + if (!annotation) return; + return [self addAnnotations:@[ annotation ]]; } - (void)addAnnotations:(NSArray> *)annotations { - MLNLogDebug(@"Adding: %lu annotations", annotations.count); + MLNLogDebug(@"Adding: %lu annotations", annotations.count); - if (!_annotationsToInstall) { - _annotationsToInstall = [NSMutableArray array]; - } + if (!_annotationsToInstall) { + _annotationsToInstall = [NSMutableArray array]; + } - [_annotationsToInstall addObjectsFromArray:annotations]; + [_annotationsToInstall addObjectsFromArray:annotations]; } -- (void)installAnnotations:(NSArray> *)annotations -{ - MLNLogDebug(@"install: %lu annotations", annotations.count); - if ( ! annotations) return; +- (void)installAnnotations:(NSArray> *)annotations { + MLNLogDebug(@"install: %lu annotations", annotations.count); + if (!annotations) return; - for (id annotation in annotations) - { - MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation"); + for (id annotation in annotations) { + MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], + @"annotation should conform to MLNAnnotation"); - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; - if (!multiPoint.pointCount) { - continue; - } - _mbglMapSnapshotter->addAnnotation([multiPoint annotationObjectWithDelegate:self]); - } else if (! [annotation isKindOfClass:[MLNMultiPolyline class]] - && ![annotation isKindOfClass:[MLNMultiPolygon class]] - && ![annotation isKindOfClass:[MLNShapeCollection class]] - && ![annotation isKindOfClass:[MLNPointCollection class]]) { - NSString *symbolName; - NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; - MLNAnnotationImage *annotationImage; + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; + if (!multiPoint.pointCount) { + continue; + } + _mbglMapSnapshotter->addAnnotation([multiPoint annotationObjectWithDelegate:self]); + } else if (![annotation isKindOfClass:[MLNMultiPolyline class]] && + ![annotation isKindOfClass:[MLNMultiPolygon class]] && + ![annotation isKindOfClass:[MLNShapeCollection class]] && + ![annotation isKindOfClass:[MLNPointCollection class]]) { + NSString *symbolName; + NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; + MLNAnnotationImage *annotationImage; - if ([self.delegate respondsToSelector:@selector(mapSnapshotter:imageForAnnotation:)]) { - annotationImage = [self.delegate mapSnapshotter:self imageForAnnotation:annotation]; - } - if (!annotationImage) { - // doesn't support no icon point for snapshotter - continue; - } + if ([self.delegate respondsToSelector:@selector(mapSnapshotter:imageForAnnotation:)]) { + annotationImage = [self.delegate mapSnapshotter:self imageForAnnotation:annotation]; + } + if (!annotationImage) { + // doesn't support no icon point for snapshotter + continue; + } - symbolName = annotationImage.styleIconIdentifier; - if ( ! symbolName) - { - symbolName = [MLNSnapshotterAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = symbolName; - } + symbolName = annotationImage.styleIconIdentifier; + if (!symbolName) { + symbolName = [MLNSnapshotterAnnotationSpritePrefix + stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = symbolName; + } - [self installAnnotationImage:annotationImage]; + [self installAnnotationImage:annotationImage]; - _mbglMapSnapshotter->addAnnotation(mbgl::SymbolAnnotation { - MLNPointFromLocationCoordinate2D(annotation.coordinate), - symbolName.UTF8String - }); - } + _mbglMapSnapshotter->addAnnotation(mbgl::SymbolAnnotation{ + MLNPointFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String}); } + } } - (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage { - NSString *iconIdentifier = annotationImage.styleIconIdentifier; - if (![_installedImages containsObject:iconIdentifier]) { - _mbglMapSnapshotter->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); - } + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + if (![_installedImages containsObject:iconIdentifier]) { + _mbglMapSnapshotter->addAnnotationImage( + [annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); + } } -- (void)startWithCompletionHandler:(MLNMapSnapshotCompletionHandler)completion -{ - MLNLogDebug(@"Starting withCompletionHandler: %@", completion); - [self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; +- (void)startWithCompletionHandler:(MLNMapSnapshotCompletionHandler)completion { + MLNLogDebug(@"Starting withCompletionHandler: %@", completion); + [self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; } -- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MLNMapSnapshotCompletionHandler)completionHandler { - [self startWithQueue:queue overlayHandler:nil completionHandler:completionHandler]; +- (void)startWithQueue:(dispatch_queue_t)queue + completionHandler:(MLNMapSnapshotCompletionHandler)completionHandler { + [self startWithQueue:queue overlayHandler:nil completionHandler:completionHandler]; } -- (void)startWithOverlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler completionHandler:(MLNMapSnapshotCompletionHandler)completion { - [self startWithQueue:dispatch_get_main_queue() overlayHandler:overlayHandler completionHandler:completion]; +- (void)startWithOverlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler + completionHandler:(MLNMapSnapshotCompletionHandler)completion { + [self startWithQueue:dispatch_get_main_queue() + overlayHandler:overlayHandler + completionHandler:completion]; } -- (void)startWithQueue:(dispatch_queue_t)queue overlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler completionHandler:(MLNMapSnapshotCompletionHandler)completion -{ - if (!completion) { - return; - } +- (void)startWithQueue:(dispatch_queue_t)queue + overlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler + completionHandler:(MLNMapSnapshotCompletionHandler)completion { + if (!completion) { + return; + } - // Ensure that offline storage has been initialized on the main thread, as MLNMapView and MLNOfflineStorage do when used directly. - // https://github.com/mapbox/mapbox-gl-native-ios/issues/227 - if ([NSThread.currentThread isMainThread]) { - (void)[MLNOfflineStorage sharedOfflineStorage]; + // Ensure that offline storage has been initialized on the main thread, as MLNMapView and + // MLNOfflineStorage do when used directly. + // https://github.com/mapbox/mapbox-gl-native-ios/issues/227 + if ([NSThread.currentThread isMainThread]) { + (void)[MLNOfflineStorage sharedOfflineStorage]; + } else { + [NSException raise:NSInvalidArgumentException + format:@"-[MLNMapSnapshotter startWithQueue:completionHandler:] must be called " + @"from the main thread, not %@.", + NSThread.currentThread]; + } + + if (self.loading) { + // Consider replacing this exception with an error passed to the completion block. + [NSException raise:NSInternalInconsistencyException + format:@"Already started this snapshotter."]; + } + self.loading = YES; + + if (self.terminated) { + [NSException raise:NSInternalInconsistencyException + format:@"Starting a snapshotter after application termination is not supported."]; + } + + MLNMapSnapshotOptions *options = [self.options copy]; + [self configureWithOptions:options]; + MLNLogDebug(@"Starting with options: %@", self.options); + + // Temporarily capture the snapshotter until the completion handler finishes executing, to keep + // standalone local usage of the snapshotter from becoming a no-op. POSTCONDITION: Only refer to + // this variable in the final result queue. POSTCONDITION: It is important to nil out this + // variable at some point in the future to avoid a leak. In cases where the completion handler + // gets called, the variable should be nilled out explicitly. If -cancel is called, mbgl releases + // the snapshot block below, causing the only remaining references to the snapshotter to go out of + // scope. + __block MLNMapSnapshotter *strongSelf = self; + _mbglMapSnapshotter->snapshot(^(std::exception_ptr mbglError, mbgl::PremultipliedImage image, + mbgl::MapSnapshotter::Attributions attributions, + mbgl::MapSnapshotter::PointForFn pointForFn, + mbgl::MapSnapshotter::LatLngForFn latLngForFn) { + if (mbglError) { + NSString *description = @(mbgl::util::toString(mbglError).c_str()); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey : description}; + NSError *error = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeSnapshotFailed + userInfo:userInfo]; + + // Dispatch to result queue + dispatch_async(queue, ^{ + strongSelf.loading = NO; + completion(nil, error); + strongSelf = nil; + }); } else { - [NSException raise:NSInvalidArgumentException - format:@"-[MLNMapSnapshotter startWithQueue:completionHandler:] must be called from the main thread, not %@.", NSThread.currentThread]; - } - - if (self.loading) { - // Consider replacing this exception with an error passed to the completion block. - [NSException raise:NSInternalInconsistencyException - format:@"Already started this snapshotter."]; - } - self.loading = YES; - - if (self.terminated) { - [NSException raise:NSInternalInconsistencyException - format:@"Starting a snapshotter after application termination is not supported."]; - } - - MLNMapSnapshotOptions *options = [self.options copy]; - [self configureWithOptions:options]; - MLNLogDebug(@"Starting with options: %@", self.options); - - // Temporarily capture the snapshotter until the completion handler finishes executing, to keep standalone local usage of the snapshotter from becoming a no-op. - // POSTCONDITION: Only refer to this variable in the final result queue. - // POSTCONDITION: It is important to nil out this variable at some point in the future to avoid a leak. In cases where the completion handler gets called, the variable should be nilled out explicitly. If -cancel is called, mbgl releases the snapshot block below, causing the only remaining references to the snapshotter to go out of scope. - __block MLNMapSnapshotter *strongSelf = self; - _mbglMapSnapshotter->snapshot(^(std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn) { - if (mbglError) { - NSString *description = @(mbgl::util::toString(mbglError).c_str()); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; - NSError *error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeSnapshotFailed userInfo:userInfo]; - - // Dispatch to result queue - dispatch_async(queue, ^{ - strongSelf.loading = NO; - completion(nil, error); - strongSelf = nil; - }); - } else { #if TARGET_OS_IPHONE - MLNImage *mglImage = [[MLNImage alloc] initWithMLNPremultipliedImage:std::move(image) scale:options.scale]; + MLNImage *mglImage = [[MLNImage alloc] initWithMLNPremultipliedImage:std::move(image) + scale:options.scale]; #else MLNImage *mglImage = [[MLNImage alloc] initWithMLNPremultipliedImage:std::move(image)]; mglImage.size = NSMakeSize(mglImage.size.width / options.scale, mglImage.size.height / options.scale); #endif - // Process image watermark in a work queue - dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + // Process image watermark in a work queue + dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(workQueue, ^{ - // Call a function that cannot accidentally capture self. - NSError *error; - MLNMapSnapshot *snapshot = MLNSnapshotWithDecoratedImage(mglImage, options, attributions, pointForFn, latLngForFn, overlayHandler, &error); + dispatch_async(workQueue, ^{ + // Call a function that cannot accidentally capture self. + NSError *error; + MLNMapSnapshot *snapshot = MLNSnapshotWithDecoratedImage( + mglImage, options, attributions, pointForFn, latLngForFn, overlayHandler, &error); - // Dispatch result to result queue - dispatch_async(queue, ^{ - strongSelf.loading = NO; - completion(snapshot, error); - strongSelf = nil; - }); - }); - } - }); + // Dispatch result to result queue + dispatch_async(queue, ^{ + strongSelf.loading = NO; + completion(snapshot, error); + strongSelf = nil; + }); + }); + } + }); } -MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, MLNMapSnapshotOptions *options, void (^overlayHandler)()) { - - NSArray *attributionInfo = MLNAttributionInfosFromAttributions(attributions); +MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, + MLNMapSnapshotOptions *options, void (^overlayHandler)()) { + NSArray *attributionInfo = + MLNAttributionInfosFromAttributions(attributions); #if TARGET_OS_IPHONE - MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; - for (NSUInteger styleValue = MLNAttributionInfoStyleLong; styleValue >= MLNAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; - CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo]; - if (attributionSize.width <= mglImage.size.width) { - break; - } + MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; + for (NSUInteger styleValue = MLNAttributionInfoStyleLong; + styleValue >= MLNAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; + CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle + sourceAttributionStyle:attributionInfoStyle + attributionInfo:attributionInfo]; + if (attributionSize.width <= mglImage.size.width) { + break; } + } - UIImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo]; + UIImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = + [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle + attributionInfo:attributionInfo]; - CGRect logoImageRect = CGRectMake(MLNLogoImagePosition.x, mglImage.size.height - (MLNLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - if (!logoImage) { - CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; - logoImageRect = CGRectMake(0, mglImage.size.height - (MLNLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - } + CGRect logoImageRect = + CGRectMake(MLNLogoImagePosition.x, + mglImage.size.height - (MLNLogoImagePosition.y + logoImage.size.height), + logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = + CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - + (attributionBackgroundSize.height / 2) + 1); + if (!logoImage) { + CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; + logoImageRect = + CGRectMake(0, mglImage.size.height - (MLNLogoImagePosition.y + defaultLogoSize.height), 0, + defaultLogoSize.height); + attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - + (attributionBackgroundSize.height / 2) + 1); + } - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - attributionBackgroundFrame.origin.y - 1); + CGRect attributionBackgroundFrame = + CGRectMake(attributionOrigin.x, attributionOrigin.y, attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, + attributionBackgroundFrame.origin.y - 1); - CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, - attributionBackgroundFrame.origin.y * mglImage.scale, - attributionBackgroundSize.width * mglImage.scale, - attributionBackgroundSize.height * mglImage.scale); + CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, + attributionBackgroundFrame.origin.y * mglImage.scale, + attributionBackgroundSize.width * mglImage.scale, + attributionBackgroundSize.height * mglImage.scale); - UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, options.scale); + UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, options.scale); - [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; + [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; - overlayHandler(); - CGContextRef currentContext = UIGraphicsGetCurrentContext(); + overlayHandler(); + CGContextRef currentContext = UIGraphicsGetCurrentContext(); - if (!currentContext) { - // If the current context has been corrupted by the user, - // return nil so we can generate an error later. - return nil; - } + if (!currentContext) { + // If the current context has been corrupted by the user, + // return nil so we can generate an error later. + return nil; + } - if (options.showsLogo && logoImage) { - [logoImage drawInRect:logoImageRect]; - } + if (options.showsLogo && logoImage) { + [logoImage drawInRect:logoImageRect]; + } - if (options.showsAttribution) { - UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); - CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); - UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; - CGImageRelease(attributionImageRef); + if (options.showsAttribution) { + UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); + CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); + UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; + CGImageRelease(attributionImageRef); - CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; + CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; - UIImage *blurredAttributionBackground = [MLNMapSnapshotter blurredAttributionBackground:ciAttributionImage]; + UIImage *blurredAttributionBackground = + [MLNMapSnapshotter blurredAttributionBackground:ciAttributionImage]; - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [MLNMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition attributionInfo:attributionInfo]; - } + [MLNMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle + origin:attributionTextPosition + attributionInfo:attributionInfo]; + } - UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIGraphicsEndImageContext(); - return compositedImage; + return compositedImage; #else - NSRect targetFrame = { .origin = NSZeroPoint, .size = options.size }; + NSRect targetFrame = {.origin = NSZeroPoint, .size = options.size}; - MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; - for (NSUInteger styleValue = MLNAttributionInfoStyleLong; styleValue >= MLNAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; - CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo]; - if (attributionSize.width <= mglImage.size.width) { - break; - } + MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; + for (NSUInteger styleValue = MLNAttributionInfoStyleLong; + styleValue >= MLNAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; + CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle + sourceAttributionStyle:attributionInfoStyle + attributionInfo:attributionInfo]; + if (attributionSize.width <= mglImage.size.width) { + break; } + } - NSImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo]; - NSImage *sourceImage = mglImage; + NSImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = + [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle + attributionInfo:attributionInfo]; + NSImage *sourceImage = mglImage; - CGRect logoImageRect = CGRectMake(MLNLogoImagePosition.x, MLNLogoImagePosition.y, logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, - MLNLogoImagePosition.y + 1); - if (!logoImage) { - CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; - logoImageRect = CGRectMake(0, MLNLogoImagePosition.y, 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, attributionOrigin.y); - } + CGRect logoImageRect = CGRectMake(MLNLogoImagePosition.x, MLNLogoImagePosition.y, + logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = CGPointMake( + targetFrame.size.width - 10 - attributionBackgroundSize.width, MLNLogoImagePosition.y + 1); + if (!logoImage) { + CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; + logoImageRect = CGRectMake(0, MLNLogoImagePosition.y, 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, attributionOrigin.y); + } - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); + CGRect attributionBackgroundFrame = + CGRectMake(attributionOrigin.x, attributionOrigin.y, attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = + CGPointMake(attributionBackgroundFrame.origin.x + 10, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - + (attributionBackgroundSize.height / 2)); + NSImage *compositedImage = nil; + NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame + context:nil + hints:nil]; + compositedImage = [[NSImage alloc] initWithSize:targetFrame.size]; - NSImage *compositedImage = nil; - NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame - context:nil - hints:nil]; - compositedImage = [[NSImage alloc] initWithSize:targetFrame.size]; + [compositedImage lockFocus]; - [compositedImage lockFocus]; + [sourceImageRep drawInRect:targetFrame]; - [sourceImageRep drawInRect: targetFrame]; + overlayHandler(); - overlayHandler(); + NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; + if (!currentContext) { + // If the current context has been corrupted by the user, + // return nil so we can generate an error later. + return nil; + } - NSGraphicsContext *currentContext = [NSGraphicsContext currentContext]; - if (!currentContext) { - // If the current context has been corrupted by the user, - // return nil so we can generate an error later. - return nil; - } + if (options.showsLogo) { + [logoImage drawInRect:logoImageRect]; + } - if (options.showsLogo) { - [logoImage drawInRect:logoImageRect]; - } + if (options.showsAttribution) { + NSBitmapImageRep *attributionBackground = + [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; - if (options.showsAttribution) { - NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; + CIImage *attributionBackgroundImage = + [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; - CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; + NSImage *blurredAttributionBackground = + [MLNMapSnapshotter blurredAttributionBackground:attributionBackgroundImage]; - NSImage *blurredAttributionBackground = [MLNMapSnapshotter blurredAttributionBackground:attributionBackgroundImage]; + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + [MLNMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle + origin:attributionTextPosition + attributionInfo:attributionInfo]; + } - [MLNMapSnapshotter drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition attributionInfo:attributionInfo]; - } + [compositedImage unlockFocus]; - [compositedImage unlockFocus]; - - return compositedImage; + return compositedImage; #endif } -MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError) { - MLNImage *compositedImage = MLNAttributedSnapshot(attributions, mglImage, options, ^{ - if (!overlayHandler) { - return; - } +MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, + mbgl::MapSnapshotter::Attributions attributions, + mbgl::MapSnapshotter::PointForFn pointForFn, + mbgl::MapSnapshotter::LatLngForFn latLngForFn, + MLNMapSnapshotOverlayHandler overlayHandler, + NSError *_Nullable *outError) { + MLNImage *compositedImage = MLNAttributedSnapshot(attributions, mglImage, options, ^{ + if (!overlayHandler) { + return; + } #if TARGET_OS_IPHONE - CGContextRef context = UIGraphicsGetCurrentContext(); - if (!context) { - return; - } - MLNMapSnapshotOverlay *snapshotOverlay = [[MLNMapSnapshotOverlay alloc] initWithContext:context - scale:options.scale - pointForFn:pointForFn - latLngForFn:latLngForFn]; - CGContextSaveGState(context); - overlayHandler(snapshotOverlay); - CGContextRestoreGState(context); + CGContextRef context = UIGraphicsGetCurrentContext(); + if (!context) { + return; + } + MLNMapSnapshotOverlay *snapshotOverlay = + [[MLNMapSnapshotOverlay alloc] initWithContext:context + scale:options.scale + pointForFn:pointForFn + latLngForFn:latLngForFn]; + CGContextSaveGState(context); + overlayHandler(snapshotOverlay); + CGContextRestoreGState(context); #else NSGraphicsContext *context = [NSGraphicsContext currentContext]; if (!context) { @@ -620,272 +696,280 @@ MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshot overlayHandler(snapshotOverlay); [context restoreGraphicsState]; #endif - }); + }); - if (compositedImage) { - return [[MLNMapSnapshot alloc] initWithImage:compositedImage - scale:options.scale - pointForFn:pointForFn - latLngForFn:latLngForFn]; - } else { - if (outError) { - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Failed to generate composited snapshot."}; - *outError = [NSError errorWithDomain:MLNErrorDomain - code:MLNErrorCodeSnapshotFailed - userInfo:userInfo]; - } - return nil; + if (compositedImage) { + return [[MLNMapSnapshot alloc] initWithImage:compositedImage + scale:options.scale + pointForFn:pointForFn + latLngForFn:latLngForFn]; + } else { + if (outError) { + NSDictionary *userInfo = + @{NSLocalizedDescriptionKey : @"Failed to generate composited snapshot."}; + *outError = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeSnapshotFailed + userInfo:userInfo]; } + return nil; + } } -NSArray *MLNAttributionInfosFromAttributions(mbgl::MapSnapshotter::Attributions attributions) { - NSMutableArray *infos = [NSMutableArray array]; +NSArray *MLNAttributionInfosFromAttributions( + mbgl::MapSnapshotter::Attributions attributions) { + NSMutableArray *infos = [NSMutableArray array]; #if TARGET_OS_IPHONE - CGFloat fontSize = [UIFont smallSystemFontSize]; - UIColor *attributeFontColor = [UIColor blackColor]; + CGFloat fontSize = [UIFont smallSystemFontSize]; + UIColor *attributeFontColor = [UIColor blackColor]; #else - CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSControlSizeMini]; - NSColor *attributeFontColor = [NSColor blackColor]; + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSControlSizeMini]; + NSColor *attributeFontColor = [NSColor blackColor]; #endif - for (auto attribution : attributions) { - NSString *attributionHTMLString = @(attribution.c_str()); - NSArray *tileSetInfos = [MLNAttributionInfo attributionInfosFromHTMLString:attributionHTMLString - fontSize:fontSize - linkColor:attributeFontColor]; - [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; - } - return infos; + for (auto attribution : attributions) { + NSString *attributionHTMLString = @(attribution.c_str()); + NSArray *tileSetInfos = [MLNAttributionInfo attributionInfosFromHTMLString:attributionHTMLString + fontSize:fontSize + linkColor:attributeFontColor]; + [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; + } + return infos; } -+ (void)drawAttributionTextWithStyle:(MLNAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin attributionInfo:(NSArray*)attributionInfo -{ - for (MLNAttributionInfo *info in attributionInfo) { - if (info.isFeedbackLink) { - continue; - } - NSAttributedString *attribution = [info titleWithStyle:attributionInfoStyle]; - [attribution drawAtPoint:origin]; - - origin.x += [attribution size].width + 10; ++ (void)drawAttributionTextWithStyle:(MLNAttributionInfoStyle)attributionInfoStyle + origin:(CGPoint)origin + attributionInfo:(NSArray *)attributionInfo { + for (MLNAttributionInfo *info in attributionInfo) { + if (info.isFeedbackLink) { + continue; } + NSAttributedString *attribution = [info titleWithStyle:attributionInfoStyle]; + [attribution drawAtPoint:origin]; + + origin.x += [attribution size].width + 10; + } } -+ (MLNImage *)blurredAttributionBackground:(CIImage *)backgroundImage -{ - CGAffineTransform transform = CGAffineTransformIdentity; - CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"]; - [clamp setValue:backgroundImage forKey:kCIInputImageKey]; - [clamp setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"]; ++ (MLNImage *)blurredAttributionBackground:(CIImage *)backgroundImage { + CGAffineTransform transform = CGAffineTransformIdentity; + CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"]; + [clamp setValue:backgroundImage forKey:kCIInputImageKey]; + [clamp setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] + forKey:@"inputTransform"]; - CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; - [attributionBlurFilter setValue:[clamp outputImage] forKey:kCIInputImageKey]; - [attributionBlurFilter setValue:@10 forKey:kCIInputRadiusKey]; + CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; + [attributionBlurFilter setValue:[clamp outputImage] forKey:kCIInputImageKey]; + [attributionBlurFilter setValue:@10 forKey:kCIInputRadiusKey]; - CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; - [attributionColorFilter setValue:[attributionBlurFilter outputImage] forKey:kCIInputImageKey]; - [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; + CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; + [attributionColorFilter setValue:[attributionBlurFilter outputImage] forKey:kCIInputImageKey]; + [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; - CIImage *blurredImage = attributionColorFilter.outputImage; + CIImage *blurredImage = attributionColorFilter.outputImage; - CIContext *ctx = [CIContext contextWithOptions:nil]; - CGImageRef cgimg = [ctx createCGImage:blurredImage fromRect:[backgroundImage extent]]; - MLNImage *image; + CIContext *ctx = [CIContext contextWithOptions:nil]; + CGImageRef cgimg = [ctx createCGImage:blurredImage fromRect:[backgroundImage extent]]; + MLNImage *image; #if TARGET_OS_IPHONE - image = [UIImage imageWithCGImage:cgimg]; + image = [UIImage imageWithCGImage:cgimg]; #else - image = [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size]; + image = [[NSImage alloc] initWithCGImage:cgimg size:[backgroundImage extent].size]; #endif - CGImageRelease(cgimg); - return image; + CGImageRelease(cgimg); + return image; } -+ (MLNImage *)logoImageWithStyle:(MLNAttributionInfoStyle)style -{ - MLNImage *logoImage; - switch (style) { - case MLNAttributionInfoStyleLong: - logoImage = [MLNMapSnapshotter maplibreLongStyleLogo]; - break; - case MLNAttributionInfoStyleMedium: ++ (MLNImage *)logoImageWithStyle:(MLNAttributionInfoStyle)style { + MLNImage *logoImage; + switch (style) { + case MLNAttributionInfoStyleLong: + logoImage = [MLNMapSnapshotter maplibreLongStyleLogo]; + break; + case MLNAttributionInfoStyleMedium: #if TARGET_OS_IPHONE - logoImage = [UIImage imageNamed:@"maplibre-logo-icon" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + logoImage = [UIImage imageNamed:@"maplibre-logo-icon" + inBundle:[NSBundle mgl_frameworkBundle] + compatibleWithTraitCollection:nil]; #else - logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" ofType:@"pdf"]]; + logoImage = [[NSImage alloc] + initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox_helmet" + ofType:@"pdf"]]; #endif - break; - case MLNAttributionInfoStyleShort: - logoImage = nil; - break; - } - return logoImage; + break; + case MLNAttributionInfoStyleShort: + logoImage = nil; + break; + } + return logoImage; } -+ (MLNImage *)maplibreLongStyleLogo -{ - MLNImage *logoImage; ++ (MLNImage *)maplibreLongStyleLogo { + MLNImage *logoImage; #if TARGET_OS_IPHONE - logoImage =[UIImage imageNamed:@"maplibre-logo-stroke-gray" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + logoImage = [UIImage imageNamed:@"maplibre-logo-stroke-gray" + inBundle:[NSBundle mgl_frameworkBundle] + compatibleWithTraitCollection:nil]; #else - logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; + logoImage = [[NSImage alloc] + initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" + ofType:@"pdf"]]; #endif - return logoImage; + return logoImage; } -+ (CGSize)attributionSizeWithLogoStyle:(MLNAttributionInfoStyle)logoStyle sourceAttributionStyle:(MLNAttributionInfoStyle)attributionStyle attributionInfo:(NSArray*)attributionInfo -{ - MLNImage *logoImage = [self logoImageWithStyle:logoStyle]; ++ (CGSize)attributionSizeWithLogoStyle:(MLNAttributionInfoStyle)logoStyle + sourceAttributionStyle:(MLNAttributionInfoStyle)attributionStyle + attributionInfo:(NSArray *)attributionInfo { + MLNImage *logoImage = [self logoImageWithStyle:logoStyle]; - CGSize attributionBackgroundSize = [MLNMapSnapshotter attributionTextSizeWithStyle:attributionStyle attributionInfo:attributionInfo]; + CGSize attributionBackgroundSize = + [MLNMapSnapshotter attributionTextSizeWithStyle:attributionStyle + attributionInfo:attributionInfo]; - CGSize attributionSize = CGSizeZero; + CGSize attributionSize = CGSizeZero; - if (logoImage) { - attributionSize.width = MLNLogoImagePosition.x + logoImage.size.width + 10; - } - attributionSize.width = attributionSize.width + 10 + attributionBackgroundSize.width + 10; - attributionSize.height = MAX(logoImage.size.height, attributionBackgroundSize.height); + if (logoImage) { + attributionSize.width = MLNLogoImagePosition.x + logoImage.size.width + 10; + } + attributionSize.width = attributionSize.width + 10 + attributionBackgroundSize.width + 10; + attributionSize.height = MAX(logoImage.size.height, attributionBackgroundSize.height); - return attributionSize; + return attributionSize; } -+ (CGSize)attributionTextSizeWithStyle:(MLNAttributionInfoStyle)attributionStyle attributionInfo:(NSArray*)attributionInfo -{ - CGSize attributionBackgroundSize = CGSizeMake(10, 0); - for (MLNAttributionInfo *info in attributionInfo) { - if (info.isFeedbackLink) { - continue; - } - CGSize attributionSize = [info titleWithStyle:attributionStyle].size; - attributionBackgroundSize.width += attributionSize.width + 10; - attributionBackgroundSize.height = MAX(attributionSize.height, attributionBackgroundSize.height); ++ (CGSize)attributionTextSizeWithStyle:(MLNAttributionInfoStyle)attributionStyle + attributionInfo:(NSArray *)attributionInfo { + CGSize attributionBackgroundSize = CGSizeMake(10, 0); + for (MLNAttributionInfo *info in attributionInfo) { + if (info.isFeedbackLink) { + continue; } + CGSize attributionSize = [info titleWithStyle:attributionStyle].size; + attributionBackgroundSize.width += attributionSize.width + 10; + attributionBackgroundSize.height = + MAX(attributionSize.height, attributionBackgroundSize.height); + } - return attributionBackgroundSize; + return attributionBackgroundSize; } -- (void)cancel -{ - MLNLogInfo(@"Cancelling snapshotter."); +- (void)cancel { + MLNLogInfo(@"Cancelling snapshotter."); - if (_mbglMapSnapshotter) { - _mbglMapSnapshotter->cancel(); - } - _mbglMapSnapshotter.reset(); - _delegateHost.reset(); + if (_mbglMapSnapshotter) { + _mbglMapSnapshotter->cancel(); + } + _mbglMapSnapshotter.reset(); + _delegateHost.reset(); } - (void)configureWithOptions:(MLNMapSnapshotOptions *)options { - auto mbglFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglFileSource]; + auto mbglFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglFileSource]; - // Size; taking into account the minimum texture size for OpenGL ES - // For non retina screens the ratio is 1:1 MLNSnapshotterMinimumPixelSize - mbgl::Size size = { - static_cast(MAX(options.size.width, MLNSnapshotterMinimumPixelSize)), - static_cast(MAX(options.size.height, MLNSnapshotterMinimumPixelSize)) - }; + // Size; taking into account the minimum texture size for OpenGL ES + // For non retina screens the ratio is 1:1 MLNSnapshotterMinimumPixelSize + mbgl::Size size = { + static_cast(MAX(options.size.width, MLNSnapshotterMinimumPixelSize)), + static_cast(MAX(options.size.height, MLNSnapshotterMinimumPixelSize))}; - float pixelRatio = MAX(options.scale, 1); + float pixelRatio = MAX(options.scale, 1); - // App-global configuration - MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; + // App-global configuration + MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; - auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - mbgl::ResourceOptions resourceOptions; - resourceOptions - .withTileServerOptions(*tileServerOptions) - .withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) - .withAssetPath(NSBundle.mainBundle.resourceURL.path.UTF8String); - mbgl::ClientOptions clientOptions; + auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withTileServerOptions(*tileServerOptions) + .withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) + .withAssetPath(NSBundle.mainBundle.resourceURL.path.UTF8String); + mbgl::ClientOptions clientOptions; - auto apiKey = [[MLNSettings sharedSettings] apiKey]; - if (apiKey) { - resourceOptions.withApiKey([apiKey UTF8String]); - } + auto apiKey = [[MLNSettings sharedSettings] apiKey]; + if (apiKey) { + resourceOptions.withApiKey([apiKey UTF8String]); + } - // Create the snapshotter - auto localFontFamilyName = config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr; - _delegateHost = std::make_unique(self); - _mbglMapSnapshotter = std::make_unique( - size, pixelRatio, resourceOptions, clientOptions, *_delegateHost, localFontFamilyName); + // Create the snapshotter + auto localFontFamilyName = + config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr; + _delegateHost = std::make_unique(self); + _mbglMapSnapshotter = std::make_unique( + size, pixelRatio, resourceOptions, clientOptions, *_delegateHost, localFontFamilyName); - _mbglMapSnapshotter->setStyleURL(std::string(options.styleURL.absoluteString.UTF8String)); + _mbglMapSnapshotter->setStyleURL(std::string(options.styleURL.absoluteString.UTF8String)); - // Camera options - mbgl::CameraOptions cameraOptions; - if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { - cameraOptions.center = MLNLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); - } - if (options.camera.heading >= 0) { - cameraOptions.bearing = MAX(0, options.camera.heading); - } - if (options.zoomLevel >= 0) { - cameraOptions.zoom = MAX(0, options.zoomLevel); - } - if (options.camera.pitch >= 0) { - cameraOptions.pitch = MAX(0, options.camera.pitch); - } - if (options.camera.roll != 0) { - cameraOptions.roll = options.camera.roll; - } - if (cameraOptions != mbgl::CameraOptions()) { - _mbglMapSnapshotter->setCameraOptions(cameraOptions); - } + // Camera options + mbgl::CameraOptions cameraOptions; + if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { + cameraOptions.center = MLNLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); + } + if (options.camera.heading >= 0) { + cameraOptions.bearing = MAX(0, options.camera.heading); + } + if (options.zoomLevel >= 0) { + cameraOptions.zoom = MAX(0, options.zoomLevel); + } + if (options.camera.pitch >= 0) { + cameraOptions.pitch = MAX(0, options.camera.pitch); + } + if (options.camera.roll != 0) { + cameraOptions.roll = options.camera.roll; + } + if (cameraOptions != mbgl::CameraOptions()) { + _mbglMapSnapshotter->setCameraOptions(cameraOptions); + } - // Region - if (!MLNCoordinateBoundsIsEmpty(options.coordinateBounds)) { - _mbglMapSnapshotter->setRegion(MLNLatLngBoundsFromCoordinateBounds(options.coordinateBounds)); - } + // Region + if (!MLNCoordinateBoundsIsEmpty(options.coordinateBounds)) { + _mbglMapSnapshotter->setRegion(MLNLatLngBoundsFromCoordinateBounds(options.coordinateBounds)); + } - // Annotations - if (_annotationsToInstall) { - [self installAnnotations:_annotationsToInstall]; - } + // Annotations + if (_annotationsToInstall) { + [self installAnnotations:_annotationsToInstall]; + } } - (MLNStyle *)style { - if (!_mbglMapSnapshotter) { - return nil; - } - return [[MLNStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self]; + if (!_mbglMapSnapshotter) { + return nil; + } + return [[MLNStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self]; } -- (double)alphaForShapeAnnotation:(MLNShape *)annotation -{ - if ([self.delegate respondsToSelector:@selector(mapSnapshotter:alphaForShapeAnnotation:)]) { - return [self.delegate mapSnapshotter:self alphaForShapeAnnotation:annotation]; - } - return 1.0; +- (double)alphaForShapeAnnotation:(MLNShape *)annotation { + if ([self.delegate respondsToSelector:@selector(mapSnapshotter:alphaForShapeAnnotation:)]) { + return [self.delegate mapSnapshotter:self alphaForShapeAnnotation:annotation]; + } + return 1.0; } -- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation -{ - if ([self.delegate respondsToSelector:@selector(mapSnapshotter:strokeColorForShapeAnnotation:)]) { - MLNColor *color = [self.delegate mapSnapshotter:self strokeColorForShapeAnnotation:annotation]; - return color.mgl_color; - } +- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation { + if ([self.delegate respondsToSelector:@selector(mapSnapshotter:strokeColorForShapeAnnotation:)]) { + MLNColor *color = [self.delegate mapSnapshotter:self strokeColorForShapeAnnotation:annotation]; + return color.mgl_color; + } - return mbgl::Color::white(); + return mbgl::Color::white(); } -- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation -{ - if ([self.delegate respondsToSelector:@selector(mapSnapshotter:fillColorForPolygonAnnotation:)]){ - MLNColor *color = [self.delegate mapSnapshotter:self fillColorForPolygonAnnotation:annotation]; - return color.mgl_color; - } - return mbgl::Color::white(); +- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation { + if ([self.delegate respondsToSelector:@selector(mapSnapshotter:fillColorForPolygonAnnotation:)]) { + MLNColor *color = [self.delegate mapSnapshotter:self fillColorForPolygonAnnotation:annotation]; + return color.mgl_color; + } + return mbgl::Color::white(); } -- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation -{ - if ([self.delegate respondsToSelector:@selector(mapSnapshotter:lineWidthForPolylineAnnotation:)]) { - return [self.delegate mapSnapshotter:self lineWidthForPolylineAnnotation:annotation]; - } +- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation { + if ([self.delegate respondsToSelector:@selector(mapSnapshotter: + lineWidthForPolylineAnnotation:)]) { + return [self.delegate mapSnapshotter:self lineWidthForPolylineAnnotation:annotation]; + } - return 3.0; + return 3.0; } @end diff --git a/platform/darwin/src/MLNMultiPoint.mm b/platform/darwin/src/MLNMultiPoint.mm index d5deb4ba9..0f89e003e 100644 --- a/platform/darwin/src/MLNMultiPoint.mm +++ b/platform/darwin/src/MLNMultiPoint.mm @@ -1,208 +1,201 @@ -#import "MLNMultiPoint_Private.h" #import "MLNGeometry_Private.h" -#import "MLNShape_Private.h" -#import "NSCoder+MLNAdditions.h" -#import "MLNTypes.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNMultiPoint_Private.h" +#import "MLNShape_Private.h" +#import "MLNTypes.h" +#import "NSCoder+MLNAdditions.h" -@implementation MLNMultiPoint -{ - std::optional _bounds; - std::vector _coordinates; +@implementation MLNMultiPoint { + std::optional _bounds; + std::vector _coordinates; } -- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count -{ - MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long) count); - self = [super init]; +- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { + MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count); + self = [super init]; - if (self) - { - if (!count) { - [NSException raise:NSInvalidArgumentException - format:@"A multipoint must have at least one vertex."]; - } - _coordinates = { coords, coords + count }; + if (self) { + if (!count) { + [NSException raise:NSInvalidArgumentException + format:@"A multipoint must have at least one vertex."]; } + _coordinates = {coords, coords + count}; + } - return self; + return self; } + (BOOL)supportsSecureCoding { - return YES; + return YES; } -- (instancetype)initWithCoder:(NSCoder *)decoder -{ - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:decoder]) { - _coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"]; - } - return self; +- (instancetype)initWithCoder:(NSCoder *)decoder { + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:decoder]) { + _coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"]; + } + return self; } -- (void)encodeWithCoder:(NSCoder *)coder -{ - [super encodeWithCoder:coder]; - [coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"]; +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"]; } -- (BOOL)isEqual:(id)other -{ - if (self == other) return YES; - if (![other isKindOfClass:[MLNMultiPoint class]]) return NO; +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MLNMultiPoint class]]) return NO; - MLNMultiPoint *otherMultipoint = other; - return ([super isEqual:otherMultipoint] - && _coordinates == otherMultipoint->_coordinates); + MLNMultiPoint *otherMultipoint = other; + return ([super isEqual:otherMultipoint] && _coordinates == otherMultipoint->_coordinates); } -- (NSUInteger)hash -{ - NSUInteger hash = [super hash]; - for (auto coord : _coordinates) { - hash += @(coord.latitude+coord.longitude).hash; - } - return hash; +- (NSUInteger)hash { + NSUInteger hash = [super hash]; + for (auto coord : _coordinates) { + hash += @(coord.latitude + coord.longitude).hash; + } + return hash; } -- (CLLocationCoordinate2D)coordinate -{ - MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); - return _coordinates.at(0); +- (CLLocationCoordinate2D)coordinate { + MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); + return _coordinates.at(0); } -- (NSUInteger)pointCount -{ - return _coordinates.size(); +- (NSUInteger)pointCount { + return _coordinates.size(); } -+ (NSSet *)keyPathsForValuesAffectingPointCount -{ - return [NSSet setWithObjects:@"coordinates", nil]; ++ (NSSet *)keyPathsForValuesAffectingPointCount { + return [NSSet setWithObjects:@"coordinates", nil]; } -- (CLLocationCoordinate2D *)coordinates -{ - return _coordinates.data(); +- (CLLocationCoordinate2D *)coordinates { + return _coordinates.data(); } -- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range -{ - if (range.location + range.length > [self pointCount]) - { - [NSException raise:NSRangeException - format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", - NSStringFromRange(range), (unsigned long)[self pointCount]]; - } +- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range { + if (range.location + range.length > [self pointCount]) { + [NSException + raise:NSRangeException + format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", + NSStringFromRange(range), (unsigned long)[self pointCount]]; + } - std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), coords); + std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), + coords); } - (void)setCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count { - MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count); - if (!count) { - [NSException raise:NSInvalidArgumentException - format:@"A multipoint must have at least one vertex."]; - } + MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count); + if (!count) { + [NSException raise:NSInvalidArgumentException + format:@"A multipoint must have at least one vertex."]; + } - [self willChangeValueForKey:@"coordinates"]; - _coordinates = { coords, coords + count }; - _bounds = {}; - [self didChangeValueForKey:@"coordinates"]; + [self willChangeValueForKey:@"coordinates"]; + _coordinates = {coords, coords + count}; + _bounds = {}; + [self didChangeValueForKey:@"coordinates"]; } -- (void)insertCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count atIndex:(NSUInteger)index { - MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count, (unsigned long)index); - if (!count) { - return; - } +- (void)insertCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count + atIndex:(NSUInteger)index { + MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count, + (unsigned long)index); + if (!count) { + return; + } - if (index > _coordinates.size()) { - [NSException raise:NSRangeException - format:@"Invalid index %lu for existing coordinate count %ld", - (unsigned long)index, (unsigned long)[self pointCount]]; - } + if (index > _coordinates.size()) { + [NSException raise:NSRangeException + format:@"Invalid index %lu for existing coordinate count %ld", (unsigned long)index, + (unsigned long)[self pointCount]]; + } - [self willChangeValueForKey:@"coordinates"]; - _coordinates.insert(_coordinates.begin() + index, count, *coords); - _bounds = {}; - [self didChangeValueForKey:@"coordinates"]; + [self willChangeValueForKey:@"coordinates"]; + _coordinates.insert(_coordinates.begin() + index, count, *coords); + _bounds = {}; + [self didChangeValueForKey:@"coordinates"]; } -- (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count -{ - MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count); - [self insertCoordinates:coords count:count atIndex:_coordinates.size()]; +- (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { + MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count); + [self insertCoordinates:coords count:count atIndex:_coordinates.size()]; } -- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords -{ - MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range)); - [self replaceCoordinatesInRange:range withCoordinates:coords count:range.length]; +- (void)replaceCoordinatesInRange:(NSRange)range + withCoordinates:(const CLLocationCoordinate2D *)coords { + MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range)); + [self replaceCoordinatesInRange:range withCoordinates:coords count:range.length]; } -- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { - MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count, NSStringFromRange(range)); - if (!count && !range.length) { - return; - } +- (void)replaceCoordinatesInRange:(NSRange)range + withCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count { + MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count, + NSStringFromRange(range)); + if (!count && !range.length) { + return; + } - if (NSMaxRange(range) > _coordinates.size()) { - [NSException raise:NSRangeException - format:@"Invalid range %@ for existing coordinate count %ld", - NSStringFromRange(range), (unsigned long)[self pointCount]]; - } + if (NSMaxRange(range) > _coordinates.size()) { + [NSException raise:NSRangeException + format:@"Invalid range %@ for existing coordinate count %ld", + NSStringFromRange(range), (unsigned long)[self pointCount]]; + } - [self willChangeValueForKey:@"coordinates"]; - std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location); - if (count >= range.length) { - _coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords, coords + count - range.length); - } else { - _coordinates.erase(_coordinates.begin() + range.location + count, _coordinates.begin() + NSMaxRange(range)); - } - _bounds = {}; - [self didChangeValueForKey:@"coordinates"]; + [self willChangeValueForKey:@"coordinates"]; + std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location); + if (count >= range.length) { + _coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords, + coords + count - range.length); + } else { + _coordinates.erase(_coordinates.begin() + range.location + count, + _coordinates.begin() + NSMaxRange(range)); + } + _bounds = {}; + [self didChangeValueForKey:@"coordinates"]; } - (void)removeCoordinatesInRange:(NSRange)range { - MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range)); - CLLocationCoordinate2D coords; - [self replaceCoordinatesInRange:range withCoordinates:&coords count:0]; + MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range)); + CLLocationCoordinate2D coords; + [self replaceCoordinatesInRange:range withCoordinates:&coords count:0]; } -- (MLNCoordinateBounds)overlayBounds -{ - if (!_bounds) { - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (auto coordinate : _coordinates) { - if (!MLNLocationCoordinate2DIsValid(coordinate)) { - bounds = mbgl::LatLngBounds::empty(); - break; - } - bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate)); - } - _bounds = bounds; +- (MLNCoordinateBounds)overlayBounds { + if (!_bounds) { + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + for (auto coordinate : _coordinates) { + if (!MLNLocationCoordinate2DIsValid(coordinate)) { + bounds = mbgl::LatLngBounds::empty(); + break; + } + bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate)); } - return MLNCoordinateBoundsFromLatLngBounds(*_bounds); + _bounds = bounds; + } + return MLNCoordinateBoundsFromLatLngBounds(*_bounds); } -- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds -{ - return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); +- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { + return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); } -- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id )delegate -{ - MLNAssert(NO, @"Cannot add an annotation from an instance of %@", NSStringFromClass([self class])); - return mbgl::SymbolAnnotation(mbgl::Point()); +- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id)delegate { + MLNAssert(NO, @"Cannot add an annotation from an instance of %@", + NSStringFromClass([self class])); + return mbgl::SymbolAnnotation(mbgl::Point()); } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", - NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], - MLNStringFromCoordinateBounds(self.overlayBounds)]; +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + (unsigned long)[self pointCount], + MLNStringFromCoordinateBounds(self.overlayBounds)]; } @end diff --git a/platform/darwin/src/MLNNetworkConfiguration.mm b/platform/darwin/src/MLNNetworkConfiguration.mm index dbc4816e0..fda221705 100644 --- a/platform/darwin/src/MLNNetworkConfiguration.mm +++ b/platform/darwin/src/MLNNetworkConfiguration.mm @@ -1,244 +1,238 @@ -#import "MLNNetworkConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNNetworkConfiguration_Private.h" #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #import "MLNSettings_Private.h" #endif #import "MLNReachability.h" -static NSString * const MLNStartTime = @"start_time"; -static NSString * const MLNResourceType = @"resource_type"; -NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace"; +static NSString *const MLNStartTime = @"start_time"; +static NSString *const MLNResourceType = @"resource_type"; +NSString *const kMLNDownloadPerformanceEvent = @"mobile.performance_trace"; @interface MLNNetworkConfiguration () -@property (nonatomic, strong) NSMutableDictionary *events; +@property (nonatomic, strong) NSMutableDictionary *events; @property (nonatomic, weak) id metricsDelegate; @property (nonatomic) dispatch_queue_t eventsQueue; @end -@implementation MLNNetworkConfiguration -{ - NSURLSessionConfiguration *_sessionConfig; +@implementation MLNNetworkConfiguration { + NSURLSessionConfiguration *_sessionConfig; } - (instancetype)init { - if (self = [super init]) { - self.sessionConfiguration = nil; - _events = [NSMutableDictionary dictionary]; - _eventsQueue = dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT); - } + if (self = [super init]) { + self.sessionConfiguration = nil; + _events = [NSMutableDictionary dictionary]; + _eventsQueue = + dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT); + } - return self; + return self; } + (instancetype)sharedManager { - static dispatch_once_t onceToken; - static MLNNetworkConfiguration *_sharedManager; - dispatch_once(&onceToken, ^{ - _sharedManager = [[self alloc] init]; - }); + static dispatch_once_t onceToken; + static MLNNetworkConfiguration *_sharedManager; + dispatch_once(&onceToken, ^{ + _sharedManager = [[self alloc] init]; + }); - // Notice, this is reset for each call. This is primarily for testing purposes. - // TODO: Consider only calling this for testing? - [_sharedManager resetNativeNetworkManagerDelegate]; + // Notice, this is reset for each call. This is primarily for testing purposes. + // TODO: Consider only calling this for testing? + [_sharedManager resetNativeNetworkManagerDelegate]; - return _sharedManager; + return _sharedManager; } - (void)resetNativeNetworkManagerDelegate { - // Tell core about our network integration. `delegate` here is not (yet) - // intended to be set to nil, except for testing. - [MLNNativeNetworkManager sharedManager].delegate = self; + // Tell core about our network integration. `delegate` here is not (yet) + // intended to be set to nil, except for testing. + [MLNNativeNetworkManager sharedManager].delegate = self; } + (NSURLSessionConfiguration *)defaultSessionConfiguration { - NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSessionConfiguration *sessionConfiguration = + [NSURLSessionConfiguration defaultSessionConfiguration]; - sessionConfiguration.timeoutIntervalForResource = 30; - sessionConfiguration.HTTPMaximumConnectionsPerHost = 8; - sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - sessionConfiguration.URLCache = nil; + sessionConfiguration.timeoutIntervalForResource = 30; + sessionConfiguration.HTTPMaximumConnectionsPerHost = 8; + sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + sessionConfiguration.URLCache = nil; - return sessionConfiguration; + return sessionConfiguration; } // MARK: - MLNNativeNetworkDelegate - (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager { - // Note: this method is NOT called on the main thread. - NSURLSession *session; - if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) { - session = [self.delegate sessionForNetworkConfiguration:self]; - } + // Note: this method is NOT called on the main thread. + NSURLSession *session; + if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) { + session = [self.delegate sessionForNetworkConfiguration:self]; + } - // Check for a background session; string checking is fragile, but this is not - // a deal breaker as we're only doing this to provide more clarity to the - // developer - NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")], - @"Background NSURLSessions are not yet supported"); + // Check for a background session; string checking is fragile, but this is not + // a deal breaker as we're only doing this to provide more clarity to the + // developer + NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")], + @"Background NSURLSessions are not yet supported"); - if (session.delegate) { - NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)], - @"Session delegates conforming to NSURLSessionDataDelegate are not yet supported"); - } + if (session.delegate) { + NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)], + @"Session delegates conforming to NSURLSessionDataDelegate are not yet supported"); + } - return session; + return session; } - (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request { + if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) { + return [self.delegate willSendRequest:request]; + } - if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) { - return [self.delegate willSendRequest:request]; - } - - return request; - + return request; } - (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response { - - if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) { - - MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data - urlResponse:response.response - error:response.error]; - if (tempResponse) { - MLNInternalNetworkResponse *internalResponse = [MLNInternalNetworkResponse responseWithData:tempResponse.data - urlResponse:tempResponse.response - error:tempResponse.error]; - return internalResponse; - } else { - return nil; - } - + if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) { + MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data + urlResponse:response.response + error:response.error]; + if (tempResponse) { + MLNInternalNetworkResponse *internalResponse = + [MLNInternalNetworkResponse responseWithData:tempResponse.data + urlResponse:tempResponse.response + error:tempResponse.error]; + return internalResponse; + } else { + return nil; } + } - return response; - + return response; } - - (NSURLSessionConfiguration *)sessionConfiguration { - NSURLSessionConfiguration *sessionConfig = nil; - @synchronized (self) { - sessionConfig = _sessionConfig; - } - return sessionConfig; + NSURLSessionConfiguration *sessionConfig = nil; + @synchronized(self) { + sessionConfig = _sessionConfig; + } + return sessionConfig; } - (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration { - @synchronized (self) { - if (sessionConfiguration == nil) { - _sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration]; - } else { - _sessionConfig = sessionConfiguration; - } + @synchronized(self) { + if (sessionConfiguration == nil) { + _sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration]; + } else { + _sessionConfig = sessionConfiguration; } + } } - (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType { - if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) { - NSDate *startDate = [NSDate date]; - [self setEventDictionary:@{ MLNStartTime: startDate, MLNResourceType: resourceType } - forKey:urlString]; - } + if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) { + NSDate *startDate = [NSDate date]; + [self setEventDictionary:@{MLNStartTime : startDate, MLNResourceType : resourceType} + forKey:urlString]; + } } - (void)stopDownloadEventForResponse:(NSURLResponse *)response { - [self sendEventForURLResponse:response withAction:nil]; + [self sendEventForURLResponse:response withAction:nil]; } - (void)cancelDownloadEventForResponse:(NSURLResponse *)response { - [self sendEventForURLResponse:response withAction:@"cancel"]; + [self sendEventForURLResponse:response withAction:@"cancel"]; } - (void)debugLog:(NSString *)message { - MLNLogDebug(message); + MLNLogDebug(message); } - (void)errorLog:(NSString *)message { - MLNLogError(message); + MLNLogError(message); } // MARK: - Event management -- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action -{ - if ([response isKindOfClass:[NSURLResponse class]]) { - NSString *urlString = response.URL.relativePath; - if (urlString && [self eventDictionaryForKey:urlString]) { - NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action]; - [self removeEventDictionaryForKey:urlString]; +- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action { + if ([response isKindOfClass:[NSURLResponse class]]) { + NSString *urlString = response.URL.relativePath; + if (urlString && [self eventDictionaryForKey:urlString]) { + NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action]; + [self removeEventDictionaryForKey:urlString]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes]; - }); - } + dispatch_async(dispatch_get_main_queue(), ^{ + [self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes]; + }); } + } } -- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action -{ - NSString *urlString = response.URL.relativePath; - NSDictionary *parameters = [self eventDictionaryForKey:urlString]; - NSDate *startDate = [parameters objectForKey:MLNStartTime]; - NSDate *endDate = [NSDate date]; - NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate]; - NSDateFormatter* iso8601Formatter = [[NSDateFormatter alloc] init]; - iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; - NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]]; +- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action { + NSString *urlString = response.URL.relativePath; + NSDictionary *parameters = [self eventDictionaryForKey:urlString]; + NSDate *startDate = [parameters objectForKey:MLNStartTime]; + NSDate *endDate = [NSDate date]; + NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate]; + NSDateFormatter *iso8601Formatter = [[NSDateFormatter alloc] init]; + iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; + NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]]; - NSMutableArray *attributes = [NSMutableArray array]; - [attributes addObject:@{ @"name" : @"requestUrl" , @"value" : urlString }]; - [attributes addObject:@{ @"name" : MLNResourceType , @"value" : [parameters objectForKey:MLNResourceType] }]; + NSMutableArray *attributes = [NSMutableArray array]; + [attributes addObject:@{@"name" : @"requestUrl", @"value" : urlString}]; + [attributes + addObject:@{@"name" : MLNResourceType, @"value" : [parameters objectForKey:MLNResourceType]}]; - if ([response isKindOfClass:[NSHTTPURLResponse class]]) { - NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode]; - [attributes addObject:@{ @"name" : @"responseCode", @"value" : @(responseCode)}]; - } + if ([response isKindOfClass:[NSHTTPURLResponse class]]) { + NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode]; + [attributes addObject:@{@"name" : @"responseCode", @"value" : @(responseCode)}]; + } - BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi]; - [attributes addObject:@{ @"name" : @"wifiOn", @"value" : @(isWIFIOn)}]; + BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi]; + [attributes addObject:@{@"name" : @"wifiOn", @"value" : @(isWIFIOn)}]; - if (action) { - [attributes addObject:@{ @"name" : @"action" , @"value" : action }]; - } + if (action) { + [attributes addObject:@{@"name" : @"action", @"value" : action}]; + } - double elapsedTimeInMS = elapsedTime * 1000.0; + double elapsedTimeInMS = elapsedTime * 1000.0; - return @{ - @"event" : kMLNDownloadPerformanceEvent, - @"created" : createdDate, - @"sessionId" : [NSUUID UUID].UUIDString, - @"counters" : @[ @{ @"name" : @"elapsedMS" , @"value" : @(elapsedTimeInMS) } ], - @"attributes" : attributes - }; + return @{ + @"event" : kMLNDownloadPerformanceEvent, + @"created" : createdDate, + @"sessionId" : [NSUUID UUID].UUIDString, + @"counters" : @[ @{@"name" : @"elapsedMS", @"value" : @(elapsedTimeInMS)} ], + @"attributes" : attributes + }; } // MARK: - Events dictionary access -- (nullable NSDictionary*)eventDictionaryForKey:(nonnull NSString*)key { - __block NSDictionary *dictionary; +- (nullable NSDictionary *)eventDictionaryForKey:(nonnull NSString *)key { + __block NSDictionary *dictionary; - dispatch_sync(self.eventsQueue, ^{ - dictionary = [self.events objectForKey:key]; - }); + dispatch_sync(self.eventsQueue, ^{ + dictionary = [self.events objectForKey:key]; + }); - return dictionary; + return dictionary; } -- (void)setEventDictionary:(nonnull NSDictionary*)dictionary forKey:(nonnull NSString*)key { - dispatch_barrier_async(self.eventsQueue, ^{ - [self.events setObject:dictionary forKey:key]; - }); +- (void)setEventDictionary:(nonnull NSDictionary *)dictionary forKey:(nonnull NSString *)key { + dispatch_barrier_async(self.eventsQueue, ^{ + [self.events setObject:dictionary forKey:key]; + }); } -- (void)removeEventDictionaryForKey:(nonnull NSString*)key { - dispatch_barrier_async(self.eventsQueue, ^{ - [self.events removeObjectForKey:key]; - }); +- (void)removeEventDictionaryForKey:(nonnull NSString *)key { + dispatch_barrier_async(self.eventsQueue, ^{ + [self.events removeObjectForKey:key]; + }); } @end @@ -246,11 +240,11 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace"; @implementation MLNNetworkConfiguration (ForTesting) + (void)testing_clearNativeNetworkManagerDelegate { - [MLNNativeNetworkManager sharedManager].delegate = nil; + [MLNNativeNetworkManager sharedManager].delegate = nil; } + (id)testing_nativeNetworkManagerDelegate { - return [MLNNativeNetworkManager sharedManager].delegate; + return [MLNNativeNetworkManager sharedManager].delegate; } @end diff --git a/platform/darwin/src/MLNNetworkResponse.mm b/platform/darwin/src/MLNNetworkResponse.mm index 516aafbd7..fd957f6e0 100644 --- a/platform/darwin/src/MLNNetworkResponse.mm +++ b/platform/darwin/src/MLNNetworkResponse.mm @@ -1,17 +1,16 @@ -#import #import "MLNNetworkResponse.h" +#import @implementation MLNNetworkResponse -+(MLNNetworkResponse *)responseWithData:(NSData *)data - urlResponse:(NSURLResponse *)response - error:(NSError *)error { - - MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init]; - tempResult.data = data; - tempResult.response = response; - tempResult.error = error; - return tempResult; ++ (MLNNetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error { + MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init]; + tempResult.data = data; + tempResult.response = response; + tempResult.error = error; + return tempResult; } @end diff --git a/platform/darwin/src/MLNOfflinePack.mm b/platform/darwin/src/MLNOfflinePack.mm index b68cd5562..2adb4715a 100644 --- a/platform/darwin/src/MLNOfflinePack.mm +++ b/platform/darwin/src/MLNOfflinePack.mm @@ -1,12 +1,12 @@ #import "MLNOfflinePack_Private.h" -#import "MLNOfflineStorage_Private.h" +#import "MLNLoggingConfiguration_Private.h" #import "MLNOfflineRegion_Private.h" -#import "MLNTilePyramidOfflineRegion.h" -#import "MLNTilePyramidOfflineRegion_Private.h" +#import "MLNOfflineStorage_Private.h" #import "MLNShapeOfflineRegion.h" #import "MLNShapeOfflineRegion_Private.h" -#import "MLNLoggingConfiguration_Private.h" +#import "MLNTilePyramidOfflineRegion.h" +#import "MLNTilePyramidOfflineRegion_Private.h" #import "NSValue+MLNAdditions.h" @@ -22,18 +22,19 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE This macro should be used at the beginning of any public-facing instance method of ``MLNOfflinePack``. For private methods, an assertion is more appropriate. */ -#define MLNAssertOfflinePackIsValid() \ - do { \ - if (_state == MLNOfflinePackStateInvalid) { \ - [NSException raise:MLNInvalidOfflinePackException \ - format: \ - @"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \ - @"on this instance of MLNOfflinePack, rendering it invalid. It is an " \ - @"error to send any message to this pack."]; \ - } \ - } while (NO); +#define MLNAssertOfflinePackIsValid() \ + do { \ + if (_state == MLNOfflinePackStateInvalid) { \ + [NSException \ + raise:MLNInvalidOfflinePackException \ + format:@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \ + @"on this instance of MLNOfflinePack, rendering it invalid. It is an " \ + @"error to send any message to this pack."]; \ + } \ + } while (NO); -@interface MLNTilePyramidOfflineRegion () +@interface MLNTilePyramidOfflineRegion () @end @interface MLNShapeOfflineRegion () @@ -41,14 +42,14 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver { public: - MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {} + MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {} - void statusChanged(mbgl::OfflineRegionStatus status) override; - void responseError(mbgl::Response::Error error) override; - void mapboxTileCountLimitExceeded(uint64_t limit) override; + void statusChanged(mbgl::OfflineRegionStatus status) override; + void responseError(mbgl::Response::Error error) override; + void mapboxTileCountLimitExceeded(uint64_t limit) override; private: - __weak MLNOfflinePack *pack = nullptr; + __weak MLNOfflinePack *pack = nullptr; }; @interface MLNOfflinePack () @@ -59,268 +60,296 @@ private: @end @implementation MLNOfflinePack { - BOOL _isSuspending; - std::shared_ptr _mbglDatabaseFileSource; + BOOL _isSuspending; + std::shared_ptr _mbglDatabaseFileSource; } - (instancetype)init { - MLNLogInfo(@"Calling this initializer is not allowed."); - if (self = [super init]) { - _state = MLNOfflinePackStateInvalid; - NSLog(@"%s called; did you mean to call +[MLNOfflineStorage addPackForRegion:withContext:completionHandler:] instead?", __PRETTY_FUNCTION__); - } - return self; + MLNLogInfo(@"Calling this initializer is not allowed."); + if (self = [super init]) { + _state = MLNOfflinePackStateInvalid; + NSLog(@"%s called; did you mean to call +[MLNOfflineStorage " + @"addPackForRegion:withContext:completionHandler:] instead?", + __PRETTY_FUNCTION__); + } + return self; } - (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region { - if (self = [super init]) { - _mbglOfflineRegion = region; - _state = MLNOfflinePackStateUnknown; + if (self = [super init]) { + _mbglOfflineRegion = region; + _state = MLNOfflinePackStateUnknown; - _mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource]; - _mbglDatabaseFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, std::make_unique(self)); - } - return self; + _mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource]; + _mbglDatabaseFileSource->setOfflineRegionObserver( + *_mbglOfflineRegion, std::make_unique(self)); + } + return self; } - (void)dealloc { - MLNAssert(_state == MLNOfflinePackStateInvalid, @"MLNOfflinePack was not invalided prior to deallocation."); + MLNAssert(_state == MLNOfflinePackStateInvalid, + @"MLNOfflinePack was not invalided prior to deallocation."); } -- (id )region { - MLNAssertOfflinePackIsValid(); +- (id)region { + MLNAssertOfflinePackIsValid(); - const mbgl::OfflineRegionDefinition ®ionDefinition = _mbglOfflineRegion->getDefinition(); - MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private."); - MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private."); + const mbgl::OfflineRegionDefinition ®ionDefinition = _mbglOfflineRegion->getDefinition(); + MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], + @"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private."); + MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], + @"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private."); - return std::visit(mbgl::overloaded{ - [&] (const mbgl::OfflineTilePyramidRegionDefinition def){ - return (id )[[MLNTilePyramidOfflineRegion alloc] initWithOfflineRegionDefinition:def]; - }, - [&] (const mbgl::OfflineGeometryRegionDefinition& def){ - return (id )[[MLNShapeOfflineRegion alloc] initWithOfflineRegionDefinition:def]; - } - }, regionDefinition); + return std::visit( + mbgl::overloaded{[&](const mbgl::OfflineTilePyramidRegionDefinition def) { + return (id)[[MLNTilePyramidOfflineRegion alloc] + initWithOfflineRegionDefinition:def]; + }, + [&](const mbgl::OfflineGeometryRegionDefinition &def) { + return (id)[[MLNShapeOfflineRegion alloc] + initWithOfflineRegionDefinition:def]; + }}, + regionDefinition); } - (NSData *)context { - MLNAssertOfflinePackIsValid(); + MLNAssertOfflinePackIsValid(); - const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata(); - return [NSData dataWithBytes:&metadata[0] length:metadata.size()]; + const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata(); + return [NSData dataWithBytes:&metadata[0] length:metadata.size()]; } -- (void)setContext:(NSData *)context completionHandler:(void (^_Nullable)(NSError * _Nullable error))completion { - MLNAssertOfflinePackIsValid(); +- (void)setContext:(NSData *)context + completionHandler:(void (^_Nullable)(NSError *_Nullable error))completion { + MLNAssertOfflinePackIsValid(); - mbgl::OfflineRegionMetadata metadata(context.length); - [context getBytes:&metadata[0] length:metadata.size()]; + mbgl::OfflineRegionMetadata metadata(context.length); + [context getBytes:&metadata[0] length:metadata.size()]; - [self willChangeValueForKey:@"context"]; - __weak MLNOfflinePack *weakSelf = self; - _mbglDatabaseFileSource->updateOfflineMetadata(_mbglOfflineRegion->getID(), metadata, [&, completion, weakSelf](mbgl::expected mbglOfflineRegionMetadata) { + [self willChangeValueForKey:@"context"]; + __weak MLNOfflinePack *weakSelf = self; + _mbglDatabaseFileSource->updateOfflineMetadata( + _mbglOfflineRegion->getID(), metadata, + [&, completion, weakSelf](mbgl::expected + mbglOfflineRegionMetadata) { NSError *error; if (!mbglOfflineRegionMetadata) { - NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str()); - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{ - NSLocalizedDescriptionKey: errorDescription, - } : nil]; + NSString *errorDescription = + @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str()); + error = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:errorDescription ? @{ + NSLocalizedDescriptionKey : errorDescription, + } + : nil]; } dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) { - [weakSelf reloadWithCompletionHandler:^(NSError * _Nullable reloadingError) { - MLNOfflinePack *strongSelf = weakSelf; - [strongSelf didChangeValueForKey:@"context"]; - if (completion) { - completion(error ?: reloadingError); - } - }]; + [weakSelf reloadWithCompletionHandler:^(NSError *_Nullable reloadingError) { + MLNOfflinePack *strongSelf = weakSelf; + [strongSelf didChangeValueForKey:@"context"]; + if (completion) { + completion(error ?: reloadingError); + } + }]; }); - }); + }); } -- (void)reloadWithCompletionHandler:(void (^)(NSError * _Nullable error))completion { - auto regionID = _mbglOfflineRegion->getID(); - MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage]; - __weak MLNOfflinePack *weakSelf = self; - [sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray *packs, __unused NSError * _Nullable error) { - MLNOfflinePack *strongSelf = weakSelf; - for (MLNOfflinePack *pack in packs) { - if (pack.mbglOfflineRegion->getID() == regionID) { - strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion; - } - [pack invalidate]; - } - completion(error); - }]; +- (void)reloadWithCompletionHandler:(void (^)(NSError *_Nullable error))completion { + auto regionID = _mbglOfflineRegion->getID(); + MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage]; + __weak MLNOfflinePack *weakSelf = self; + [sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray *packs, + __unused NSError *_Nullable error) { + MLNOfflinePack *strongSelf = weakSelf; + for (MLNOfflinePack *pack in packs) { + if (pack.mbglOfflineRegion->getID() == regionID) { + strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion; + } + [pack invalidate]; + } + completion(error); + }]; } - (void)resume { - MLNLogInfo(@"Resuming pack download."); - MLNAssertOfflinePackIsValid(); + MLNLogInfo(@"Resuming pack download."); + MLNAssertOfflinePackIsValid(); - self.state = MLNOfflinePackStateActive; + self.state = MLNOfflinePackStateActive; - _mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active); + _mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, + mbgl::OfflineRegionDownloadState::Active); } - (void)suspend { - MLNLogInfo(@"Suspending pack download."); - MLNAssertOfflinePackIsValid(); + MLNLogInfo(@"Suspending pack download."); + MLNAssertOfflinePackIsValid(); - if (self.state == MLNOfflinePackStateActive) { - self.state = MLNOfflinePackStateInactive; - _isSuspending = YES; - } + if (self.state == MLNOfflinePackStateActive) { + self.state = MLNOfflinePackStateInactive; + _isSuspending = YES; + } - _mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive); + _mbglDatabaseFileSource->setOfflineRegionDownloadState( + *_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive); } - (void)invalidate { - MLNLogInfo(@"Invalidating pack."); - MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack."); - MLNAssert(self.mbglOfflineRegion, @"Should have a valid region"); + MLNLogInfo(@"Invalidating pack."); + MLNAssert(_state != MLNOfflinePackStateInvalid, + @"Cannot invalidate an already invalid offline pack."); + MLNAssert(self.mbglOfflineRegion, @"Should have a valid region"); - @synchronized (self) { - self.state = MLNOfflinePackStateInvalid; - if (self.mbglOfflineRegion) { - _mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); - } - self.mbglOfflineRegion = nil; + @synchronized(self) { + self.state = MLNOfflinePackStateInvalid; + if (self.mbglOfflineRegion) { + _mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); } + self.mbglOfflineRegion = nil; + } } - (void)setState:(MLNOfflinePackState)state { - MLNLogDebug(@"Setting state: %ld", (long)state); - if (!self.mbglOfflineRegion) { - // A progress update has arrived after the call to - // -[MLNOfflineStorage removePack:withCompletionHandler:] but before the - // removal is complete and the completion handler is called. - MLNAssert(_state == MLNOfflinePackStateInvalid, @"A valid MLNOfflinePack has no mbgl::OfflineRegion."); - return; - } + MLNLogDebug(@"Setting state: %ld", (long)state); + if (!self.mbglOfflineRegion) { + // A progress update has arrived after the call to + // -[MLNOfflineStorage removePack:withCompletionHandler:] but before the + // removal is complete and the completion handler is called. + MLNAssert(_state == MLNOfflinePackStateInvalid, + @"A valid MLNOfflinePack has no mbgl::OfflineRegion."); + return; + } - MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change the state of an invalid offline pack."); + MLNAssert(_state != MLNOfflinePackStateInvalid, + @"Cannot change the state of an invalid offline pack."); - if (!_isSuspending || state != MLNOfflinePackStateActive) { - _isSuspending = NO; - _state = state; - } + if (!_isSuspending || state != MLNOfflinePackStateActive) { + _isSuspending = NO; + _state = state; + } } - (void)requestProgress { - MLNLogInfo(@"Requesting pack progress."); - MLNAssertOfflinePackIsValid(); + MLNLogInfo(@"Requesting pack progress."); + MLNAssertOfflinePackIsValid(); - __weak MLNOfflinePack *weakSelf = self; - _mbglDatabaseFileSource->getOfflineRegionStatus(*_mbglOfflineRegion, [&, weakSelf](mbgl::expected status) { + __weak MLNOfflinePack *weakSelf = self; + _mbglDatabaseFileSource->getOfflineRegionStatus( + *_mbglOfflineRegion, + [&, weakSelf](mbgl::expected status) { if (status) { - mbgl::OfflineRegionStatus checkedStatus = *status; - dispatch_async(dispatch_get_main_queue(), ^{ - MLNOfflinePack *strongSelf = weakSelf; - [strongSelf offlineRegionStatusDidChange:checkedStatus]; - }); + mbgl::OfflineRegionStatus checkedStatus = *status; + dispatch_async(dispatch_get_main_queue(), ^{ + MLNOfflinePack *strongSelf = weakSelf; + [strongSelf offlineRegionStatusDidChange:checkedStatus]; + }); } - }); + }); } - (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status { - MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change update progress of an invalid offline pack."); + MLNAssert(_state != MLNOfflinePackStateInvalid, + @"Cannot change update progress of an invalid offline pack."); - switch (status.downloadState) { - case mbgl::OfflineRegionDownloadState::Inactive: - self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive; - break; + switch (status.downloadState) { + case mbgl::OfflineRegionDownloadState::Inactive: + self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive; + break; - case mbgl::OfflineRegionDownloadState::Active: - self.state = MLNOfflinePackStateActive; - break; - } + case mbgl::OfflineRegionDownloadState::Active: + self.state = MLNOfflinePackStateActive; + break; + } - if (_isSuspending) { - return; - } + if (_isSuspending) { + return; + } - MLNOfflinePackProgress progress; - progress.countOfResourcesCompleted = status.completedResourceCount; - progress.countOfBytesCompleted = status.completedResourceSize; - progress.countOfTilesCompleted = status.completedTileCount; - progress.countOfTileBytesCompleted = status.completedTileSize; - progress.countOfResourcesExpected = status.requiredResourceCount; - progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX; - self.progress = progress; + MLNOfflinePackProgress progress; + progress.countOfResourcesCompleted = status.completedResourceCount; + progress.countOfBytesCompleted = status.completedResourceSize; + progress.countOfTilesCompleted = status.completedTileCount; + progress.countOfTileBytesCompleted = status.completedTileSize; + progress.countOfResourcesExpected = status.requiredResourceCount; + progress.maximumResourcesExpected = + status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX; + self.progress = progress; - NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyState: @(self.state), - MLNOfflinePackUserInfoKeyProgress: [NSValue valueWithMLNOfflinePackProgress:progress]}; + NSDictionary *userInfo = @{ + MLNOfflinePackUserInfoKeyState : @(self.state), + MLNOfflinePackUserInfoKeyProgress : [NSValue valueWithMLNOfflinePackProgress:progress] + }; - NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; - [noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification - object:self - userInfo:userInfo]; + NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; + [noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification + object:self + userInfo:userInfo]; } - (void)didReceiveError:(NSError *)error { - MLNLogError(@"Error: %@", error.localizedDescription); - MLNLogInfo(@"Notifying about pack error."); + MLNLogError(@"Error: %@", error.localizedDescription); + MLNLogInfo(@"Notifying about pack error."); - NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyError: error }; - NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; - [noteCenter postNotificationName:MLNOfflinePackErrorNotification - object:self - userInfo:userInfo]; + NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyError : error}; + NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; + [noteCenter postNotificationName:MLNOfflinePackErrorNotification object:self userInfo:userInfo]; } - (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit { - MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit); - NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyMaximumCount: @(limit) }; - NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; - [noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification - object:self - userInfo:userInfo]; + MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit); + NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyMaximumCount : @(limit)}; + NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; + [noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification + object:self + userInfo:userInfo]; } NSError *MLNErrorFromResponseError(mbgl::Response::Error error) { - NSInteger errorCode = MLNErrorCodeUnknown; - switch (error.reason) { - case mbgl::Response::Error::Reason::NotFound: - errorCode = MLNErrorCodeNotFound; - break; + NSInteger errorCode = MLNErrorCodeUnknown; + switch (error.reason) { + case mbgl::Response::Error::Reason::NotFound: + errorCode = MLNErrorCodeNotFound; + break; - case mbgl::Response::Error::Reason::Server: - errorCode = MLNErrorCodeBadServerResponse; - break; + case mbgl::Response::Error::Reason::Server: + errorCode = MLNErrorCodeBadServerResponse; + break; - case mbgl::Response::Error::Reason::Connection: - errorCode = MLNErrorCodeConnectionFailed; - break; + case mbgl::Response::Error::Reason::Connection: + errorCode = MLNErrorCodeConnectionFailed; + break; - default: - break; - } - return [NSError errorWithDomain:MLNErrorDomain code:errorCode userInfo:@{ - NSLocalizedFailureReasonErrorKey: @(error.message.c_str()) - }]; + default: + break; + } + return [NSError errorWithDomain:MLNErrorDomain + code:errorCode + userInfo:@{ + NSLocalizedFailureReasonErrorKey : @(error.message.c_str()) + }]; } @end void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) { - __weak MLNOfflinePack *weakPack = pack; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakPack offlineRegionStatusDidChange:status]; - }); + __weak MLNOfflinePack *weakPack = pack; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakPack offlineRegionStatusDidChange:status]; + }); } void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) { - __weak MLNOfflinePack *weakPack = pack; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakPack didReceiveError:MLNErrorFromResponseError(error)]; - }); + __weak MLNOfflinePack *weakPack = pack; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakPack didReceiveError:MLNErrorFromResponseError(error)]; + }); } void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) { - __weak MLNOfflinePack *weakPack = pack; - dispatch_async(dispatch_get_main_queue(), ^{ - [weakPack didReceiveMaximumAllowedMapboxTiles:limit]; - }); + __weak MLNOfflinePack *weakPack = pack; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakPack didReceiveMaximumAllowedMapboxTiles:limit]; + }); } diff --git a/platform/darwin/src/MLNOfflineStorage.mm b/platform/darwin/src/MLNOfflineStorage.mm index 651e57c4a..cc577ddab 100644 --- a/platform/darwin/src/MLNOfflineStorage.mm +++ b/platform/darwin/src/MLNOfflineStorage.mm @@ -1,17 +1,17 @@ #import "MLNOfflineStorage_Private.h" #import "MLNFoundation_Private.h" -#import "MLNSettings_Private.h" #import "MLNGeometry_Private.h" -#import "MLNOfflinePack_Private.h" -#import "MLNOfflineRegion_Private.h" -#import "MLNTilePyramidOfflineRegion.h" -#import "MLNShapeOfflineRegion.h" -#import "NSBundle+MLNAdditions.h" -#import "NSValue+MLNAdditions.h" -#import "NSDate+MLNAdditions.h" #import "MLNLoggingConfiguration_Private.h" #import "MLNNetworkConfiguration_Private.h" +#import "MLNOfflinePack_Private.h" +#import "MLNOfflineRegion_Private.h" +#import "MLNSettings_Private.h" +#import "MLNShapeOfflineRegion.h" +#import "MLNTilePyramidOfflineRegion.h" +#import "NSBundle+MLNAdditions.h" +#import "NSDate+MLNAdditions.h" +#import "NSValue+MLNAdditions.h" #include #include @@ -25,13 +25,16 @@ #include -static NSString * const MLNOfflineStorageDatabasePathInfoDictionaryKey = @"MLNOfflineStorageDatabasePath"; -static NSString * const MLNOfflineStorageFileName = @"cache.db"; -static NSString * const MLNOfflineStorageFileName3_2_0_beta_1 = @"offline.db"; +static NSString *const MLNOfflineStorageDatabasePathInfoDictionaryKey = + @"MLNOfflineStorageDatabasePath"; +static NSString *const MLNOfflineStorageFileName = @"cache.db"; +static NSString *const MLNOfflineStorageFileName3_2_0_beta_1 = @"offline.db"; -const NSNotificationName MLNOfflinePackProgressChangedNotification = @"MLNOfflinePackProgressChanged"; +const NSNotificationName MLNOfflinePackProgressChangedNotification = + @"MLNOfflinePackProgressChanged"; const NSNotificationName MLNOfflinePackErrorNotification = @"MLNOfflinePackError"; -const NSNotificationName MLNOfflinePackMaximumMapboxTilesReachedNotification = @"MLNOfflinePackMaximumMapboxTilesReached"; +const NSNotificationName MLNOfflinePackMaximumMapboxTilesReachedNotification = + @"MLNOfflinePackMaximumMapboxTilesReached"; const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyState = @"State"; const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyProgress = @"Progress"; @@ -50,208 +53,239 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio @end @implementation MLNOfflineStorage { - NSURL *_databaseURL; - std::unique_ptr> _mbglResourceTransform; + NSURL *_databaseURL; + std::unique_ptr> _mbglResourceTransform; } + (instancetype)sharedOfflineStorage { - static dispatch_once_t onceToken; - static MLNOfflineStorage *sharedOfflineStorage; - dispatch_once(&onceToken, ^{ - sharedOfflineStorage = [[self alloc] init]; + static dispatch_once_t onceToken; + static MLNOfflineStorage *sharedOfflineStorage; + dispatch_once(&onceToken, ^{ + sharedOfflineStorage = [[self alloc] init]; #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - [[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(unpauseFileSource:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(pauseFileSource:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage + selector:@selector(unpauseFileSource:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage + selector:@selector(pauseFileSource:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; #endif - [sharedOfflineStorage reloadPacks]; - }); + [sharedOfflineStorage reloadPacks]; + }); - // Always ensure the MLNNativeNetworkManager delegate is setup. Calling - // `resetNativeNetworkManagerDelegate` is not necessary here, since the shared - // manager already calls it. - // - // TODO: Consider only calling this for testing? - [MLNNetworkConfiguration sharedManager]; + // Always ensure the MLNNativeNetworkManager delegate is setup. Calling + // `resetNativeNetworkManagerDelegate` is not necessary here, since the shared + // manager already calls it. + // + // TODO: Consider only calling this for testing? + [MLNNetworkConfiguration sharedManager]; - return sharedOfflineStorage; + return sharedOfflineStorage; } #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - (void)pauseFileSource:(__unused NSNotification *)notification { - if (self.isPaused) { - return; - } + if (self.isPaused) { + return; + } - _mbglOnlineFileSource->pause(); - _mbglDatabaseFileSource->pause(); - self.paused = YES; + _mbglOnlineFileSource->pause(); + _mbglDatabaseFileSource->pause(); + self.paused = YES; } - (void)unpauseFileSource:(__unused NSNotification *)notification { - if (!self.isPaused) { - return; - } + if (!self.isPaused) { + return; + } - _mbglOnlineFileSource->resume(); - _mbglDatabaseFileSource->resume(); - self.paused = NO; + _mbglOnlineFileSource->resume(); + _mbglDatabaseFileSource->resume(); + self.paused = NO; } #endif - (void)setDelegate:(id)newValue { - MLNLogDebug(@"Setting delegate: %@", newValue); - _delegate = newValue; - if ([self.delegate respondsToSelector:@selector(offlineStorage:URLForResourceOfKind:withURL:)]) { - _mbglResourceTransform = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [offlineStorage = self](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb) { - NSURL* url = - [NSURL URLWithString:[[NSString alloc] initWithBytes:url_.data() - length:url_.length() - encoding:NSUTF8StringEncoding]]; - MLNResourceKind kind = MLNResourceKindUnknown; - switch (kind_) { + MLNLogDebug(@"Setting delegate: %@", newValue); + _delegate = newValue; + if ([self.delegate respondsToSelector:@selector(offlineStorage:URLForResourceOfKind:withURL:)]) { + _mbglResourceTransform = + std::make_unique>( + *mbgl::Scheduler::GetCurrent(), + [offlineStorage = self](auto kind_, const std::string &url_, + mbgl::ResourceTransform::FinishedCallback cb) { + NSURL *url = + [NSURL URLWithString:[[NSString alloc] initWithBytes:url_.data() + length:url_.length() + encoding:NSUTF8StringEncoding]]; + MLNResourceKind kind = MLNResourceKindUnknown; + switch (kind_) { case mbgl::Resource::Kind::Tile: - kind = MLNResourceKindTile; - break; + kind = MLNResourceKindTile; + break; case mbgl::Resource::Kind::Glyphs: - kind = MLNResourceKindGlyphs; - break; + kind = MLNResourceKindGlyphs; + break; case mbgl::Resource::Kind::Style: - kind = MLNResourceKindStyle; - break; + kind = MLNResourceKindStyle; + break; case mbgl::Resource::Kind::Source: - kind = MLNResourceKindSource; - break; + kind = MLNResourceKindSource; + break; case mbgl::Resource::Kind::SpriteImage: - kind = MLNResourceKindSpriteImage; - break; + kind = MLNResourceKindSpriteImage; + break; case mbgl::Resource::Kind::SpriteJSON: - kind = MLNResourceKindSpriteJSON; - break; + kind = MLNResourceKindSpriteJSON; + break; case mbgl::Resource::Kind::Image: - kind = MLNResourceKindImage; - break; + kind = MLNResourceKindImage; + break; case mbgl::Resource::Kind::Unknown: - kind = MLNResourceKindUnknown; - break; + kind = MLNResourceKindUnknown; + break; + } + url = [offlineStorage.delegate offlineStorage:offlineStorage + URLForResourceOfKind:kind + withURL:url]; + cb(url.absoluteString.UTF8String); + }); - } - url = [offlineStorage.delegate offlineStorage:offlineStorage - URLForResourceOfKind:kind - withURL:url]; - cb(url.absoluteString.UTF8String); - }); - - _mbglOnlineFileSource->setResourceTransform({[actorRef = _mbglResourceTransform->self()](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb_){ - actorRef.invoke(&mbgl::ResourceTransform::TransformCallback::operator(), kind_, url_, std::move(cb_)); + _mbglOnlineFileSource->setResourceTransform( + {[actorRef = _mbglResourceTransform->self()]( + auto kind_, const std::string &url_, mbgl::ResourceTransform::FinishedCallback cb_) { + actorRef.invoke(&mbgl::ResourceTransform::TransformCallback::operator(), kind_, url_, + std::move(cb_)); }}); - } else { - _mbglResourceTransform.reset(); - _mbglOnlineFileSource->setResourceTransform({}); - } + } else { + _mbglResourceTransform.reset(); + _mbglOnlineFileSource->setResourceTransform({}); + } } - (instancetype)init { - // Ensure network configuration & appropriate delegate prior to starting the - // run loop. Calling `resetNativeNetworkManagerDelegate` is not necessary here, - // since the shared manager already calls it. - [MLNNetworkConfiguration sharedManager]; + // Ensure network configuration & appropriate delegate prior to starting the + // run loop. Calling `resetNativeNetworkManagerDelegate` is not necessary here, + // since the shared manager already calls it. + [MLNNetworkConfiguration sharedManager]; - MLNInitializeRunLoop(); + MLNInitializeRunLoop(); - if (self = [super init]) { - mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - mbgl::ResourceOptions resourceOptions; - resourceOptions.withCachePath(self.databasePath.UTF8String) - .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) - .withTileServerOptions(*tileServerOptions); - mbgl::ClientOptions clientOptions; - _mbglFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::ResourceLoader, resourceOptions, clientOptions); - _mbglOnlineFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Network, resourceOptions, clientOptions); - _mbglDatabaseFileSource = std::static_pointer_cast(std::shared_ptr(mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Database, resourceOptions, clientOptions))); + if (self = [super init]) { + mbgl::TileServerOptions *tileServerOptions = + [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withCachePath(self.databasePath.UTF8String) + .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) + .withTileServerOptions(*tileServerOptions); + mbgl::ClientOptions clientOptions; + _mbglFileSource = mbgl::FileSourceManager::get()->getFileSource( + mbgl::FileSourceType::ResourceLoader, resourceOptions, clientOptions); + _mbglOnlineFileSource = mbgl::FileSourceManager::get()->getFileSource( + mbgl::FileSourceType::Network, resourceOptions, clientOptions); + _mbglDatabaseFileSource = std::static_pointer_cast( + std::shared_ptr(mbgl::FileSourceManager::get()->getFileSource( + mbgl::FileSourceType::Database, resourceOptions, clientOptions))); - // Observe for changes to the tile server options (and find out the current one). - [[MLNSettings sharedSettings] addObserver:self - forKeyPath:@"tileServerOptionsChangeToken" - options:(NSKeyValueObservingOptionInitial | - NSKeyValueObservingOptionNew) - context:NULL]; + // Observe for changes to the tile server options (and find out the current one). + [[MLNSettings sharedSettings] + addObserver:self + forKeyPath:@"tileServerOptionsChangeToken" + options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) + context:NULL]; - // Observe for changes to the global access token (and find out the current one). - [[MLNSettings sharedSettings] addObserver:self - forKeyPath:@"apiKey" - options:(NSKeyValueObservingOptionInitial | - NSKeyValueObservingOptionNew) - context:NULL]; - } - return self; + // Observe for changes to the global access token (and find out the current one). + [[MLNSettings sharedSettings] + addObserver:self + forKeyPath:@"apiKey" + options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew) + context:NULL]; + } + return self; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; - [[MLNSettings sharedSettings] removeObserver:self forKeyPath:@"tileServerOptionsChangeToken"]; - [[MLNSettings sharedSettings] removeObserver:self forKeyPath:@"apiKey"]; + [[MLNSettings sharedSettings] removeObserver:self forKeyPath:@"tileServerOptionsChangeToken"]; + [[MLNSettings sharedSettings] removeObserver:self forKeyPath:@"apiKey"]; - for (MLNOfflinePack *pack in self.packs) { - [pack invalidate]; - } + for (MLNOfflinePack *pack in self.packs) { + [pack invalidate]; + } } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - // Synchronize the file source’s access token with the global one in MLNSettings. - if ([keyPath isEqualToString:@"apiKey"] && object == [MLNSettings sharedSettings]) { - NSString *apiKey = change[NSKeyValueChangeNewKey]; - if (![apiKey isKindOfClass:[NSNull class]]) { - _mbglOnlineFileSource->setProperty(mbgl::API_KEY_KEY, apiKey.UTF8String); - } - } else if ([keyPath isEqualToString:@"tileServerOptionsChangeToken"] && object == [MLNSettings sharedSettings]) { - auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - auto apiBaseURL = tileServerOptions->baseURL(); - auto resourceOptions = _mbglOnlineFileSource->getResourceOptions().clone(); - _mbglOnlineFileSource->setResourceOptions(resourceOptions.withTileServerOptions(*tileServerOptions).clone()); - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + // Synchronize the file source’s access token with the global one in MLNSettings. + if ([keyPath isEqualToString:@"apiKey"] && object == [MLNSettings sharedSettings]) { + NSString *apiKey = change[NSKeyValueChangeNewKey]; + if (![apiKey isKindOfClass:[NSNull class]]) { + _mbglOnlineFileSource->setProperty(mbgl::API_KEY_KEY, apiKey.UTF8String); } + } else if ([keyPath isEqualToString:@"tileServerOptionsChangeToken"] && + object == [MLNSettings sharedSettings]) { + auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; + auto apiBaseURL = tileServerOptions->baseURL(); + auto resourceOptions = _mbglOnlineFileSource->getResourceOptions().clone(); + _mbglOnlineFileSource->setResourceOptions( + resourceOptions.withTileServerOptions(*tileServerOptions).clone()); + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } } // MARK: Database management methods - (NSString *)databasePath { - return self.databaseURL.path; + return self.databaseURL.path; } - (NSURL *)databaseURL { - if (!_databaseURL) { - NSString *customPath = [NSBundle.mainBundle objectForInfoDictionaryKey:MLNOfflineStorageDatabasePathInfoDictionaryKey]; - if ([customPath isKindOfClass:[NSString class]]) { - _databaseURL = [NSURL fileURLWithPath:customPath.stringByStandardizingPath isDirectory:NO relativeToURL:NSBundle.mainBundle.resourceURL]; + if (!_databaseURL) { + NSString *customPath = [NSBundle.mainBundle + objectForInfoDictionaryKey:MLNOfflineStorageDatabasePathInfoDictionaryKey]; + if ([customPath isKindOfClass:[NSString class]]) { + _databaseURL = [NSURL fileURLWithPath:customPath.stringByStandardizingPath + isDirectory:NO + relativeToURL:NSBundle.mainBundle.resourceURL]; - NSURL *directoryURL = _databaseURL.URLByDeletingLastPathComponent; - [NSFileManager.defaultManager createDirectoryAtURL:directoryURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - } else { - _databaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:YES]; - } - NSString *databasePath = self.databasePath; - NSAssert(databasePath, @"Offline pack database URL “%@” is not a valid file URL.", _databaseURL); - - // Move the offline database from v3.2.0-beta.1 to a location that can - // also be used for ambient caching. - if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { - NSString *legacyDatabasePath = [[self class] legacyDatabasePath]; - [[NSFileManager defaultManager] moveItemAtPath:legacyDatabasePath toPath:databasePath error:NULL]; - } - - // Move the offline database from v3.2.x path to a subdirectory that can - // be reliably excluded from backups. - if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { - NSURL *subdirectorylessDatabaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:NO]; - [[NSFileManager defaultManager] moveItemAtPath:subdirectorylessDatabaseURL.path toPath:databasePath error:NULL]; - } + NSURL *directoryURL = _databaseURL.URLByDeletingLastPathComponent; + [NSFileManager.defaultManager createDirectoryAtURL:directoryURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + } else { + _databaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:YES]; } - return _databaseURL; + NSString *databasePath = self.databasePath; + NSAssert(databasePath, @"Offline pack database URL “%@” is not a valid file URL.", + _databaseURL); + + // Move the offline database from v3.2.0-beta.1 to a location that can + // also be used for ambient caching. + if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { + NSString *legacyDatabasePath = [[self class] legacyDatabasePath]; + [[NSFileManager defaultManager] moveItemAtPath:legacyDatabasePath + toPath:databasePath + error:NULL]; + } + + // Move the offline database from v3.2.x path to a subdirectory that can + // be reliably excluded from backups. + if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { + NSURL *subdirectorylessDatabaseURL = + [[self class] defaultDatabaseURLIncludingSubdirectory:NO]; + [[NSFileManager defaultManager] moveItemAtPath:subdirectorylessDatabaseURL.path + toPath:databasePath + error:NULL]; + } + } + return _databaseURL; } /** @@ -269,32 +303,33 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio ~/Library/Application Support/tld.app.bundle.id/cache.db */ + (NSURL *)defaultDatabaseURLIncludingSubdirectory:(BOOL)useSubdirectory { - NSURL *databaseDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:YES - error:nil]; - NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; - if (!bundleIdentifier) { - // There’s no main bundle identifier when running in a unit test bundle. - bundleIdentifier = [[NSUUID UUID] UUIDString]; - } - databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - if (useSubdirectory) { - databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:@".mapbox"]; - } - [[NSFileManager defaultManager] createDirectoryAtURL:databaseDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:nil]; - if (useSubdirectory) { - // Avoid backing up the database onto iCloud, because it can be - // redownloaded. Ideally, we’d even put the ambient cache in Caches, so - // it can be reclaimed by the system when disk space runs low. But - // unfortunately it has to live in the same file as offline resources. - [databaseDirectoryURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; - } - return [databaseDirectoryURL URLByAppendingPathComponent:MLNOfflineStorageFileName]; + NSURL *databaseDirectoryURL = + [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:YES + error:nil]; + NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; + if (!bundleIdentifier) { + // There’s no main bundle identifier when running in a unit test bundle. + bundleIdentifier = [[NSUUID UUID] UUIDString]; + } + databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; + if (useSubdirectory) { + databaseDirectoryURL = [databaseDirectoryURL URLByAppendingPathComponent:@".mapbox"]; + } + [[NSFileManager defaultManager] createDirectoryAtURL:databaseDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:nil]; + if (useSubdirectory) { + // Avoid backing up the database onto iCloud, because it can be + // redownloaded. Ideally, we’d even put the ambient cache in Caches, so + // it can be reclaimed by the system when disk space runs low. But + // unfortunately it has to live in the same file as offline resources. + [databaseDirectoryURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:NULL]; + } + return [databaseDirectoryURL URLByAppendingPathComponent:MLNOfflineStorageFileName]; } /** @@ -303,356 +338,451 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio */ + (NSString *)legacyDatabasePath { #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - // ~/Documents/offline.db - NSArray *legacyPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *legacyDatabasePath = [legacyPaths.firstObject stringByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; + // ~/Documents/offline.db + NSArray *legacyPaths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *legacyDatabasePath = [legacyPaths.firstObject + stringByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; #elif TARGET_OS_MAC - // ~/Library/Caches/tld.app.bundle.id/offline.db - NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; - NSURL *legacyDatabaseDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:nil]; - legacyDatabaseDirectoryURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - NSURL *legacyDatabaseURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; - NSString *legacyDatabasePath = legacyDatabaseURL ? legacyDatabaseURL.path : @""; + // ~/Library/Caches/tld.app.bundle.id/offline.db + NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; + NSURL *legacyDatabaseDirectoryURL = + [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + legacyDatabaseDirectoryURL = + [legacyDatabaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; + NSURL *legacyDatabaseURL = [legacyDatabaseDirectoryURL + URLByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; + NSString *legacyDatabasePath = legacyDatabaseURL ? legacyDatabaseURL.path : @""; #endif - return legacyDatabasePath; + return legacyDatabasePath; } -- (void)addContentsOfFile:(NSString *)filePath withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { - MLNLogDebug(@"Adding contentsOfFile: %@ completionHandler: %@", filePath, completion); - NSURL *fileURL = [NSURL fileURLWithPath:filePath]; - - [self addContentsOfURL:fileURL withCompletionHandler:completion]; +- (void)addContentsOfFile:(NSString *)filePath + withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { + MLNLogDebug(@"Adding contentsOfFile: %@ completionHandler: %@", filePath, completion); + NSURL *fileURL = [NSURL fileURLWithPath:filePath]; + [self addContentsOfURL:fileURL withCompletionHandler:completion]; } -- (void)addContentsOfURL:(NSURL *)fileURL withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { - MLNLogDebug(@"Adding contentsOfURL: %@ completionHandler: %@", fileURL, completion); - NSFileManager *fileManager = [NSFileManager defaultManager]; +- (void)addContentsOfURL:(NSURL *)fileURL + withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { + MLNLogDebug(@"Adding contentsOfURL: %@ completionHandler: %@", fileURL, completion); + NSFileManager *fileManager = [NSFileManager defaultManager]; - if (!fileURL.isFileURL) { - [NSException raise:NSInvalidArgumentException format:@"%@ must be a valid file path", fileURL.absoluteString]; - } - if (![fileManager isWritableFileAtPath:fileURL.path]) { - [NSException raise:NSInvalidArgumentException format:@"The file path: %@ must be writable", fileURL.absoluteString]; - } + if (!fileURL.isFileURL) { + [NSException raise:NSInvalidArgumentException + format:@"%@ must be a valid file path", fileURL.absoluteString]; + } + if (![fileManager isWritableFileAtPath:fileURL.path]) { + [NSException raise:NSInvalidArgumentException + format:@"The file path: %@ must be writable", fileURL.absoluteString]; + } - __weak MLNOfflineStorage *weakSelf = self; - [self _addContentsOfFile:fileURL.path withCompletionHandler:^(NSArray * _Nullable packs, NSError * _Nullable error) { + __weak MLNOfflineStorage *weakSelf = self; + [self _addContentsOfFile:fileURL.path + withCompletionHandler:^(NSArray *_Nullable packs, + NSError *_Nullable error) { if (packs) { - NSMutableDictionary *packsByIdentifier = [NSMutableDictionary dictionary]; + NSMutableDictionary *packsByIdentifier = [NSMutableDictionary dictionary]; - MLNOfflineStorage *strongSelf = weakSelf; - for (MLNOfflinePack *pack in packs) { - [packsByIdentifier setObject:pack forKey:@(pack.mbglOfflineRegion->getID())]; + MLNOfflineStorage *strongSelf = weakSelf; + for (MLNOfflinePack *pack in packs) { + [packsByIdentifier setObject:pack forKey:@(pack.mbglOfflineRegion->getID())]; + } + + id mutablePacks = [strongSelf mutableArrayValueForKey:@"packs"]; + NSMutableIndexSet *replaceIndexSet = [NSMutableIndexSet indexSet]; + NSMutableArray *replacePacksArray = [NSMutableArray array]; + [strongSelf.packs enumerateObjectsUsingBlock:^(MLNOfflinePack *_Nonnull pack, + NSUInteger idx, BOOL *_Nonnull stop) { + MLNOfflinePack *newPack = packsByIdentifier[@(pack.mbglOfflineRegion->getID())]; + if (newPack) { + MLNOfflinePack *previousPack = [mutablePacks objectAtIndex:idx]; + [previousPack invalidate]; + [replaceIndexSet addIndex:idx]; + [replacePacksArray + addObject:[packsByIdentifier objectForKey:@(newPack.mbglOfflineRegion->getID())]]; + [packsByIdentifier removeObjectForKey:@(newPack.mbglOfflineRegion->getID())]; } + }]; - id mutablePacks = [strongSelf mutableArrayValueForKey:@"packs"]; - NSMutableIndexSet *replaceIndexSet = [NSMutableIndexSet indexSet]; - NSMutableArray *replacePacksArray = [NSMutableArray array]; - [strongSelf.packs enumerateObjectsUsingBlock:^(MLNOfflinePack * _Nonnull pack, NSUInteger idx, BOOL * _Nonnull stop) { - MLNOfflinePack *newPack = packsByIdentifier[@(pack.mbglOfflineRegion->getID())]; - if (newPack) { - MLNOfflinePack *previousPack = [mutablePacks objectAtIndex:idx]; - [previousPack invalidate]; - [replaceIndexSet addIndex:idx]; - [replacePacksArray addObject:[packsByIdentifier objectForKey:@(newPack.mbglOfflineRegion->getID())]]; - [packsByIdentifier removeObjectForKey:@(newPack.mbglOfflineRegion->getID())]; - } + if (replaceIndexSet.count > 0) { + [mutablePacks replaceObjectsAtIndexes:replaceIndexSet withObjects:replacePacksArray]; + } - }]; - - if (replaceIndexSet.count > 0) { - [mutablePacks replaceObjectsAtIndexes:replaceIndexSet withObjects:replacePacksArray]; - } - - [mutablePacks addObjectsFromArray:packsByIdentifier.allValues]; + [mutablePacks addObjectsFromArray:packsByIdentifier.allValues]; } if (completion) { - completion(fileURL, packs, error); + completion(fileURL, packs, error); } - }]; + }]; } -- (void)_addContentsOfFile:(NSString *)filePath withCompletionHandler:(void (^)(NSArray * _Nullable packs, NSError * _Nullable error))completion { - _mbglDatabaseFileSource->mergeOfflineRegions(std::string(static_cast([filePath UTF8String])), [&, completion, filePath](mbgl::expected result) { +- (void)_addContentsOfFile:(NSString *)filePath + withCompletionHandler:(void (^)(NSArray *_Nullable packs, + NSError *_Nullable error))completion { + _mbglDatabaseFileSource->mergeOfflineRegions( + std::string(static_cast([filePath UTF8String])), + [&, completion, filePath](mbgl::expected result) { NSError *error; NSMutableArray *packs; if (!result) { - NSString *description = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ADD_FILE_CONTENTS_FAILED_DESC", @"Foundation", nil, @"Unable to add offline packs from the file at %@.", @"User-friendly error description"), filePath]; - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed - userInfo:@{ - NSLocalizedDescriptionKey: description, - NSLocalizedFailureReasonErrorKey: @(mbgl::util::toString(result.error()).c_str()) - }]; + NSString *description = + [NSString stringWithFormat:NSLocalizedStringWithDefaultValue( + @"ADD_FILE_CONTENTS_FAILED_DESC", @"Foundation", nil, + @"Unable to add offline packs from the file at %@.", + @"User-friendly error description"), + filePath]; + error = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : description, + NSLocalizedFailureReasonErrorKey : + @(mbgl::util::toString(result.error()).c_str()) + }]; } else { - auto& regions = result.value(); - packs = [NSMutableArray arrayWithCapacity:regions.size()]; - for (auto ®ion : regions) { - MLNOfflinePack *pack = [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; - [packs addObject:pack]; - } + auto ®ions = result.value(); + packs = [NSMutableArray arrayWithCapacity:regions.size()]; + for (auto ®ion : regions) { + MLNOfflinePack *pack = [[MLNOfflinePack alloc] + initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; + [packs addObject:pack]; + } } if (completion) { - dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) { - completion(packs, error); - }); + dispatch_async(dispatch_get_main_queue(), + [&, completion, error, packs](void) { completion(packs, error); }); } - }); + }); } // MARK: Pack management methods -- (void)addPackForRegion:(id )region withContext:(NSData *)context completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { - MLNLogDebug(@"Adding packForRegion: %@ contextLength: %lu completionHandler: %@", region, (unsigned long)context.length, completion); - __weak MLNOfflineStorage *weakSelf = self; - [self _addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { - pack.state = MLNOfflinePackStateInactive; - MLNOfflineStorage *strongSelf = weakSelf; - [[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack]; - if (completion) { +- (void)addPackForRegion:(id)region + withContext:(NSData *)context + completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { + MLNLogDebug(@"Adding packForRegion: %@ contextLength: %lu completionHandler: %@", region, + (unsigned long)context.length, completion); + __weak MLNOfflineStorage *weakSelf = self; + [self _addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) { + pack.state = MLNOfflinePackStateInactive; + MLNOfflineStorage *strongSelf = weakSelf; + [[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack]; + if (completion) { completion(pack, error); - } - }]; + } + }]; } -- (void)_addPackForRegion:(id )region withContext:(NSData *)context completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { - if (![region conformsToProtocol:@protocol(MLNOfflineRegion_Private)]) { - [NSException raise:MLNUnsupportedRegionTypeException - format:@"Regions of type %@ are unsupported.", NSStringFromClass([region class])]; - return; - } +- (void)_addPackForRegion:(id)region + withContext:(NSData *)context + completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { + if (![region conformsToProtocol:@protocol(MLNOfflineRegion_Private)]) { + [NSException raise:MLNUnsupportedRegionTypeException + format:@"Regions of type %@ are unsupported.", NSStringFromClass([region class])]; + return; + } - const mbgl::OfflineRegionDefinition regionDefinition = [(id )region offlineRegionDefinition]; - mbgl::OfflineRegionMetadata metadata(context.length); - [context getBytes:&metadata[0] length:metadata.size()]; - _mbglDatabaseFileSource->createOfflineRegion(regionDefinition, metadata, [&, completion](mbgl::expected mbglOfflineRegion) { + const mbgl::OfflineRegionDefinition regionDefinition = + [(id)region offlineRegionDefinition]; + mbgl::OfflineRegionMetadata metadata(context.length); + [context getBytes:&metadata[0] length:metadata.size()]; + _mbglDatabaseFileSource->createOfflineRegion( + regionDefinition, metadata, + [&, completion](mbgl::expected mbglOfflineRegion) { NSError *error; if (!mbglOfflineRegion) { - NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str()); - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{ - NSLocalizedDescriptionKey: errorDescription, - } : nil]; + NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str()); + error = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:errorDescription ? @{ + NSLocalizedDescriptionKey : errorDescription, + } + : nil]; } if (completion) { - MLNOfflinePack *pack = mbglOfflineRegion ? [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(mbglOfflineRegion.value()))] : nil; - dispatch_async(dispatch_get_main_queue(), [&, completion, error, pack](void) { - completion(pack, error); - }); + MLNOfflinePack *pack = + mbglOfflineRegion + ? [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move( + mbglOfflineRegion.value()))] + : nil; + dispatch_async(dispatch_get_main_queue(), + [&, completion, error, pack](void) { completion(pack, error); }); } - }); + }); } -- (void)removePack:(MLNOfflinePack *)pack withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion { - MLNLogDebug(@"Removing pack: %@ completionHandler: %@", pack, completion); - [[self mutableArrayValueForKey:@"packs"] removeObject:pack]; - [self _removePack:pack withCompletionHandler:^(NSError * _Nullable error) { +- (void)removePack:(MLNOfflinePack *)pack + withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion { + MLNLogDebug(@"Removing pack: %@ completionHandler: %@", pack, completion); + [[self mutableArrayValueForKey:@"packs"] removeObject:pack]; + [self _removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { if (completion) { - completion(error); + completion(error); } - }]; + }]; } -- (void)_removePack:(MLNOfflinePack *)pack withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion { - mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion; +- (void)_removePack:(MLNOfflinePack *)pack + withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion { + mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion; - [pack invalidate]; + [pack invalidate]; - if (!mbglOfflineRegion) { - MLNAssert(pack.state == MLNOfflinePackStateInvalid, @"State should be invalid"); - completion(nil); - return; - } + if (!mbglOfflineRegion) { + MLNAssert(pack.state == MLNOfflinePackStateInvalid, @"State should be invalid"); + completion(nil); + return; + } - _mbglDatabaseFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) { + _mbglDatabaseFileSource->deleteOfflineRegion( + std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) { NSError *error; if (exception) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; } if (completion) { - dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { - completion(error); - }); + dispatch_async(dispatch_get_main_queue(), + [&, completion, error](void) { completion(error); }); } - }); - + }); } -- (void)invalidatePack:(MLNOfflinePack *)pack withCompletionHandler:(void (^)(NSError * _Nullable))completion { - mbgl::OfflineRegion& region = *pack.mbglOfflineRegion; - NSError *error; - if (!pack.mbglOfflineRegion) { - completion(nil); - return; - } +- (void)invalidatePack:(MLNOfflinePack *)pack + withCompletionHandler:(void (^)(NSError *_Nullable))completion { + mbgl::OfflineRegion ®ion = *pack.mbglOfflineRegion; + NSError *error; + if (!pack.mbglOfflineRegion) { + completion(nil); + return; + } - _mbglDatabaseFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) { - if (exception) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; - } - }); - if (completion) { - dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { - completion(error); - }); + _mbglDatabaseFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) { + if (exception) { + error = + [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; } + }); + if (completion) { + dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { completion(error); }); + } } - (void)reloadPacks { - MLNLogInfo(@"Reloading packs."); - [self getPacksWithCompletionHandler:^(NSArray *packs, __unused NSError * _Nullable error) { - for (MLNOfflinePack *pack in self.packs) { - [pack invalidate]; - } - self.packs = [packs mutableCopy]; - }]; + MLNLogInfo(@"Reloading packs."); + [self getPacksWithCompletionHandler:^(NSArray *packs, + __unused NSError *_Nullable error) { + for (MLNOfflinePack *pack in self.packs) { + [pack invalidate]; + } + self.packs = [packs mutableCopy]; + }]; } -- (void)getPacksWithCompletionHandler:(void (^)(NSArray *packs, NSError * _Nullable error))completion { - _mbglDatabaseFileSource->listOfflineRegions([&, completion](mbgl::expected result) { +- (void)getPacksWithCompletionHandler:(void (^)(NSArray *packs, + NSError *_Nullable error))completion { + _mbglDatabaseFileSource->listOfflineRegions( + [&, completion](mbgl::expected result) { NSError *error; NSMutableArray *packs; if (!result) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(result.error()).c_str()), - }]; + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeUnknown + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(result.error()).c_str()), + }]; } else { - auto& regions = result.value(); - packs = [NSMutableArray arrayWithCapacity:regions.size()]; - for (auto ®ion : regions) { - MLNOfflinePack *pack = [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; - [packs addObject:pack]; - } + auto ®ions = result.value(); + packs = [NSMutableArray arrayWithCapacity:regions.size()]; + for (auto ®ion : regions) { + MLNOfflinePack *pack = [[MLNOfflinePack alloc] + initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; + [packs addObject:pack]; + } } if (completion) { - dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) { - completion(packs, error); - }); + dispatch_async(dispatch_get_main_queue(), + [&, completion, error, packs](void) { completion(packs, error); }); } - }); + }); } - (void)setMaximumAllowedMapboxTiles:(uint64_t)maximumCount { - MLNLogDebug(@"Setting maximumAllowedMapboxTiles: %lu", (unsigned long)maximumCount); - _mbglDatabaseFileSource->setOfflineMapboxTileCountLimit(maximumCount); + MLNLogDebug(@"Setting maximumAllowedMapboxTiles: %lu", (unsigned long)maximumCount); + _mbglDatabaseFileSource->setOfflineMapboxTileCountLimit(maximumCount); } // MARK: - Ambient cache management -- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError * _Nullable))completion { - _mbglDatabaseFileSource->setMaximumAmbientCacheSize(cacheSize, [&, completion](std::exception_ptr exception) { +- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize + withCompletionHandler:(void (^)(NSError *_Nullable))completion { + _mbglDatabaseFileSource->setMaximumAmbientCacheSize( + cacheSize, [&, completion](std::exception_ptr exception) { NSError *error; if (completion) { - if (exception) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; - } - dispatch_sync(dispatch_get_main_queue(), ^ { - completion(error); - }); + if (exception) { + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; + } + dispatch_sync(dispatch_get_main_queue(), ^{ + completion(error); + }); } - }); + }); } - (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable))completion { - _mbglDatabaseFileSource->invalidateAmbientCache([&, completion](std::exception_ptr exception){ - NSError *error; - if (completion) { - if (exception) { - // Convert std::exception_ptr to an NSError. - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; - } - dispatch_async(dispatch_get_main_queue(), ^ { - completion(error); - }); - } - }); + _mbglDatabaseFileSource->invalidateAmbientCache([&, completion](std::exception_ptr exception) { + NSError *error; + if (completion) { + if (exception) { + // Convert std::exception_ptr to an NSError. + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + }); } - (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion { - _mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception){ - NSError *error; - if (completion) { - if (exception) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; - } - dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { - completion(error); - }); - } - }); + _mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception) { + NSError *error; + if (completion) { + if (exception) { + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeModifyingOfflineStorageFailed + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; + } + dispatch_async(dispatch_get_main_queue(), + [&, completion, error](void) { completion(error); }); + } + }); } - (void)resetDatabaseWithCompletionHandler:(void (^)(NSError *_Nullable error))completion { - _mbglDatabaseFileSource->resetDatabase([&, completion](std::exception_ptr exception) { - NSError *error; - if (completion) { - if (exception) { - error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ - NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), - }]; - } - dispatch_async(dispatch_get_main_queue(), ^{ - completion(error); - }); - } - }); + _mbglDatabaseFileSource->resetDatabase([&, completion](std::exception_ptr exception) { + NSError *error; + if (completion) { + if (exception) { + error = [NSError + errorWithDomain:MLNErrorDomain + code:MLNErrorCodeUnknown + userInfo:@{ + NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()), + }]; + } + dispatch_async(dispatch_get_main_queue(), ^{ + completion(error); + }); + } + }); } // MARK: - - (unsigned long long)countOfBytesCompleted { - NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.databasePath error:NULL]; - return attributes.fileSize; + NSDictionary *attributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:self.databasePath error:NULL]; + return attributes.fileSize; } -- (void)preloadData:(NSData *)data forURL:(NSURL *)url modificationDate:(nullable NSDate *)modified expirationDate:(nullable NSDate *)expires eTag:(nullable NSString *)eTag mustRevalidate:(BOOL)mustRevalidate { - [self preloadData:data forURL:url modificationDate:modified expirationDate:expires eTag:eTag mustRevalidate:mustRevalidate completionHandler:nil]; +- (void)preloadData:(NSData *)data + forURL:(NSURL *)url + modificationDate:(nullable NSDate *)modified + expirationDate:(nullable NSDate *)expires + eTag:(nullable NSString *)eTag + mustRevalidate:(BOOL)mustRevalidate { + [self preloadData:data + forURL:url + modificationDate:modified + expirationDate:expires + eTag:eTag + mustRevalidate:mustRevalidate + completionHandler:nil]; } -- (void)preloadData:(NSData *)data forURL:(NSURL *)url modificationDate:(nullable NSDate *)modified expirationDate:(nullable NSDate *)expires eTag:(nullable NSString *)eTag mustRevalidate:(BOOL)mustRevalidate +- (void)preloadData:(NSData *)data + forURL:(NSURL *)url + modificationDate:(nullable NSDate *)modified + expirationDate:(nullable NSDate *)expires + eTag:(nullable NSString *)eTag + mustRevalidate:(BOOL)mustRevalidate completionHandler:(nullable MLNOfflinePreloadDataCompletionHandler)completion { - mbgl::Resource resource(mbgl::Resource::Kind::Unknown, url.absoluteString.UTF8String); - mbgl::Response response; - response.data = std::make_shared(static_cast(data.bytes), data.length); - response.mustRevalidate = mustRevalidate; + mbgl::Resource resource(mbgl::Resource::Kind::Unknown, url.absoluteString.UTF8String); + mbgl::Response response; + response.data = std::make_shared(static_cast(data.bytes), data.length); + response.mustRevalidate = mustRevalidate; - if (eTag) { - response.etag = std::string(eTag.UTF8String); - } + if (eTag) { + response.etag = std::string(eTag.UTF8String); + } - if (modified) { - response.modified = mbgl::Timestamp() + std::chrono::duration_cast(MLNDurationFromTimeInterval(modified.timeIntervalSince1970)); - } + if (modified) { + response.modified = + mbgl::Timestamp() + std::chrono::duration_cast( + MLNDurationFromTimeInterval(modified.timeIntervalSince1970)); + } - if (expires) { - response.expires = mbgl::Timestamp() + std::chrono::duration_cast(MLNDurationFromTimeInterval(expires.timeIntervalSince1970)); - } + if (expires) { + response.expires = + mbgl::Timestamp() + std::chrono::duration_cast( + MLNDurationFromTimeInterval(expires.timeIntervalSince1970)); + } - std::function callback; + std::function callback; - if (completion) { - callback = [completion, url] { - dispatch_async(dispatch_get_main_queue(), [completion, url] { completion(url, nil); }); - }; - } + if (completion) { + callback = [completion, url] { + dispatch_async(dispatch_get_main_queue(), [completion, url] { completion(url, nil); }); + }; + } - _mbglDatabaseFileSource->forward(resource, response, callback); + _mbglDatabaseFileSource->forward(resource, response, callback); } -- (void)putResourceWithUrl:(NSURL *)url data:(NSData *)data modified:(nullable NSDate *)modified expires:(nullable NSDate *)expires etag:(nullable NSString *)etag mustRevalidate:(BOOL)mustRevalidate { - [self preloadData:data forURL:url modificationDate:modified expirationDate:expires eTag:etag mustRevalidate:mustRevalidate]; +- (void)putResourceWithUrl:(NSURL *)url + data:(NSData *)data + modified:(nullable NSDate *)modified + expires:(nullable NSDate *)expires + etag:(nullable NSString *)etag + mustRevalidate:(BOOL)mustRevalidate { + [self preloadData:data + forURL:url + modificationDate:modified + expirationDate:expires + eTag:etag + mustRevalidate:mustRevalidate]; } @end diff --git a/platform/darwin/src/MLNPluginLayer.mm b/platform/darwin/src/MLNPluginLayer.mm index cff0044d0..98d260522 100644 --- a/platform/darwin/src/MLNPluginLayer.mm +++ b/platform/darwin/src/MLNPluginLayer.mm @@ -2,42 +2,41 @@ @implementation MLNPluginLayerProperty -+(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName - propertyType:(MLNPluginLayerPropertyType)propertyType - defaultValue:(id)defaultValue { - MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init]; - tempResult.propertyName = propertyName; - tempResult.propertyType = propertyType; ++ (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName + propertyType:(MLNPluginLayerPropertyType)propertyType + defaultValue:(id)defaultValue { + MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init]; + tempResult.propertyName = propertyName; + tempResult.propertyType = propertyType; - if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) { - if ([defaultValue isKindOfClass:[NSNumber class]]) { - tempResult.singleFloatDefaultValue = [defaultValue floatValue]; - } - } else if (propertyType == MLNPluginLayerPropertyTypeColor) { -#if TARGET_OS_IPHONE - if ([defaultValue isKindOfClass:[UIColor class]]) { -#else - if ([defaultValue isKindOfClass:[NSColor class]]) { -#endif - tempResult.colorDefaultValue = defaultValue; - } + if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) { + if ([defaultValue isKindOfClass:[NSNumber class]]) { + tempResult.singleFloatDefaultValue = [defaultValue floatValue]; } + } else if (propertyType == MLNPluginLayerPropertyTypeColor) { +#if TARGET_OS_IPHONE + if ([defaultValue isKindOfClass:[UIColor class]]) { +#else + if ([defaultValue isKindOfClass:[NSColor class]]) { +#endif + tempResult.colorDefaultValue = defaultValue; + } + } - return tempResult; + return tempResult; } +- (id)init { + // Base class implemenation + if (self = [super init]) { + // Default setup + self.propertyType = MLNPluginLayerPropertyTypeUnknown; + self.propertyName = @"unknown"; --(id)init { - // Base class implemenation - if (self = [super init]) { - // Default setup - self.propertyType = MLNPluginLayerPropertyTypeUnknown; - self.propertyName = @"unknown"; - - // Default values for the various types - self.singleFloatDefaultValue = 1.0; - } - return self; + // Default values for the various types + self.singleFloatDefaultValue = 1.0; + } + return self; } @end @@ -45,36 +44,31 @@ @implementation MLNPluginLayer // This is the layer type in the style that is used -+(MLNPluginLayerCapabilities *)layerCapabilities { - - // Base class returns the class name just to return something - // TODO: Add an assert/etc or something to notify the developer that this needs to be overridden - return nil; - ++ (MLNPluginLayerCapabilities *)layerCapabilities { + // Base class returns the class name just to return something + // TODO: Add an assert/etc or something to notify the developer that this needs to be overridden + return nil; } - (void)onRenderLayer:(MLNMapView *)mapView renderEncoder:(id)renderEncoder { - // Base class does nothing + // Base class does nothing } - (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { - // Base class does nothing + // Base class does nothing } --(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - // Base class does nothing +- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties { + // Base class does nothing } /// Added to a map view - (void)didMoveToMapView:(MLNMapView *)mapView { - // Base class does nothing + // Base class does nothing } - @end - - @implementation MLNPluginLayerCapabilities @end diff --git a/platform/darwin/src/MLNPluginStyleLayer.mm b/platform/darwin/src/MLNPluginStyleLayer.mm index c8dc03778..6bda2c204 100644 --- a/platform/darwin/src/MLNPluginStyleLayer.mm +++ b/platform/darwin/src/MLNPluginStyleLayer.mm @@ -1,35 +1,28 @@ #import "MLNPluginStyleLayer.h" -#import "MLNPluginStyleLayer_Private.h" #import #import #import "MLNPluginLayer.h" +#import "MLNPluginStyleLayer_Private.h" @implementation MLNPluginStyleLayer --(void)getStats { - - mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; - auto pl = l->impl(); - +- (void)getStats { + mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; + auto pl = l->impl(); } --(MLNPluginLayer *)pluginLayer { - - mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; - if (l->_platformReference) { - MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference; - return pl; - } - - return nil; +- (MLNPluginLayer *)pluginLayer { + mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; + if (l->_platformReference) { + MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference; + return pl; + } + return nil; } - @end - -MLNStyleLayer* mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) { - return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer]; - +MLNStyleLayer *mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) { + return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer]; } diff --git a/platform/darwin/src/MLNPointAnnotation.mm b/platform/darwin/src/MLNPointAnnotation.mm index 8b7561da6..a1bb1b387 100644 --- a/platform/darwin/src/MLNPointAnnotation.mm +++ b/platform/darwin/src/MLNPointAnnotation.mm @@ -1,71 +1,66 @@ #import "MLNPointAnnotation.h" +#import "MLNLoggingConfiguration_Private.h" #import "MLNShape_Private.h" #import "NSCoder+MLNAdditions.h" -#import "MLNLoggingConfiguration_Private.h" #import - @implementation MLNPointAnnotation @synthesize coordinate; -+ (BOOL)supportsSecureCoding -{ - return YES; ++ (BOOL)supportsSecureCoding { + return YES; } -- (instancetype)initWithCoder:(NSCoder *)coder -{ - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:coder]) { - self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"]; - } - return self; +- (instancetype)initWithCoder:(NSCoder *)coder { + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:coder]) { + self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"]; + } + return self; } -- (void)encodeWithCoder:(NSCoder *)coder -{ - [super encodeWithCoder:coder]; - [coder encodeMLNCoordinate:coordinate forKey:@"coordinate"]; +- (void)encodeWithCoder:(NSCoder *)coder { + [super encodeWithCoder:coder]; + [coder encodeMLNCoordinate:coordinate forKey:@"coordinate"]; } -- (BOOL)isEqual:(id)other -{ - if (other == self) return YES; - if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO; +- (BOOL)isEqual:(id)other { + if (other == self) return YES; + if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO; - MLNPointAnnotation *otherAnnotation = other; - return ([super isEqual:other] - && self.coordinate.latitude == otherAnnotation.coordinate.latitude - && self.coordinate.longitude == otherAnnotation.coordinate.longitude); + MLNPointAnnotation *otherAnnotation = other; + return ([super isEqual:other] && + self.coordinate.latitude == otherAnnotation.coordinate.latitude && + self.coordinate.longitude == otherAnnotation.coordinate.longitude); } -- (NSUInteger)hash -{ - return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash; +- (NSUInteger)hash { + return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash; } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>", - NSStringFromClass([self class]), (void *)self, - self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, - self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, - coordinate.latitude, coordinate.longitude]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] + : self.subtitle, + coordinate.latitude, coordinate.longitude]; } -- (NSDictionary *)geoJSONDictionary -{ - return @{@"type": @"Point", - @"coordinates": @[@(self.coordinate.longitude), @(self.coordinate.latitude)]}; +- (NSDictionary *)geoJSONDictionary { + return @{ + @"type" : @"Point", + @"coordinates" : @[ @(self.coordinate.longitude), @(self.coordinate.latitude) ] + }; } -- (mbgl::Geometry)geometryObject -{ - mbgl::Point point = { self.coordinate.longitude, self.coordinate.latitude }; - return point; +- (mbgl::Geometry)geometryObject { + mbgl::Point point = {self.coordinate.longitude, self.coordinate.latitude}; + return point; } @end diff --git a/platform/darwin/src/MLNPointCollection.mm b/platform/darwin/src/MLNPointCollection.mm index a95845e34..a26d9ed90 100644 --- a/platform/darwin/src/MLNPointCollection.mm +++ b/platform/darwin/src/MLNPointCollection.mm @@ -1,139 +1,131 @@ -#import "MLNPointCollection_Private.h" #import "MLNGeometry_Private.h" -#import "NSArray+MLNAdditions.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNPointCollection_Private.h" +#import "NSArray+MLNAdditions.h" #import #import NS_ASSUME_NONNULL_BEGIN -@implementation MLNPointCollection -{ - std::optional _bounds; - std::vector _coordinates; +@implementation MLNPointCollection { + std::optional _bounds; + std::vector _coordinates; } + (BOOL)supportsSecureCoding { - return YES; + return YES; } -+ (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count -{ - return [[self alloc] initWithCoordinates:coords count:count]; ++ (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count { + return [[self alloc] initWithCoordinates:coords count:count]; } -- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count -{ - MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count); - self = [super init]; - if (self) - { - _coordinates = { coords, coords + count }; - } - return self; +- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { + MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count); + self = [super init]; + if (self) { + _coordinates = {coords, coords + count}; + } + return self; } - (nullable instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializing with coder.O"); - if (self = [super initWithCoder:decoder]) { - NSSet *coordinatesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class]]]; - NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"]; - _coordinates = [coordinates mgl_coordinates]; - } - return self; + MLNLogInfo(@"Initializing with coder.O"); + if (self = [super initWithCoder:decoder]) { + NSSet *coordinatesClasses = + [NSSet setWithArray:@[ [NSDictionary class], [NSArray class] ]]; + NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"]; + _coordinates = [coordinates mgl_coordinates]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"]; + [super encodeWithCoder:coder]; + [coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"]; } - (BOOL)isEqual:(id)other { - if (self == other) return YES; - if (![other isKindOfClass:[MLNPointCollection class]]) return NO; + if (self == other) return YES; + if (![other isKindOfClass:[MLNPointCollection class]]) return NO; - MLNPointCollection *otherCollection = (MLNPointCollection *)other; - return ([super isEqual:other] - && ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) || [[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]])); + MLNPointCollection *otherCollection = (MLNPointCollection *)other; + return ([super isEqual:other] && + ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) || + [[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]])); } - (MLNCoordinateBounds)overlayBounds { - if (!_bounds) { - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (auto coordinate : _coordinates) { - if (!MLNLocationCoordinate2DIsValid(coordinate)) { - bounds = mbgl::LatLngBounds::empty(); - break; - } - bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate)); - } - _bounds = bounds; + if (!_bounds) { + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + for (auto coordinate : _coordinates) { + if (!MLNLocationCoordinate2DIsValid(coordinate)) { + bounds = mbgl::LatLngBounds::empty(); + break; + } + bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate)); } - return MLNCoordinateBoundsFromLatLngBounds(*_bounds); + _bounds = bounds; + } + return MLNCoordinateBoundsFromLatLngBounds(*_bounds); } -- (NSUInteger)pointCount -{ - return _coordinates.size(); +- (NSUInteger)pointCount { + return _coordinates.size(); } -- (CLLocationCoordinate2D *)coordinates -{ - return _coordinates.data(); +- (CLLocationCoordinate2D *)coordinates { + return _coordinates.data(); } -- (CLLocationCoordinate2D)coordinate -{ - MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); - return _coordinates.at(0); +- (CLLocationCoordinate2D)coordinate { + MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); + return _coordinates.at(0); } -- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range -{ - if (range.location + range.length > [self pointCount]) - { - [NSException raise:NSRangeException - format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", - NSStringFromRange(range), (unsigned long)[self pointCount]]; - } +- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range { + if (range.location + range.length > [self pointCount]) { + [NSException + raise:NSRangeException + format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", + NSStringFromRange(range), (unsigned long)[self pointCount]]; + } - std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), coords); + std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), + coords); } -- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds -{ - return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); +- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { + return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); } -- (mbgl::Geometry)geometryObject -{ - mbgl::MultiPoint multiPoint; - multiPoint.reserve(self.pointCount); - for (NSUInteger i = 0; i < self.pointCount; i++) - { - multiPoint.push_back(mbgl::Point(self.coordinates[i].longitude, self.coordinates[i].latitude)); - } - return multiPoint; +- (mbgl::Geometry)geometryObject { + mbgl::MultiPoint multiPoint; + multiPoint.reserve(self.pointCount); + for (NSUInteger i = 0; i < self.pointCount; i++) { + multiPoint.push_back( + mbgl::Point(self.coordinates[i].longitude, self.coordinates[i].latitude)); + } + return multiPoint; } -- (NSDictionary *)geoJSONDictionary -{ - NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; - for (NSUInteger index = 0; index < self.pointCount; index++) { - CLLocationCoordinate2D coordinate = self.coordinates[index]; - [coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; - } +- (NSDictionary *)geoJSONDictionary { + NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; + for (NSUInteger index = 0; index < self.pointCount; index++) { + CLLocationCoordinate2D coordinate = self.coordinates[index]; + [coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]]; + } - return @{@"type": @"MultiPoint", - @"coordinates": coordinates}; + return @{@"type" : @"MultiPoint", @"coordinates" : coordinates}; } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", - NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], - MLNStringFromCoordinateBounds(self.overlayBounds)]; +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + (unsigned long)[self pointCount], + MLNStringFromCoordinateBounds(self.overlayBounds)]; } @end diff --git a/platform/darwin/src/MLNPolygon.mm b/platform/darwin/src/MLNPolygon.mm index d3818e2d6..1b58cba6b 100644 --- a/platform/darwin/src/MLNPolygon.mm +++ b/platform/darwin/src/MLNPolygon.mm @@ -1,135 +1,140 @@ #import "MLNPolygon_Private.h" -#import "MLNMultiPoint_Private.h" #import "MLNGeometry_Private.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNMultiPoint_Private.h" #import "MLNFeature.h" -#import #import +#import @implementation MLNPolygon @dynamic overlayBounds; + (BOOL)supportsSecureCoding { - return YES; + return YES; } -+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { - return [self polygonWithCoordinates:coords count:count interiorPolygons:nil]; ++ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count { + return [self polygonWithCoordinates:coords count:count interiorPolygons:nil]; } -+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray *)interiorPolygons { - return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons]; ++ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count + interiorPolygons:(NSArray *)interiorPolygons { + return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons]; } -- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray *)interiorPolygons { - MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count, (unsigned long)interiorPolygons); - if (self = [super initWithCoordinates:coords count:count]) { - if (interiorPolygons.count) { - _interiorPolygons = interiorPolygons; - } +- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords + count:(NSUInteger)count + interiorPolygons:(NSArray *)interiorPolygons { + MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count, + (unsigned long)interiorPolygons); + if (self = [super initWithCoordinates:coords count:count]) { + if (interiorPolygons.count) { + _interiorPolygons = interiorPolygons; } - return self; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializng with coder."); - self = [super initWithCoder:decoder]; - if (self) { - NSSet *polygonClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]]; - _interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"]; - } - return self; + MLNLogInfo(@"Initializng with coder."); + self = [super initWithCoder:decoder]; + if (self) { + NSSet *polygonClasses = + [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]]; + _interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"]; + [super encodeWithCoder:coder]; + [coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"]; } - (BOOL)isEqual:(id)other { - if (self == other) return YES; - if (![other isKindOfClass:[MLNPolygon class]]) return NO; + if (self == other) return YES; + if (![other isKindOfClass:[MLNPolygon class]]) return NO; - MLNPolygon *otherPolygon = (MLNPolygon *)other; - return ([super isEqual:otherPolygon] && - [[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]); + MLNPolygon *otherPolygon = (MLNPolygon *)other; + return ([super isEqual:otherPolygon] && + [[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]); } - (NSUInteger)hash { - return [super hash] + [[self geoJSONDictionary] hash]; + return [super hash] + [[self geoJSONDictionary] hash]; } - (CLLocationCoordinate2D)coordinate { - // pole of inaccessibility - auto poi = mapbox::polylabel([self polygon]); + // pole of inaccessibility + auto poi = mapbox::polylabel([self polygon]); - return MLNLocationCoordinate2DFromPoint(poi); + return MLNLocationCoordinate2DFromPoint(poi); } - (mbgl::LinearRing)ring { - NSUInteger count = self.pointCount; - CLLocationCoordinate2D *coordinates = self.coordinates; + NSUInteger count = self.pointCount; + CLLocationCoordinate2D *coordinates = self.coordinates; - mbgl::LinearRing result; - result.reserve(self.pointCount); - for (NSUInteger i = 0; i < count; i++) { - result.push_back(mbgl::Point(coordinates[i].longitude, coordinates[i].latitude)); - } - return result; + mbgl::LinearRing result; + result.reserve(self.pointCount); + for (NSUInteger i = 0; i < count; i++) { + result.push_back(mbgl::Point(coordinates[i].longitude, coordinates[i].latitude)); + } + return result; } - (mbgl::Polygon)polygon { - mbgl::Polygon geometry; - geometry.push_back(self.ring); - for (MLNPolygon *polygon in self.interiorPolygons) { - geometry.push_back(polygon.ring); - } - return geometry; + mbgl::Polygon geometry; + geometry.push_back(self.ring); + for (MLNPolygon *polygon in self.interiorPolygons) { + geometry.push_back(polygon.ring); + } + return geometry; } - (mbgl::Geometry)geometryObject { - return [self polygon]; + return [self polygon]; } -- (mbgl::Annotation)annotationObjectWithDelegate:(id )delegate { +- (mbgl::Annotation)annotationObjectWithDelegate:(id)delegate { + mbgl::FillAnnotation annotation{[self polygon]}; + annotation.opacity = {static_cast([delegate alphaForShapeAnnotation:self])}; + annotation.outlineColor = {[delegate strokeColorForShapeAnnotation:self]}; + annotation.color = {[delegate fillColorForPolygonAnnotation:self]}; - mbgl::FillAnnotation annotation { [self polygon] }; - annotation.opacity = { static_cast([delegate alphaForShapeAnnotation:self]) }; - annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] }; - annotation.color = { [delegate fillColorForPolygonAnnotation:self] }; - - return annotation; + return annotation; } - (NSDictionary *)geoJSONDictionary { - return @{@"type": @"Polygon", - @"coordinates": self.mgl_coordinates}; + return @{@"type" : @"Polygon", @"coordinates" : self.mgl_coordinates}; } - (NSArray *)mgl_coordinates { - NSMutableArray *coordinates = [NSMutableArray array]; + NSMutableArray *coordinates = [NSMutableArray array]; - NSMutableArray *exteriorRing = [NSMutableArray array]; - for (NSUInteger index = 0; index < self.pointCount; index++) { - CLLocationCoordinate2D coordinate = self.coordinates[index]; - [exteriorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; + NSMutableArray *exteriorRing = [NSMutableArray array]; + for (NSUInteger index = 0; index < self.pointCount; index++) { + CLLocationCoordinate2D coordinate = self.coordinates[index]; + [exteriorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]]; + } + [coordinates addObject:exteriorRing]; + + for (MLNPolygon *interiorPolygon in self.interiorPolygons) { + NSMutableArray *interiorRing = [NSMutableArray array]; + for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) { + CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index]; + [interiorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]]; } - [coordinates addObject:exteriorRing]; + [coordinates addObject:interiorRing]; + } - for (MLNPolygon *interiorPolygon in self.interiorPolygons) { - NSMutableArray *interiorRing = [NSMutableArray array]; - for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) { - CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index]; - [interiorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; - } - [coordinates addObject:interiorRing]; - } - - return [coordinates copy]; + return [coordinates copy]; } @end @@ -141,117 +146,117 @@ @end @implementation MLNMultiPolygon { - MLNCoordinateBounds _overlayBounds; + MLNCoordinateBounds _overlayBounds; } @synthesize overlayBounds = _overlayBounds; + (BOOL)supportsSecureCoding { - return YES; + return YES; } + (instancetype)multiPolygonWithPolygons:(NSArray *)polygons { - return [[self alloc] initWithPolygons:polygons]; + return [[self alloc] initWithPolygons:polygons]; } - (instancetype)initWithPolygons:(NSArray *)polygons { - MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count); - if (self = [super init]) { - _polygons = polygons; + MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count); + if (self = [super init]) { + _polygons = polygons; - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (MLNPolygon *polygon in _polygons) { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); - } - _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + for (MLNPolygon *polygon in _polygons) { + bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); } - return self; + _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + } + return self; } - (instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:decoder]) { - NSSet *polygonsClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]]; - _polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"]; + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:decoder]) { + NSSet *polygonsClasses = + [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]]; + _polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"]; - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (MLNPolygon *polygon in _polygons) { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); - } - _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + for (MLNPolygon *polygon in _polygons) { + bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); } - return self; + _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:_polygons forKey:@"polygons"]; + [super encodeWithCoder:coder]; + [coder encodeObject:_polygons forKey:@"polygons"]; } - (BOOL)isEqual:(id)other { - if (self == other) return YES; - if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO; + if (self == other) return YES; + if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO; - MLNMultiPolygon *otherMultiPolygon = other; - return [super isEqual:other] - && [self.polygons isEqualToArray:otherMultiPolygon.polygons]; + MLNMultiPolygon *otherMultiPolygon = other; + return [super isEqual:other] && [self.polygons isEqualToArray:otherMultiPolygon.polygons]; } - (NSUInteger)hash { - NSUInteger hash = [super hash]; - for (MLNPolygon *polygon in self.polygons) { - hash += [polygon hash]; - } - return hash; + NSUInteger hash = [super hash]; + for (MLNPolygon *polygon in self.polygons) { + hash += [polygon hash]; + } + return hash; } - (CLLocationCoordinate2D)coordinate { - MLNPolygon *firstPolygon = self.polygons.firstObject; + MLNPolygon *firstPolygon = self.polygons.firstObject; - return firstPolygon.coordinate; + return firstPolygon.coordinate; } - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { - return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); + return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); } - (mbgl::MultiPolygon)multiPolygon { - mbgl::MultiPolygon multiPolygon; - multiPolygon.reserve(self.polygons.count); - for (MLNPolygon *polygon in self.polygons) { - mbgl::Polygon geometry; - geometry.push_back(polygon.ring); - for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) { - geometry.push_back(interiorPolygon.ring); - } - multiPolygon.push_back(geometry); + mbgl::MultiPolygon multiPolygon; + multiPolygon.reserve(self.polygons.count); + for (MLNPolygon *polygon in self.polygons) { + mbgl::Polygon geometry; + geometry.push_back(polygon.ring); + for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) { + geometry.push_back(interiorPolygon.ring); } - return multiPolygon; + multiPolygon.push_back(geometry); + } + return multiPolygon; } - (mbgl::Geometry)geometryObject { - return [self multiPolygon]; + return [self multiPolygon]; } - (NSDictionary *)geoJSONDictionary { - NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count]; - for (MLNPolygonFeature *feature in self.polygons) { - [coordinates addObject: feature.mgl_coordinates]; - } - return @{@"type": @"MultiPolygon", - @"coordinates": coordinates}; + NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count]; + for (MLNPolygonFeature *feature in self.polygons) { + [coordinates addObject:feature.mgl_coordinates]; + } + return @{@"type" : @"MultiPolygon", @"coordinates" : coordinates}; } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", - NSStringFromClass([self class]), (void *)self, - self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, - self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, - (unsigned long)self.polygons.count, - MLNStringFromCoordinateBounds(self.overlayBounds)]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] + : self.subtitle, + (unsigned long)self.polygons.count, + MLNStringFromCoordinateBounds(self.overlayBounds)]; } @end diff --git a/platform/darwin/src/MLNPolyline.mm b/platform/darwin/src/MLNPolyline.mm index 7b0a191ad..a4a49c023 100644 --- a/platform/darwin/src/MLNPolyline.mm +++ b/platform/darwin/src/MLNPolyline.mm @@ -1,128 +1,125 @@ #import "MLNPolyline_Private.h" -#import "MLNMultiPoint_Private.h" #import "MLNGeometry_Private.h" +#import "MLNMultiPoint_Private.h" #import "MLNFeature.h" #import "MLNLoggingConfiguration_Private.h" -#import #import +#import @implementation MLNPolyline @dynamic overlayBounds; -+ (BOOL)supportsSecureCoding -{ - return YES; ++ (BOOL)supportsSecureCoding { + return YES; } + (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords - count:(NSUInteger)count -{ - return [[self alloc] initWithCoordinates:coords count:count]; + count:(NSUInteger)count { + return [[self alloc] initWithCoordinates:coords count:count]; } - (mbgl::LineString)lineString { - NSUInteger count = self.pointCount; - CLLocationCoordinate2D *coordinates = self.coordinates; + NSUInteger count = self.pointCount; + CLLocationCoordinate2D *coordinates = self.coordinates; - mbgl::LineString geometry; - geometry.reserve(self.pointCount); - for (NSUInteger i = 0; i < count; i++) { - geometry.push_back(mbgl::Point(coordinates[i].longitude, coordinates[i].latitude)); - } + mbgl::LineString geometry; + geometry.reserve(self.pointCount); + for (NSUInteger i = 0; i < count; i++) { + geometry.push_back(mbgl::Point(coordinates[i].longitude, coordinates[i].latitude)); + } - return geometry; + return geometry; } -- (mbgl::Annotation)annotationObjectWithDelegate:(id )delegate { - mbgl::LineAnnotation annotation { [self lineString] }; - annotation.opacity = { static_cast([delegate alphaForShapeAnnotation:self]) }; - annotation.color = { [delegate strokeColorForShapeAnnotation:self] }; - annotation.width = { static_cast([delegate lineWidthForPolylineAnnotation:self]) }; +- (mbgl::Annotation)annotationObjectWithDelegate:(id)delegate { + mbgl::LineAnnotation annotation{[self lineString]}; + annotation.opacity = {static_cast([delegate alphaForShapeAnnotation:self])}; + annotation.color = {[delegate strokeColorForShapeAnnotation:self]}; + annotation.width = {static_cast([delegate lineWidthForPolylineAnnotation:self])}; - return annotation; + return annotation; } - (mbgl::Geometry)geometryObject { - return [self lineString]; + return [self lineString]; } - (NSDictionary *)geoJSONDictionary { - return @{@"type": @"LineString", - @"coordinates": self.mgl_coordinates}; + return @{@"type" : @"LineString", @"coordinates" : self.mgl_coordinates}; } - (NSArray *)mgl_coordinates { - NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; - for (NSUInteger index = 0; index < self.pointCount; index++) { - CLLocationCoordinate2D coordinate = self.coordinates[index]; - [coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; - } - return [coordinates copy]; + NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; + for (NSUInteger index = 0; index < self.pointCount; index++) { + CLLocationCoordinate2D coordinate = self.coordinates[index]; + [coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]]; + } + return [coordinates copy]; } - (BOOL)isEqual:(id)other { - return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]); + return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]); } - (CLLocationCoordinate2D)coordinate { - NSUInteger count = self.pointCount; - MLNAssert(count > 0, @"Polyline must have coordinates"); + NSUInteger count = self.pointCount; + MLNAssert(count > 0, @"Polyline must have coordinates"); - CLLocationCoordinate2D *coordinates = self.coordinates; - CLLocationDistance middle = [self length] / 2.0; - CLLocationDistance traveled = 0.0; + CLLocationCoordinate2D *coordinates = self.coordinates; + CLLocationDistance middle = [self length] / 2.0; + CLLocationDistance traveled = 0.0; - if (count > 1 || middle > traveled) { - for (NSUInteger i = 0; i < count; i++) { + if (count > 1 || middle > traveled) { + for (NSUInteger i = 0; i < count; i++) { + // Avoid a heap buffer overflow when there are only two coordinates. + NSUInteger nextIndex = (i + 1 == count) ? 0 : 1; - // Avoid a heap buffer overflow when there are only two coordinates. - NSUInteger nextIndex = (i + 1 == count) ? 0 : 1; + MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]); + MLNRadianCoordinate2D to = + MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]); - MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]); - MLNRadianCoordinate2D to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]); - - if (traveled >= middle) { - double overshoot = middle - traveled; - if (overshoot == 0) { - return coordinates[i]; - } - to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]); - CLLocationDirection direction = [self direction:from to:to] - 180; - MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(from, - overshoot/mbgl::util::EARTH_RADIUS_M, - MLNRadiansFromDegrees(direction)); - return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude), - MLNDegreesFromRadians(otherCoordinate.longitude)); - } - - traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M); + if (traveled >= middle) { + double overshoot = middle - traveled; + if (overshoot == 0) { + return coordinates[i]; } - } + to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]); + CLLocationDirection direction = [self direction:from to:to] - 180; + MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection( + from, overshoot / mbgl::util::EARTH_RADIUS_M, MLNRadiansFromDegrees(direction)); + return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude), + MLNDegreesFromRadians(otherCoordinate.longitude)); + } - return coordinates[count - 1]; + traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M); + } + } + + return coordinates[count - 1]; } -- (CLLocationDistance)length -{ - CLLocationDistance length = 0.0; +- (CLLocationDistance)length { + CLLocationDistance length = 0.0; - NSUInteger count = self.pointCount; - CLLocationCoordinate2D *coordinates = self.coordinates; + NSUInteger count = self.pointCount; + CLLocationCoordinate2D *coordinates = self.coordinates; - for (NSUInteger i = 0; i < count - 1; i++) { - length += (MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinateFromLocationCoordinate(coordinates[i]), MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * mbgl::util::EARTH_RADIUS_M); - } + for (NSUInteger i = 0; i < count - 1; i++) { + length += (MLNDistanceBetweenRadianCoordinates( + MLNRadianCoordinateFromLocationCoordinate(coordinates[i]), + MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * + mbgl::util::EARTH_RADIUS_M); + } - return length; + return length; } -- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to -{ - return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to)); +- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to { + return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to)); } @end @@ -134,112 +131,112 @@ @end @implementation MLNMultiPolyline { - MLNCoordinateBounds _overlayBounds; + MLNCoordinateBounds _overlayBounds; } @synthesize overlayBounds = _overlayBounds; + (instancetype)multiPolylineWithPolylines:(NSArray *)polylines { - return [[self alloc] initWithPolylines:polylines]; + return [[self alloc] initWithPolylines:polylines]; } - (instancetype)initWithPolylines:(NSArray *)polylines { - MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count); - if (self = [super init]) { - _polylines = polylines; + MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count); + if (self = [super init]) { + _polylines = polylines; - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (MLNPolyline *polyline in _polylines) { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); - } - _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + for (MLNPolyline *polyline in _polylines) { + bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); } - return self; + _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + } + return self; } + (BOOL)supportsSecureCoding { - return YES; + return YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:decoder]) { - NSSet *polylinesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolyline class]]]; - _polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"]; + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:decoder]) { + NSSet *polylinesClasses = + [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolyline class] ]]; + _polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"]; - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (MLNPolyline *polyline in _polylines) { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); - } - _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + for (MLNPolyline *polyline in _polylines) { + bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); } - return self; + _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds); + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:_polylines forKey:@"polylines"]; + [super encodeWithCoder:coder]; + [coder encodeObject:_polylines forKey:@"polylines"]; } -- (BOOL)isEqual:(id)other -{ - if (self == other) return YES; - if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO; +- (BOOL)isEqual:(id)other { + if (self == other) return YES; + if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO; - MLNMultiPolyline *otherMultipoline = other; - return ([super isEqual:otherMultipoline] - && [self.polylines isEqualToArray:otherMultipoline.polylines]); + MLNMultiPolyline *otherMultipoline = other; + return ([super isEqual:otherMultipoline] && + [self.polylines isEqualToArray:otherMultipoline.polylines]); } - (NSUInteger)hash { - NSUInteger hash = [super hash]; - for (MLNPolyline *polyline in self.polylines) { - hash += [polyline hash]; - } - return hash; + NSUInteger hash = [super hash]; + for (MLNPolyline *polyline in self.polylines) { + hash += [polyline hash]; + } + return hash; } - (CLLocationCoordinate2D)coordinate { - MLNPolyline *polyline = self.polylines.firstObject; - CLLocationCoordinate2D *coordinates = polyline.coordinates; - MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates"); - CLLocationCoordinate2D firstCoordinate = coordinates[0]; + MLNPolyline *polyline = self.polylines.firstObject; + CLLocationCoordinate2D *coordinates = polyline.coordinates; + MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates"); + CLLocationCoordinate2D firstCoordinate = coordinates[0]; - return firstCoordinate; + return firstCoordinate; } - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { - return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); + return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); } - (mbgl::Geometry)geometryObject { - mbgl::MultiLineString multiLineString; - multiLineString.reserve(self.polylines.count); - for (MLNPolyline *polyline in self.polylines) { - multiLineString.push_back([polyline lineString]); - } - return multiLineString; + mbgl::MultiLineString multiLineString; + multiLineString.reserve(self.polylines.count); + for (MLNPolyline *polyline in self.polylines) { + multiLineString.push_back([polyline lineString]); + } + return multiLineString; } - (NSDictionary *)geoJSONDictionary { - NSMutableArray *coordinates = [NSMutableArray array]; - for (MLNPolylineFeature *feature in self.polylines) { - [coordinates addObject: feature.mgl_coordinates]; - } - return @{@"type": @"MultiLineString", - @"coordinates": coordinates}; + NSMutableArray *coordinates = [NSMutableArray array]; + for (MLNPolylineFeature *feature in self.polylines) { + [coordinates addObject:feature.mgl_coordinates]; + } + return @{@"type" : @"MultiLineString", @"coordinates" : coordinates}; } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", - NSStringFromClass([self class]), (void *)self, - self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, - self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, - (unsigned long)self.polylines.count, - MLNStringFromCoordinateBounds(self.overlayBounds)]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] + : self.subtitle, + (unsigned long)self.polylines.count, + MLNStringFromCoordinateBounds(self.overlayBounds)]; } @end diff --git a/platform/darwin/src/MLNRasterDEMSource.mm b/platform/darwin/src/MLNRasterDEMSource.mm index be2e34b13..bb87b71db 100644 --- a/platform/darwin/src/MLNRasterDEMSource.mm +++ b/platform/darwin/src/MLNRasterDEMSource.mm @@ -9,10 +9,12 @@ const MLNTileSourceOption MLNTileSourceOptionDEMEncoding = @"MLNTileSourceOption @implementation MLNRasterDEMSource -- (std::unique_ptr)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant)urlOrTileset tileSize:(uint16_t)tileSize { - auto source = std::make_unique(identifier.UTF8String, - urlOrTileset, - tileSize); - return source; +- (std::unique_ptr) + pendingSourceWithIdentifier:(NSString *)identifier + urlOrTileset:(mbgl::variant)urlOrTileset + tileSize:(uint16_t)tileSize { + auto source = + std::make_unique(identifier.UTF8String, urlOrTileset, tileSize); + return source; } @end diff --git a/platform/darwin/src/MLNRasterTileSource.mm b/platform/darwin/src/MLNRasterTileSource.mm index 358130b72..2734b8dd6 100644 --- a/platform/darwin/src/MLNRasterTileSource.mm +++ b/platform/darwin/src/MLNRasterTileSource.mm @@ -22,64 +22,78 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512; @implementation MLNRasterTileSource -- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL { - // The style specification default is 512, but 256 is the expected value for - // any tile set that would be accessed through a mapbox: URL and therefore - // any tile URL that this option currently affects. - BOOL isMapboxURL = ([configurationURL.scheme isEqualToString:@"mapbox"] - && [configurationURL.host containsString:@"."] - && (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"])); - CGFloat tileSize = isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize; - return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize]; +- (instancetype)initWithIdentifier:(NSString *)identifier + configurationURL:(NSURL *)configurationURL { + // The style specification default is 512, but 256 is the expected value for + // any tile set that would be accessed through a mapbox: URL and therefore + // any tile URL that this option currently affects. + BOOL isMapboxURL = + ([configurationURL.scheme isEqualToString:@"mapbox"] && + [configurationURL.host containsString:@"."] && + (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"])); + CGFloat tileSize = + isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize; + return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize]; } -- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL tileSize:(CGFloat)tileSize { - NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString; - auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:configurationURLString.UTF8String tileSize:uint16_t(round(tileSize))]; - return self = [super initWithPendingSource:std::move(source)]; +- (instancetype)initWithIdentifier:(NSString *)identifier + configurationURL:(NSURL *)configurationURL + tileSize:(CGFloat)tileSize { + NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString; + auto source = [self pendingSourceWithIdentifier:identifier + urlOrTileset:configurationURLString.UTF8String + tileSize:uint16_t(round(tileSize))]; + return self = [super initWithPendingSource:std::move(source)]; } -- (std::unique_ptr)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant)urlOrTileset tileSize:(uint16_t)tileSize { - auto source = std::make_unique(identifier.UTF8String, - urlOrTileset, - tileSize); - return source; +- (std::unique_ptr) + pendingSourceWithIdentifier:(NSString *)identifier + urlOrTileset:(mbgl::variant)urlOrTileset + tileSize:(uint16_t)tileSize { + auto source = + std::make_unique(identifier.UTF8String, urlOrTileset, tileSize); + return source; } -- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray *)tileURLTemplates options:(nullable NSDictionary *)options { - mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); +- (instancetype)initWithIdentifier:(NSString *)identifier + tileURLTemplates:(NSArray *)tileURLTemplates + options:(nullable NSDictionary *)options { + mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); - uint16_t tileSize = MLNRasterTileSourceRetinaTileSize; - if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) { - if (![tileSizeNumber isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."]; - } - tileSize = static_cast(round(tileSizeNumber.doubleValue)); + uint16_t tileSize = MLNRasterTileSourceRetinaTileSize; + if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) { + if (![tileSizeNumber isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."]; } + tileSize = static_cast(round(tileSizeNumber.doubleValue)); + } - auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:tileSet tileSize:tileSize]; - return self = [super initWithPendingSource:std::move(source)]; + auto source = [self pendingSourceWithIdentifier:identifier + urlOrTileset:tileSet + tileSize:tileSize]; + return self = [super initWithPendingSource:std::move(source)]; } - (mbgl::style::RasterSource *)rawSource { - return (mbgl::style::RasterSource *)super.rawSource; + return (mbgl::style::RasterSource *)super.rawSource; } - (NSURL *)configurationURL { - MLNAssertStyleSourceIsValid(); - auto url = self.rawSource->getURL(); - return url ? [NSURL URLWithString:@(url->c_str())] : nil; + MLNAssertStyleSourceIsValid(); + auto url = self.rawSource->getURL(); + return url ? [NSURL URLWithString:@(url->c_str())] : nil; } - (NSString *)attributionHTMLString { - if (!self.rawSource) { - MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); - return nil; - } + if (!self.rawSource) { + MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", + self.identifier); + return nil; + } - auto attribution = self.rawSource->getAttribution(); - return attribution ? @(attribution->c_str()) : nil; + auto attribution = self.rawSource->getAttribution(); + return attribution ? @(attribution->c_str()) : nil; } @end diff --git a/platform/darwin/src/MLNRendererFrontend.h b/platform/darwin/src/MLNRendererFrontend.h index 22e98780f..ae3662834 100644 --- a/platform/darwin/src/MLNRendererFrontend.h +++ b/platform/darwin/src/MLNRendererFrontend.h @@ -10,7 +10,7 @@ dependent way. */ class MLNRenderFrontend : public mbgl::RendererFrontend { - public: +public: MLNRenderFrontend(std::unique_ptr renderer_, MLNMapView* nativeView_, mbgl::gfx::RendererBackend& mbglBackend_, bool async = false) : renderer(std::move(renderer_)), nativeView(nativeView_), mbglBackend(mbglBackend_) { @@ -73,7 +73,7 @@ class MLNRenderFrontend : public mbgl::RendererFrontend { renderer->reduceMemoryUse(); } - private: +private: std::unique_ptr renderer; __weak MLNMapView* nativeView = nullptr; mbgl::gfx::RendererBackend& mbglBackend; diff --git a/platform/darwin/src/MLNRenderingStats.mm b/platform/darwin/src/MLNRenderingStats.mm index 63231993c..337e53702 100644 --- a/platform/darwin/src/MLNRenderingStats.mm +++ b/platform/darwin/src/MLNRenderingStats.mm @@ -3,36 +3,35 @@ @implementation MLNRenderingStats - (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats { - _encodingTime = stats.encodingTime; - _renderingTime = stats.renderingTime; - _numFrames = stats.numFrames; - _numDrawCalls = stats.numDrawCalls; - _totalDrawCalls = stats.totalDrawCalls; - _numCreatedTextures = stats.numCreatedTextures; - _numActiveTextures = stats.numActiveTextures; - _numTextureBindings = stats.numTextureBindings; - _numTextureUpdates = stats.numTextureUpdates; - _textureUpdateBytes = stats.textureUpdateBytes; - _totalBuffers = stats.totalBuffers; - _totalBufferObjs = stats.totalBufferObjs; - _bufferUpdates = stats.bufferUpdates; - _bufferObjUpdates = stats.bufferObjUpdates; - _bufferUpdateBytes = stats.bufferUpdateBytes; - _numIndexBuffers = stats.numIndexBuffers; - _indexUpdateBytes = stats.indexUpdateBytes; - _numVertexBuffers = stats.numVertexBuffers; - _vertexUpdateBytes = stats.vertexUpdateBytes; - _numUniformBuffers = stats.numUniformBuffers; - _numUniformUpdates = stats.numUniformUpdates; - _uniformUpdateBytes = stats.uniformUpdateBytes; - _memTextures = stats.memTextures; - _memBuffers = stats.memBuffers; - _memIndexBuffers = stats.memIndexBuffers; - _memVertexBuffers = stats.memVertexBuffers; - _memUniformBuffers = stats.memUniformBuffers; - _stencilClears = stats.stencilClears; - _stencilUpdates = stats.stencilUpdates; + _encodingTime = stats.encodingTime; + _renderingTime = stats.renderingTime; + _numFrames = stats.numFrames; + _numDrawCalls = stats.numDrawCalls; + _totalDrawCalls = stats.totalDrawCalls; + _numCreatedTextures = stats.numCreatedTextures; + _numActiveTextures = stats.numActiveTextures; + _numTextureBindings = stats.numTextureBindings; + _numTextureUpdates = stats.numTextureUpdates; + _textureUpdateBytes = stats.textureUpdateBytes; + _totalBuffers = stats.totalBuffers; + _totalBufferObjs = stats.totalBufferObjs; + _bufferUpdates = stats.bufferUpdates; + _bufferObjUpdates = stats.bufferObjUpdates; + _bufferUpdateBytes = stats.bufferUpdateBytes; + _numIndexBuffers = stats.numIndexBuffers; + _indexUpdateBytes = stats.indexUpdateBytes; + _numVertexBuffers = stats.numVertexBuffers; + _vertexUpdateBytes = stats.vertexUpdateBytes; + _numUniformBuffers = stats.numUniformBuffers; + _numUniformUpdates = stats.numUniformUpdates; + _uniformUpdateBytes = stats.uniformUpdateBytes; + _memTextures = stats.memTextures; + _memBuffers = stats.memBuffers; + _memIndexBuffers = stats.memIndexBuffers; + _memVertexBuffers = stats.memVertexBuffers; + _memUniformBuffers = stats.memUniformBuffers; + _stencilClears = stats.stencilClears; + _stencilUpdates = stats.stencilUpdates; } - @end diff --git a/platform/darwin/src/MLNSettings.mm b/platform/darwin/src/MLNSettings.mm index b0d6d8038..8c1f00265 100644 --- a/platform/darwin/src/MLNSettings.mm +++ b/platform/darwin/src/MLNSettings.mm @@ -18,215 +18,231 @@ // MARK: - Internal - (instancetype)init { - if (self = [super init]) { - self.tileServerOptionsInternal = new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration()); - } - return self; + if (self = [super init]) { + self.tileServerOptionsInternal = + new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration()); + } + return self; } + (void)load { - // Read the initial configuration from Info.plist. - NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"]; - if (apiKey.length) { - self.apiKey = apiKey; - } + // Read the initial configuration from Info.plist. + NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"]; + if (apiKey.length) { + self.apiKey = apiKey; + } - NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"]; + NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"]; - // If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`. - if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) { - [self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]]; - [self tileServerOptionsChanged]; - } + // If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`. + if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) { + [self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]]; + [self tileServerOptionsChanged]; + } } + (instancetype)sharedSettings { #if TARGET_OS_OSX - if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) { - return nil; - } + if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) { + return nil; + } #endif - static dispatch_once_t onceToken; - static MLNSettings *_sharedSettings; - void (^setupBlock)(void) = ^{ - dispatch_once(&onceToken, ^{ - _sharedSettings = [[self alloc] init]; - }); - }; - if (![[NSThread currentThread] isMainThread]) { - dispatch_sync(dispatch_get_main_queue(), ^{ - setupBlock(); - }); - } else { - setupBlock(); - } - return _sharedSettings; + static dispatch_once_t onceToken; + static MLNSettings *_sharedSettings; + void (^setupBlock)(void) = ^{ + dispatch_once(&onceToken, ^{ + _sharedSettings = [[self alloc] init]; + }); + }; + if (![[NSThread currentThread] isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + setupBlock(); + }); + } else { + setupBlock(); + } + return _sharedSettings; } + (void)setApiKey:(NSString *)apiKey { - apiKey = [apiKey stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - if (!apiKey.length) { - return; - } + apiKey = + [apiKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + if (!apiKey.length) { + return; + } - [MLNSettings sharedSettings].apiKey = apiKey; + [MLNSettings sharedSettings].apiKey = apiKey; } + (NSString *)apiKey { - return [MLNSettings sharedSettings].apiKey; + return [MLNSettings sharedSettings].apiKey; } + (void)setAPIBaseURL:(NSURL *)apiBaseURL { - auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal; - NSString *baseUrlNSStr = apiBaseURL.absoluteString; - auto baseUrl = std::string([baseUrlNSStr UTF8String]); + auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal; + NSString *baseUrlNSStr = apiBaseURL.absoluteString; + auto baseUrl = std::string([baseUrlNSStr UTF8String]); - [MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl); + [MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl); } + (NSURL *)apiBaseURL { - auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL(); - NSString* baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()]; - NSURL *url = [NSURL URLWithString:baseUrlNSStr]; - return url; + auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL(); + NSString *baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()]; + NSURL *url = [NSURL URLWithString:baseUrlNSStr]; + return url; } + (void)setTileServerOptionsInternal:(mbgl::TileServerOptions)options { - [MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions(); - *[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone(); - [self tileServerOptionsChanged]; + [MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions(); + *[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone(); + [self tileServerOptionsChanged]; } + (mbgl::TileServerOptions)tileServerOptionsInternal { - auto options = [MLNSettings sharedSettings]; - return options.tileServerOptionsInternal->clone(); + auto options = [MLNSettings sharedSettings]; + return options.tileServerOptionsInternal->clone(); } + (void)tileServerOptionsChanged { - [MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString]; + [MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString]; } + (void)useWellKnownTileServer:(MLNWellKnownTileServer)tileServer { - switch(tileServer){ - case MLNMapTiler: - [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()]; - break; - case MLNMapLibre: - [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()]; - break; - case MLNMapbox: - [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()]; - break; - default: - [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()]; - } + switch (tileServer) { + case MLNMapTiler: + [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()]; + break; + case MLNMapLibre: + [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()]; + break; + case MLNMapbox: + [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()]; + break; + default: + [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()]; + } } -+ (void)setTileServerOptions:(MLNTileServerOptions*)options { ++ (void)setTileServerOptions:(MLNTileServerOptions *)options { + auto opts = mbgl::TileServerOptions() + .withBaseURL(std::string([options.baseURL UTF8String])) + .withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String])) + .withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String])); - auto opts = mbgl::TileServerOptions() - .withBaseURL(std::string([options.baseURL UTF8String])) - .withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String])) - .withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String])); + opts.withSourceTemplate(std::string([options.sourceTemplate UTF8String]), + std::string([options.sourceDomainName UTF8String]), + options.sourceVersionPrefix + ? std::string([options.sourceVersionPrefix UTF8String]) + : std::optional{}); - opts.withSourceTemplate( - std::string([options.sourceTemplate UTF8String]), - std::string([options.sourceDomainName UTF8String]), - options.sourceVersionPrefix ? std::string([options.sourceVersionPrefix UTF8String]) : std::optional{}); + opts.withStyleTemplate(std::string([options.styleTemplate UTF8String]), + std::string([options.styleDomainName UTF8String]), + options.styleVersionPrefix + ? std::string([options.styleVersionPrefix UTF8String]) + : std::optional{}); - opts.withStyleTemplate( - std::string([options.styleTemplate UTF8String]), - std::string([options.styleDomainName UTF8String]), - options.styleVersionPrefix ? std::string([options.styleVersionPrefix UTF8String]) : std::optional{}); + opts.withSpritesTemplate(std::string([options.spritesTemplate UTF8String]), + std::string([options.spritesDomainName UTF8String]), + options.spritesVersionPrefix + ? std::string([options.spritesVersionPrefix UTF8String]) + : std::optional{}); - opts.withSpritesTemplate( - std::string([options.spritesTemplate UTF8String]), - std::string([options.spritesDomainName UTF8String]), - options.spritesVersionPrefix ? std::string([options.spritesVersionPrefix UTF8String]) : std::optional{}); + opts.withGlyphsTemplate(std::string([options.glyphsTemplate UTF8String]), + std::string([options.glyphsDomainName UTF8String]), + options.glyphsVersionPrefix + ? std::string([options.glyphsVersionPrefix UTF8String]) + : std::optional{}); - opts.withGlyphsTemplate( - std::string([options.glyphsTemplate UTF8String]), - std::string([options.glyphsDomainName UTF8String]), - options.glyphsVersionPrefix ? std::string([options.glyphsVersionPrefix UTF8String]) : std::optional{}); + opts.withTileTemplate(std::string([options.tileTemplate UTF8String]), + std::string([options.tileDomainName UTF8String]), + options.tileVersionPrefix + ? std::string([options.tileVersionPrefix UTF8String]) + : std::optional{}); - opts.withTileTemplate( - std::string([options.tileTemplate UTF8String]), - std::string([options.tileDomainName UTF8String]), - options.tileVersionPrefix ? std::string([options.tileVersionPrefix UTF8String]) : std::optional{}); - - std::vector defaultStyles; - if (options.defaultStyles) { - for (MLNDefaultStyle* objCStyle in options.defaultStyles) { - mbgl::util::DefaultStyle cppStyle( - std::string([objCStyle.url.absoluteString UTF8String]), - std::string([objCStyle.name UTF8String]), - objCStyle.version - ); - defaultStyles.push_back(cppStyle); - } + std::vector defaultStyles; + if (options.defaultStyles) { + for (MLNDefaultStyle *objCStyle in options.defaultStyles) { + mbgl::util::DefaultStyle cppStyle(std::string([objCStyle.url.absoluteString UTF8String]), + std::string([objCStyle.name UTF8String]), + objCStyle.version); + defaultStyles.push_back(cppStyle); } - opts.withDefaultStyles(defaultStyles); - opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String])); + } + opts.withDefaultStyles(defaultStyles); + opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String])); - self.tileServerOptionsInternal = opts; + self.tileServerOptionsInternal = opts; } -+ (MLNTileServerOptions*)tileServerOptions { - auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal; - if (cppOpts) { ++ (MLNTileServerOptions *)tileServerOptions { + auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal; + if (cppOpts) { + MLNTileServerOptions *retVal = [[MLNTileServerOptions alloc] init]; + retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()]; + retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()]; + retVal.apiKeyParameterName = + [NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()]; - MLNTileServerOptions* retVal = [[MLNTileServerOptions alloc] init]; - retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()]; - retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()]; - retVal.apiKeyParameterName = [NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()]; + retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()]; + retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()]; + retVal.sourceVersionPrefix = + cppOpts->sourceVersionPrefix().has_value() + ? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()] + : nil; - retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()]; - retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()]; - retVal.sourceVersionPrefix = cppOpts->sourceVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()] : nil; + retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()]; + retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()]; + retVal.styleVersionPrefix = + cppOpts->styleVersionPrefix().has_value() + ? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()] + : nil; - retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()]; - retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()]; - retVal.styleVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()] : nil; + retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()]; + retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()]; + retVal.spritesVersionPrefix = + cppOpts->styleVersionPrefix().has_value() + ? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()] + : nil; - retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()]; - retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()]; - retVal.spritesVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()] : nil; + retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()]; + retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()]; + retVal.glyphsVersionPrefix = + cppOpts->styleVersionPrefix().has_value() + ? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()] + : nil; - retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()]; - retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()]; - retVal.glyphsVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()] : nil; + retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()]; + retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()]; + retVal.tileVersionPrefix = + cppOpts->styleVersionPrefix().has_value() + ? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()] + : nil; - retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()]; - retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()]; - retVal.tileVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()] : nil; + std::vector cppDefaultStyles = cppOpts->defaultStyles(); - std::vector cppDefaultStyles = cppOpts->defaultStyles(); + NSMutableArray *mglStyles = [[NSMutableArray alloc] init]; + for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) { + NSString *url = [NSString stringWithUTF8String:it->getUrl().c_str()]; + auto *mglDefaultStyle = [[MLNDefaultStyle alloc] init]; + mglDefaultStyle.url = [NSURL URLWithString:url]; + mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()]; + mglDefaultStyle.version = it->getCurrentVersion(); + [mglStyles addObject:mglDefaultStyle]; - NSMutableArray* mglStyles = [[NSMutableArray alloc] init]; - for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) { - NSString* url = [NSString stringWithUTF8String:it->getUrl().c_str()]; - - auto* mglDefaultStyle = [[MLNDefaultStyle alloc] init]; - mglDefaultStyle.url = [NSURL URLWithString:url]; - mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()]; - mglDefaultStyle.version = it->getCurrentVersion(); - [mglStyles addObject:mglDefaultStyle]; - - // set default style name - if (it->getName() == cppOpts->defaultStyle()) { - retVal.defaultStyle = mglDefaultStyle; - } - } - - retVal.defaultStyles = mglStyles; - return retVal; + // set default style name + if (it->getName() == cppOpts->defaultStyle()) { + retVal.defaultStyle = mglDefaultStyle; + } } - return nil; + retVal.defaultStyles = mglStyles; + return retVal; + } + return nil; } @end diff --git a/platform/darwin/src/MLNShape.mm b/platform/darwin/src/MLNShape.mm index e12a91ff9..050ceb1f8 100644 --- a/platform/darwin/src/MLNShape.mm +++ b/platform/darwin/src/MLNShape.mm @@ -2,110 +2,110 @@ #import "MLNFeature_Private.h" -#import "NSString+MLNAdditions.h" #import "MLNTypes.h" +#import "NSString+MLNAdditions.h" #import bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) { - return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude; + return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude; } @implementation MLNShape -+ (nullable MLNShape *)shapeWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError * _Nullable *)outError { - NSString *string = [[NSString alloc] initWithData:data encoding:encoding]; - if (!string) { - if (outError) { - *outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil]; - } - return nil; ++ (nullable MLNShape *)shapeWithData:(NSData *)data + encoding:(NSStringEncoding)encoding + error:(NSError *_Nullable *)outError { + NSString *string = [[NSString alloc] initWithData:data encoding:encoding]; + if (!string) { + if (outError) { + *outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil]; } + return nil; + } - try { - const auto geojson = mapbox::geojson::parse(string.UTF8String); - return MLNShapeFromGeoJSON(geojson); - } catch (std::runtime_error &err) { - if (outError) { - *outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ - NSLocalizedFailureReasonErrorKey: @(err.what()), - }]; - } - return nil; + try { + const auto geojson = mapbox::geojson::parse(string.UTF8String); + return MLNShapeFromGeoJSON(geojson); + } catch (std::runtime_error &err) { + if (outError) { + *outError = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeUnknown + userInfo:@{ + NSLocalizedFailureReasonErrorKey : @(err.what()), + }]; } + return nil; + } } - (mbgl::GeoJSON)geoJSONObject { - return self.geometryObject; + return self.geometryObject; } - (mbgl::Geometry)geometryObject { - [NSException raise:MLNAbstractClassException - format:@"MLNShape is an abstract class"]; - return mbgl::Point(); + [NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"]; + return mbgl::Point(); } - (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding { - auto geometry = self.geoJSONObject; - NSString *string = @(mapbox::geojson::stringify(geometry).c_str()); - return [string dataUsingEncoding:NSUTF8StringEncoding]; + auto geometry = self.geoJSONObject; + NSString *string = @(mapbox::geojson::stringify(geometry).c_str()); + return [string dataUsingEncoding:NSUTF8StringEncoding]; } -+ (BOOL)supportsSecureCoding -{ ++ (BOOL)supportsSecureCoding { + return YES; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { + if (self = [super init]) { + _title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"]; + _subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"]; +#if !TARGET_OS_IPHONE + _toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"]; +#endif + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_title forKey:@"title"]; + [coder encodeObject:_subtitle forKey:@"subtitle"]; +#if !TARGET_OS_IPHONE + [coder encodeObject:_toolTip forKey:@"toolTip"]; +#endif +} + +- (BOOL)isEqual:(id)other { + if (other == self) { return YES; -} - -- (instancetype)initWithCoder:(NSCoder *)coder -{ - if (self = [super init]) { - _title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"]; - _subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"]; -#if !TARGET_OS_IPHONE - _toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"]; -#endif - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeObject:_title forKey:@"title"]; - [coder encodeObject:_subtitle forKey:@"subtitle"]; -#if !TARGET_OS_IPHONE - [coder encodeObject:_toolTip forKey:@"toolTip"]; -#endif -} - -- (BOOL)isEqual:(id)other -{ - if (other == self) { return YES; } - id annotation = other; + } + id annotation = other; #if TARGET_OS_IPHONE - return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) - && ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]); + return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) && + ((!_subtitle && ![annotation subtitle]) || + [_subtitle isEqualToString:[annotation subtitle]]); #else - return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) - && ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]) - && ((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]); + return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) && + ((!_subtitle && ![annotation subtitle]) || + [_subtitle isEqualToString:[annotation subtitle]]) && + ((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]); #endif } -- (NSUInteger)hash -{ - NSUInteger hash = _title.hash + _subtitle.hash; +- (NSUInteger)hash { + NSUInteger hash = _title.hash + _subtitle.hash; #if !TARGET_OS_IPHONE - hash += _toolTip.hash; + hash += _toolTip.hash; #endif - return hash; + return hash; } -- (CLLocationCoordinate2D)coordinate -{ - [NSException raise:MLNAbstractClassException - format:@"MLNShape is an abstract class"]; - return kCLLocationCoordinate2DInvalid; +- (CLLocationCoordinate2D)coordinate { + [NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"]; + return kCLLocationCoordinate2DInvalid; } @end diff --git a/platform/darwin/src/MLNShapeCollection.mm b/platform/darwin/src/MLNShapeCollection.mm index ca1811bd9..ed9fbfeb2 100644 --- a/platform/darwin/src/MLNShapeCollection.mm +++ b/platform/darwin/src/MLNShapeCollection.mm @@ -1,85 +1,85 @@ #import "MLNShapeCollection.h" -#import "MLNShape_Private.h" #import "MLNFeature.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNShape_Private.h" #import @implementation MLNShapeCollection + (BOOL)supportsSecureCoding { - return YES; + return YES; } + (instancetype)shapeCollectionWithShapes:(NSArray *)shapes { - return [[self alloc] initWithShapes:shapes]; + return [[self alloc] initWithShapes:shapes]; } - (instancetype)initWithShapes:(NSArray *)shapes { - MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count); - if (self = [super init]) { - _shapes = shapes.copy; - } - return self; + MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count); + if (self = [super init]) { + _shapes = shapes.copy; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:decoder]) { - NSSet *shapeClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNShape class]]]; - _shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"]; - } - return self; + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:decoder]) { + NSSet *shapeClasses = + [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNShape class] ]]; + _shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:_shapes forKey:@"shapes"]; + [super encodeWithCoder:coder]; + [coder encodeObject:_shapes forKey:@"shapes"]; } - (BOOL)isEqual:(id)other { - if (self == other) return YES; - if (![other isKindOfClass:[MLNShapeCollection class]]) return NO; + if (self == other) return YES; + if (![other isKindOfClass:[MLNShapeCollection class]]) return NO; - MLNShapeCollection *otherShapeCollection = other; - return [super isEqual:otherShapeCollection] - && [_shapes isEqualToArray:otherShapeCollection.shapes]; + MLNShapeCollection *otherShapeCollection = other; + return + [super isEqual:otherShapeCollection] && [_shapes isEqualToArray:otherShapeCollection.shapes]; } - (NSUInteger)hash { - NSUInteger hash = [super hash]; - for (MLNShape *shape in _shapes) { - hash += [shape hash]; - } - return hash; + NSUInteger hash = [super hash]; + for (MLNShape *shape in _shapes) { + hash += [shape hash]; + } + return hash; } - (CLLocationCoordinate2D)coordinate { - return _shapes.firstObject.coordinate; + return _shapes.firstObject.coordinate; } - (NSDictionary *)geoJSONDictionary { - return @{@"type": @"GeometryCollection", - @"geometries": [self geometryCollection]}; + return @{@"type" : @"GeometryCollection", @"geometries" : [self geometryCollection]}; } - (NSArray *)geometryCollection { - NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count]; - for (id shape in self.shapes) { - NSDictionary *geometry = [shape geoJSONDictionary]; - [geometries addObject:geometry]; - } - return [geometries copy]; + NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count]; + for (id shape in self.shapes) { + NSDictionary *geometry = [shape geoJSONDictionary]; + [geometries addObject:geometry]; + } + return [geometries copy]; } - (mbgl::Geometry)geometryObject { - mapbox::geojson::geometry_collection collection; - collection.reserve(self.shapes.count); - for (MLNShape *shape in self.shapes) { - collection.push_back([shape geometryObject]); - } - return collection; + mapbox::geojson::geometry_collection collection; + collection.reserve(self.shapes.count); + for (MLNShape *shape in self.shapes) { + collection.push_back([shape geometryObject]); + } + return collection; } @end diff --git a/platform/darwin/src/MLNShapeOfflineRegion.mm b/platform/darwin/src/MLNShapeOfflineRegion.mm index 8e2492080..07558d4a9 100644 --- a/platform/darwin/src/MLNShapeOfflineRegion.mm +++ b/platform/darwin/src/MLNShapeOfflineRegion.mm @@ -1,139 +1,151 @@ #import "MLNShapeOfflineRegion.h" #if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR - #import +#import #else - #import +#import #endif +#import "MLNFeature_Private.h" +#import "MLNLoggingConfiguration_Private.h" #import "MLNOfflineRegion_Private.h" #import "MLNShapeOfflineRegion_Private.h" -#import "MLNFeature_Private.h" #import "MLNShape_Private.h" #import "MLNStyle.h" -#import "MLNLoggingConfiguration_Private.h" @interface MLNShapeOfflineRegion () @end @implementation MLNShapeOfflineRegion { - NSURL *_styleURL; + NSURL *_styleURL; } @synthesize styleURL = _styleURL; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; --(NSDictionary *)offlineStartEventAttributes { - return @{}; +- (NSDictionary *)offlineStartEventAttributes { + return @{}; } + (BOOL)supportsSecureCoding { - return YES; + return YES; } - (instancetype)init { - MLNLogInfo(@"Calling this initializer is not allowed."); - [NSException raise:@"Method unavailable" - format: - @"-[MLNShapeOfflineRegion init] is unavailable. " - @"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."]; - return nil; + MLNLogInfo(@"Calling this initializer is not allowed."); + [NSException raise:@"Method unavailable" + format:@"-[MLNShapeOfflineRegion init] is unavailable. " + @"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."]; + return nil; } -- (instancetype)initWithStyleURL:(NSURL *)styleURL shape:(MLNShape *)shape fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel { - MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, shape, minimumZoomLevel, maximumZoomLevel); - if (self = [super init]) { - if (!styleURL) { - styleURL = [MLNStyle defaultStyleURL]; - } - - if (!styleURL.scheme) { - [NSException raise:@"Invalid style URL" format: - @"%@ does not support setting a relative file URL as the style URL. " - @"To download the online resources required by this style, " - @"specify a URL to an online copy of this style. " - @"For Mapbox-hosted styles, use the mapbox: scheme.", - NSStringFromClass([self class])]; - } - - _styleURL = styleURL; - _shape = shape; - _minimumZoomLevel = minimumZoomLevel; - _maximumZoomLevel = maximumZoomLevel; - _includesIdeographicGlyphs = NO; +- (instancetype)initWithStyleURL:(NSURL *)styleURL + shape:(MLNShape *)shape + fromZoomLevel:(double)minimumZoomLevel + toZoomLevel:(double)maximumZoomLevel { + MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, + shape, minimumZoomLevel, maximumZoomLevel); + if (self = [super init]) { + if (!styleURL) { + styleURL = [MLNStyle defaultStyleURL]; } - return self; + + if (!styleURL.scheme) { + [NSException raise:@"Invalid style URL" + format:@"%@ does not support setting a relative file URL as the style URL. " + @"To download the online resources required by this style, " + @"specify a URL to an online copy of this style. " + @"For Mapbox-hosted styles, use the mapbox: scheme.", + NSStringFromClass([self class])]; + } + + _styleURL = styleURL; + _shape = shape; + _minimumZoomLevel = minimumZoomLevel; + _maximumZoomLevel = maximumZoomLevel; + _includesIdeographicGlyphs = NO; + } + return self; } -- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineGeometryRegionDefinition &)definition { - NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; - MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry); - MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; - result.includesIdeographicGlyphs = definition.includeIdeographs; - return result; +- (instancetype)initWithOfflineRegionDefinition: + (const mbgl::OfflineGeometryRegionDefinition &)definition { + NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; + MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry); + MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL + shape:shape + fromZoomLevel:definition.minZoom + toZoomLevel:definition.maxZoom]; + result.includesIdeographicGlyphs = definition.includeIdeographs; + return result; } - (const mbgl::OfflineRegionDefinition)offlineRegionDefinition { #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; + const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] + ? [[UIScreen mainScreen] nativeScale] + : [[UIScreen mainScreen] scale]; #elif TARGET_OS_MAC - const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; + const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; #endif - return mbgl::OfflineGeometryRegionDefinition(_styleURL.absoluteString.UTF8String, - _shape.geometryObject, - _minimumZoomLevel, _maximumZoomLevel, - scaleFactor, _includesIdeographicGlyphs); + return mbgl::OfflineGeometryRegionDefinition( + _styleURL.absoluteString.UTF8String, _shape.geometryObject, _minimumZoomLevel, + _maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs); } - (nullable instancetype)initWithCoder:(NSCoder *)coder { - MLNLogInfo(@"Initializing with coder."); - NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; - MLNShape * shape = [coder decodeObjectForKey:@"shape"]; - double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; - double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; + MLNLogInfo(@"Initializing with coder."); + NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; + MLNShape *shape = [coder decodeObjectForKey:@"shape"]; + double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; + double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; - MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel]; - result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; - return result; + MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL + shape:shape + fromZoomLevel:minimumZoomLevel + toZoomLevel:maximumZoomLevel]; + result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; + return result; } -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeObject:_styleURL forKey:@"styleURL"]; - [coder encodeObject:_shape forKey:@"shape"]; - [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; - [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; - [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"]; +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_styleURL forKey:@"styleURL"]; + [coder encodeObject:_shape forKey:@"shape"]; + [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; + [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; + [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"]; } - (id)copyWithZone:(nullable NSZone *)zone { - MLNShapeOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL shape:_shape fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel]; - result.includesIdeographicGlyphs = _includesIdeographicGlyphs; - return result; + MLNShapeOfflineRegion *result = + [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL + shape:_shape + fromZoomLevel:_minimumZoomLevel + toZoomLevel:_maximumZoomLevel]; + result.includesIdeographicGlyphs = _includesIdeographicGlyphs; + return result; } - (BOOL)isEqual:(id)other { - if (other == self) { - return YES; - } - if (![other isKindOfClass:[self class]]) { - return NO; - } + if (other == self) { + return YES; + } + if (![other isKindOfClass:[self class]]) { + return NO; + } - MLNShapeOfflineRegion *otherRegion = other; - return (_minimumZoomLevel == otherRegion->_minimumZoomLevel - && _maximumZoomLevel == otherRegion->_maximumZoomLevel - && _shape.geometryObject == otherRegion->_shape.geometryObject - && [_styleURL isEqual:otherRegion->_styleURL] - && _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); + MLNShapeOfflineRegion *otherRegion = other; + return (_minimumZoomLevel == otherRegion->_minimumZoomLevel && + _maximumZoomLevel == otherRegion->_maximumZoomLevel && + _shape.geometryObject == otherRegion->_shape.geometryObject && + [_styleURL isEqual:otherRegion->_styleURL] && + _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); } - (NSUInteger)hash { - return (_styleURL.hash - + _shape.hash - + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash - + @(_includesIdeographicGlyphs).hash); + return (_styleURL.hash + _shape.hash + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash + + @(_includesIdeographicGlyphs).hash); } @end diff --git a/platform/darwin/src/MLNShapeSource.mm b/platform/darwin/src/MLNShapeSource.mm index 0e3cbf225..05c385214 100644 --- a/platform/darwin/src/MLNShapeSource.mm +++ b/platform/darwin/src/MLNShapeSource.mm @@ -1,169 +1,188 @@ #import "MLNFoundation_Private.h" #import "MLNShapeSource_Private.h" -#import "MLNLoggingConfiguration_Private.h" -#import "MLNStyle_Private.h" -#import "MLNStyleValue_Private.h" -#import "MLNMapView_Private.h" -#import "MLNSource_Private.h" -#import "MLNFeature_Private.h" -#import "MLNShape_Private.h" #import "MLNCluster.h" +#import "MLNFeature_Private.h" +#import "MLNLoggingConfiguration_Private.h" +#import "MLNMapView_Private.h" +#import "MLNShape_Private.h" +#import "MLNSource_Private.h" +#import "MLNStyleValue_Private.h" +#import "MLNStyle_Private.h" #import "NSPredicate+MLNPrivateAdditions.h" #import "NSURL+MLNAdditions.h" #include -#include #include +#include const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer"; const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius"; -const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints = @"MLNShapeSourceOptionClusterMinPoints"; +const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints = + @"MLNShapeSourceOptionClusterMinPoints"; const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered"; -const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties = @"MLNShapeSourceOptionClusterProperties"; -const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel = @"MLNShapeSourceOptionMaximumZoomLevel"; -const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = @"MLNShapeSourceOptionMaximumZoomLevelForClustering"; -const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = @"MLNShapeSourceOptionMinimumZoomLevel"; -const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = @"MLNShapeSourceOptionSimplificationTolerance"; -const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = @"MLNShapeSourceOptionLineDistanceMetrics"; -const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = @"MLNShapeSourceOptionSynchronousUpdate"; +const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties = + @"MLNShapeSourceOptionClusterProperties"; +const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel = + @"MLNShapeSourceOptionMaximumZoomLevel"; +const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = + @"MLNShapeSourceOptionMaximumZoomLevelForClustering"; +const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = + @"MLNShapeSourceOptionMinimumZoomLevel"; +const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = + @"MLNShapeSourceOptionSimplificationTolerance"; +const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = + @"MLNShapeSourceOptionLineDistanceMetrics"; +const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = + @"MLNShapeSourceOptionSynchronousUpdate"; -mbgl::Immutable MLNGeoJSONOptionsFromDictionary(NSDictionary *options) { - auto geoJSONOptions = mbgl::makeMutable(); +mbgl::Immutable MLNGeoJSONOptionsFromDictionary( + NSDictionary *options) { + auto geoJSONOptions = mbgl::makeMutable(); - if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; - } - geoJSONOptions->minzoom = value.integerValue; + if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions->minzoom = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions->maxzoom = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; + } + geoJSONOptions->buffer = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; + } + geoJSONOptions->tolerance = value.doubleValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."]; + } + geoJSONOptions->clusterRadius = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."]; + } + geoJSONOptions->clusterMinPoints = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; + } + geoJSONOptions->clusterMaxZoom = value.integerValue; + } + + if (NSNumber *value = options[MLNShapeSourceOptionClustered]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClustered must be an NSNumber."]; + } + geoJSONOptions->cluster = value.boolValue; + } + + if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) { + if (![value isKindOfClass:[NSDictionary class]]) { + [NSException + raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString " + @"as a key and an array containing two NSExpression objects as a value."]; } - if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; - } - geoJSONOptions->maxzoom = value.integerValue; + NSEnumerator *stringEnumerator = [value keyEnumerator]; + NSString *key; + + while (key = [stringEnumerator nextObject]) { + NSArray *expressionsArray = value[key]; + if (![expressionsArray isKindOfClass:[NSArray class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be " + @"an array containing two objects."]; + } + // Check that the array has 2 values. One should be a the reduce expression and one should be + // the map expression. + if ([expressionsArray count] != 2) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterProperties member value requires array of " + @"two objects."]; + } + + // reduceExpression should be a valid NSExpression + NSExpression *reduceExpression = expressionsArray[0]; + if (![reduceExpression isKindOfClass:[NSExpression class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterProperties array value requires two " + @"expression objects."]; + } + auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression); + if (!reduce) { + [NSException + raise:NSInvalidArgumentException + format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."]; + } + + // mapExpression should be a valid NSExpression + NSExpression *mapExpression = expressionsArray[1]; + if (![mapExpression isKindOfClass:[NSExpression class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionClusterProperties member value must contain a " + @"valid NSExpression."]; + } + auto map = MLNClusterPropertyFromNSExpression(mapExpression); + if (!map) { + [NSException + raise:NSInvalidArgumentException + format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."]; + } + + std::string keyString = std::string([key UTF8String]); + + geoJSONOptions->clusterProperties.emplace(keyString, + std::make_pair(std::move(map), std::move(reduce))); } + } - if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; - } - geoJSONOptions->buffer = value.integerValue; + if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."]; } + geoJSONOptions->lineMetrics = value.boolValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; - } - geoJSONOptions->tolerance = value.doubleValue; + if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."]; } + geoJSONOptions->synchronousUpdate = value.boolValue; + } - if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."]; - } - geoJSONOptions->clusterRadius = value.integerValue; - } - - if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."]; - } - geoJSONOptions->clusterMinPoints = value.integerValue; - } - - if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; - } - geoJSONOptions->clusterMaxZoom = value.integerValue; - } - - if (NSNumber *value = options[MLNShapeSourceOptionClustered]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClustered must be an NSNumber."]; - } - geoJSONOptions->cluster = value.boolValue; - } - - if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) { - if (![value isKindOfClass:[NSDictionary class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."]; - } - - NSEnumerator *stringEnumerator = [value keyEnumerator]; - NSString *key; - - while (key = [stringEnumerator nextObject]) { - NSArray *expressionsArray = value[key]; - if (![expressionsArray isKindOfClass:[NSArray class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."]; - } - // Check that the array has 2 values. One should be a the reduce expression and one should be the map expression. - if ([expressionsArray count] != 2) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterProperties member value requires array of two objects."]; - } - - // reduceExpression should be a valid NSExpression - NSExpression *reduceExpression = expressionsArray[0]; - if (![reduceExpression isKindOfClass:[NSExpression class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterProperties array value requires two expression objects."]; - } - auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression); - if (!reduce) { - [NSException raise:NSInvalidArgumentException - format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."]; - } - - // mapExpression should be a valid NSExpression - NSExpression *mapExpression = expressionsArray[1]; - if (![mapExpression isKindOfClass:[NSExpression class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionClusterProperties member value must contain a valid NSExpression."]; - } - auto map = MLNClusterPropertyFromNSExpression(mapExpression); - if (!map) { - [NSException raise:NSInvalidArgumentException - format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."]; - } - - std::string keyString = std::string([key UTF8String]); - - geoJSONOptions->clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce))); - } - } - - if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."]; - } - geoJSONOptions->lineMetrics = value.boolValue; - } - - if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."]; - } - geoJSONOptions->synchronousUpdate = value.boolValue; - } - - return geoJSONOptions; + return geoJSONOptions; } @interface MLNShapeSource () @@ -175,203 +194,225 @@ mbgl::Immutable MLNGeoJSONOptionsFromDictionary(NSD @implementation MLNShapeSource -- (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url options:(NSDictionary *)options { - auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); - auto source = std::make_unique(identifier.UTF8String, std::move(geoJSONOptions)); - if (self = [super initWithPendingSource:std::move(source)]) { - self.URL = url; - } - return self; +- (instancetype)initWithIdentifier:(NSString *)identifier + URL:(NSURL *)url + options:(NSDictionary *)options { + auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); + auto source = std::make_unique(identifier.UTF8String, + std::move(geoJSONOptions)); + if (self = [super initWithPendingSource:std::move(source)]) { + self.URL = url; + } + return self; } -- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MLNShape *)shape options:(NSDictionary *)options { - auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); - auto source = std::make_unique(identifier.UTF8String, std::move(geoJSONOptions)); - if (self = [super initWithPendingSource:std::move(source)]) { - if ([shape isMemberOfClass:[MLNShapeCollection class]]) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." - @"Use MLNShapeCollectionFeature to retain attributes instead." - @"This will be logged only once."); - }); - } - self.shape = shape; +- (instancetype)initWithIdentifier:(NSString *)identifier + shape:(nullable MLNShape *)shape + options:(NSDictionary *)options { + auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); + auto source = std::make_unique(identifier.UTF8String, + std::move(geoJSONOptions)); + if (self = [super initWithPendingSource:std::move(source)]) { + if ([shape isMemberOfClass:[MLNShapeCollection class]]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." + @"Use MLNShapeCollectionFeature to retain attributes instead." + @"This will be logged only once."); + }); } - return self; + self.shape = shape; + } + return self; } -- (instancetype)initWithIdentifier:(NSString *)identifier features:(NSArray *> *)features options:(nullable NSDictionary *)options { - for (id feature in features) { - if (![feature conformsToProtocol:@protocol(MLNFeature)]) { - [NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MLNFeature protocol.", feature]; - } +- (instancetype)initWithIdentifier:(NSString *)identifier + features:(NSArray *> *)features + options:(nullable NSDictionary *)options { + for (id feature in features) { + if (![feature conformsToProtocol:@protocol(MLNFeature)]) { + [NSException raise:NSInvalidArgumentException + format:@"The object %@ included in the features argument does not conform to the " + @"MLNFeature protocol.", + feature]; } - MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:features]; - return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options]; + } + MLNShapeCollectionFeature *shapeCollectionFeature = + [MLNShapeCollectionFeature shapeCollectionWithShapes:features]; + return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options]; } -- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NSArray *)shapes options:(nullable NSDictionary *)options { - MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes]; - return [self initWithIdentifier:identifier shape:shapeCollection options:options]; +- (instancetype)initWithIdentifier:(NSString *)identifier + shapes:(NSArray *)shapes + options:(nullable NSDictionary *)options { + MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes]; + return [self initWithIdentifier:identifier shape:shapeCollection options:options]; } - (mbgl::style::GeoJSONSource *)rawSource { - return (mbgl::style::GeoJSONSource *)super.rawSource; + return (mbgl::style::GeoJSONSource *)super.rawSource; } - (NSURL *)URL { - MLNAssertStyleSourceIsValid(); - auto url = self.rawSource->getURL(); - return url ? [NSURL URLWithString:@(url->c_str())] : nil; + MLNAssertStyleSourceIsValid(); + auto url = self.rawSource->getURL(); + return url ? [NSURL URLWithString:@(url->c_str())] : nil; } - (void)setURL:(NSURL *)url { - MLNAssertStyleSourceIsValid(); - if (url) { - self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); - _shape = nil; - } else { - self.shape = nil; - } + MLNAssertStyleSourceIsValid(); + if (url) { + self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); + _shape = nil; + } else { + self.shape = nil; + } } - (void)setShape:(MLNShape *)shape { - MLNAssertStyleSourceIsValid(); - self.rawSource->setGeoJSON({ shape.geoJSONObject }); - _shape = shape; + MLNAssertStyleSourceIsValid(); + self.rawSource->setGeoJSON({shape.geoJSONObject}); + _shape = shape; } - (NSString *)description { - if (self.rawSource) { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, self.URL, self.shape]; - } - else { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = ; shape = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, self.shape]; - } + if (self.rawSource) { + return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier, self.URL, self.shape]; + } else { + return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = ; shape = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier, self.shape]; + } } -- (NSArray> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate { - MLNAssertStyleSourceIsValid(); - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } +- (NSArray> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate { + MLNAssertStyleSourceIsValid(); + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features; - if ([self.stylable isKindOfClass:[MLNMapView class]]) { - MLNMapView *mapView = (MLNMapView *)self.stylable; - features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter }); - } - return MLNFeaturesFromMBGLFeatures(features); + std::vector features; + if ([self.stylable isKindOfClass:[MLNMapView class]]) { + MLNMapView *mapView = (MLNMapView *)self.stylable; + features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), {{}, optionalFilter}); + } + return MLNFeaturesFromMBGLFeatures(features); } // MARK: - MLNCluster management -- (std::optional)featureExtensionValueOfCluster:(MLNShape *)cluster extension:(std::string)extension options:(const std::map)options { - MLNAssertStyleSourceIsValid(); - std::optional extensionValue; +- (std::optional) + featureExtensionValueOfCluster:(MLNShape *)cluster + extension:(std::string)extension + options:(const std::map)options { + MLNAssertStyleSourceIsValid(); + std::optional extensionValue; - // Check parameters - if (!self.rawSource || !self.stylable || !cluster) { - return extensionValue; - } - - auto geoJSON = [cluster geoJSONObject]; - - if (!geoJSON.is()) { - MLNAssert(0, @"cluster geoJSON object is not a feature."); - return extensionValue; - } - - auto clusterFeature = geoJSON.get(); - - if ([self.stylable isKindOfClass:[MLNMapView class]]) { - MLNMapView *mapView = (MLNMapView *)self.stylable; - extensionValue = mapView.renderer->queryFeatureExtensions(self.rawSource->getID(), - clusterFeature, - "supercluster", - extension, - options); - } + // Check parameters + if (!self.rawSource || !self.stylable || !cluster) { return extensionValue; + } + + auto geoJSON = [cluster geoJSONObject]; + + if (!geoJSON.is()) { + MLNAssert(0, @"cluster geoJSON object is not a feature."); + return extensionValue; + } + + auto clusterFeature = geoJSON.get(); + + if ([self.stylable isKindOfClass:[MLNMapView class]]) { + MLNMapView *mapView = (MLNMapView *)self.stylable; + extensionValue = mapView.renderer->queryFeatureExtensions( + self.rawSource->getID(), clusterFeature, "supercluster", extension, options); + } + return extensionValue; } -- (NSArray> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit { - const std::map options = { - { "limit", static_cast(limit) }, - { "offset", static_cast(offset) } - }; +- (NSArray> *)leavesOfCluster:(MLNPointFeatureCluster *)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]; + auto featureExtension = [self featureExtensionValueOfCluster:cluster + extension:"leaves" + options:options]; - if (!featureExtension) { - return @[]; - } + if (!featureExtension) { + return @[]; + } - if (!featureExtension->is()) { - return @[]; - } + if (!featureExtension->is()) { + return @[]; + } - std::vector leaves = featureExtension->get(); - return MLNFeaturesFromMBGLFeatures(leaves); + std::vector leaves = featureExtension->get(); + return MLNFeaturesFromMBGLFeatures(leaves); } -- (NSArray> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster { - auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"children" options:{}]; +- (NSArray> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster { + auto featureExtension = [self featureExtensionValueOfCluster:cluster + extension:"children" + options:{}]; - if (!featureExtension) { - return @[]; - } + if (!featureExtension) { + return @[]; + } - if (!featureExtension->is()) { - return @[]; - } + if (!featureExtension->is()) { + return @[]; + } - std::vector leaves = featureExtension->get(); - return MLNFeaturesFromMBGLFeatures(leaves); + std::vector leaves = featureExtension->get(); + return MLNFeaturesFromMBGLFeatures(leaves); } - (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)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); - } + 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; +- (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)]; + // 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); + printf("%*s%s\n", (int)indent, "", log.UTF8String); - MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster); + MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster); - if (cluster) { - for (id child in [self childrenOfCluster:cluster]) { - [self debugRecursiveLogForFeature:child indent:indent + 4]; - } + if (cluster) { + for (id child in [self childrenOfCluster:cluster]) { + [self debugRecursiveLogForFeature:child indent:indent + 4]; } + } } @end diff --git a/platform/darwin/src/MLNSource.mm b/platform/darwin/src/MLNSource.mm index 5e37ffffe..d0184cdb7 100644 --- a/platform/darwin/src/MLNSource.mm +++ b/platform/darwin/src/MLNSource.mm @@ -1,11 +1,11 @@ +#import "MLNMapView_Private.h" #import "MLNSource_Private.h" #import "MLNStyle_Private.h" -#import "MLNMapView_Private.h" #import "NSBundle+MLNAdditions.h" -#include #include #include +#include const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException"; @@ -15,95 +15,102 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE // special internal source types like mbgl::AnnotationSource. @property (nonatomic, readonly) mbgl::style::Source *rawSource; -@property (nonatomic, readonly, weak) id stylable; +@property (nonatomic, readonly, weak) id stylable; @end @implementation MLNSource { - std::unique_ptr _pendingSource; - mapbox::base::WeakPtr _weakSource; + std::unique_ptr _pendingSource; + mapbox::base::WeakPtr _weakSource; } - -- (instancetype)initWithIdentifier:(NSString *)identifier -{ - if (self = [super init]) { - _identifier = [identifier copy]; - } - return self; +- (instancetype)initWithIdentifier:(NSString *)identifier { + if (self = [super init]) { + _identifier = [identifier copy]; + } + return self; } -- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource stylable:(id )stylable { - NSString *identifier = @(rawSource->getID().c_str()); - if (self = [self initWithIdentifier:identifier]) { - _weakSource = rawSource->makeWeakPtr(); - rawSource->peer = SourceWrapper { self }; - _stylable = stylable; - } - return self; +- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource + stylable:(id)stylable { + NSString *identifier = @(rawSource->getID().c_str()); + if (self = [self initWithIdentifier:identifier]) { + _weakSource = rawSource->makeWeakPtr(); + rawSource->peer = SourceWrapper{self}; + _stylable = stylable; + } + return self; } -- (mbgl::style::Source *)rawSource -{ - return _weakSource.get(); +- (mbgl::style::Source *)rawSource { + return _weakSource.get(); } - (instancetype)initWithPendingSource:(std::unique_ptr)pendingSource { - if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) { - _pendingSource = std::move(pendingSource); - } - return self; + if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) { + _pendingSource = std::move(pendingSource); + } + return self; } -- (void)addToStylable:(id )stylable { - if (_pendingSource == nullptr) { - [NSException raise:MLNRedundantSourceException - format:@"This instance %@ was already added to %@. Adding the same source instance " \ - "to the style more than once is invalid.", self, stylable.style]; - } +- (void)addToStylable:(id)stylable { + if (_pendingSource == nullptr) { + [NSException raise:MLNRedundantSourceException + format:@"This instance %@ was already added to %@. Adding the same source instance " + "to the style more than once is invalid.", + self, stylable.style]; + } - _stylable = stylable; - _stylable.style.rawStyle->addSource(std::move(_pendingSource)); + _stylable = stylable; + _stylable.style.rawStyle->addSource(std::move(_pendingSource)); } -- (BOOL)removeFromStylable:(id )mapView error:(NSError * __nullable * __nullable)outError { - MLNAssertStyleSourceIsValid(); - BOOL removed = NO; +- (BOOL)removeFromStylable:(id)mapView + error:(NSError *__nullable *__nullable)outError { + MLNAssertStyleSourceIsValid(); + BOOL removed = NO; - if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) { + if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) { + auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String); - auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String); - - if (removedSource) { - removed = YES; - _pendingSource = std::move(removedSource); - _stylable = nil; - } else if (outError) { - NSString *localizedDescription = [NSString stringWithFormat: - NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil, @"The source “%@” can’t be removed while it is in use.", @"User-friendly error description; first placeholder is the source’s identifier"), - self.identifier]; - - *outError = [NSError errorWithDomain:MLNErrorDomain - code:MLNErrorCodeSourceIsInUseCannotRemove - userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; - } + if (removedSource) { + removed = YES; + _pendingSource = std::move(removedSource); + _stylable = nil; } else if (outError) { - // TODO: Consider raising an exception here - NSString *localizedDescription = [NSString stringWithFormat: - NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil, @"The source can’t be removed because its identifier, “%@”, belongs to a different source in this style.", @"User-friendly error description"), - self.identifier]; + NSString *localizedDescription = [NSString + stringWithFormat: + NSLocalizedStringWithDefaultValue( + @"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil, + @"The source “%@” can’t be removed while it is in use.", + @"User-friendly error description; first placeholder is the source’s identifier"), + self.identifier]; - *outError = [NSError errorWithDomain:MLNErrorDomain - code:MLNErrorCodeSourceIdentifierMismatch - userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; + *outError = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeSourceIsInUseCannotRemove + userInfo:@{NSLocalizedDescriptionKey : localizedDescription}]; } + } else if (outError) { + // TODO: Consider raising an exception here + NSString *localizedDescription = + [NSString stringWithFormat:NSLocalizedStringWithDefaultValue( + @"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil, + @"The source can’t be removed because its identifier, “%@”, " + @"belongs to a different source in this style.", + @"User-friendly error description"), + self.identifier]; - return removed; + *outError = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeSourceIdentifierMismatch + userInfo:@{NSLocalizedDescriptionKey : localizedDescription}]; + } + + return removed; } - (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier]; + return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", NSStringFromClass([self class]), + (void *)self, self.identifier]; } @end diff --git a/platform/darwin/src/MLNStyle.mm b/platform/darwin/src/MLNStyle.mm index 3f4d211ff..7c29cfc94 100644 --- a/platform/darwin/src/MLNStyle.mm +++ b/platform/darwin/src/MLNStyle.mm @@ -1,87 +1,92 @@ #import "MLNStyle_Private.h" -#import "MLNMapView_Private.h" -#import "MLNStyleLayer.h" -#import "MLNStyleLayer_Private.h" -#import "MLNFillStyleLayer.h" -#import "MLNFillExtrusionStyleLayer.h" -#import "MLNLineStyleLayer.h" +#import "MLNBackgroundStyleLayer.h" #import "MLNCircleStyleLayer.h" -#import "MLNSymbolStyleLayer.h" +#import "MLNFillExtrusionStyleLayer.h" +#import "MLNFillStyleLayer.h" #import "MLNHeatmapStyleLayer.h" #import "MLNHillshadeStyleLayer.h" +#import "MLNLineStyleLayer.h" +#import "MLNMapView_Private.h" #import "MLNRasterStyleLayer.h" -#import "MLNBackgroundStyleLayer.h" +#import "MLNStyleLayer.h" #import "MLNStyleLayerManager.h" +#import "MLNStyleLayer_Private.h" +#import "MLNSymbolStyleLayer.h" +#import "MLNImageSource.h" +#import "MLNLight_Private.h" +#import "MLNRasterDEMSource.h" +#import "MLNRasterTileSource.h" +#import "MLNShapeSource.h" #import "MLNSource.h" #import "MLNSource_Private.h" -#import "MLNLight_Private.h" #import "MLNTileSource_Private.h" #import "MLNVectorTileSource_Private.h" -#import "MLNRasterTileSource.h" -#import "MLNRasterDEMSource.h" -#import "MLNShapeSource.h" -#import "MLNImageSource.h" #import "MLNAttributionInfo_Private.h" #import "MLNLoggingConfiguration_Private.h" #include -#include #include #include #include -#include -#include -#include #include +#include +#include +#include +#include #import "NSDate+MLNAdditions.h" #import "MLNCustomStyleLayer.h" #if TARGET_OS_IPHONE - #import "UIImage+MLNAdditions.h" +#import "UIImage+MLNAdditions.h" #else - #import "NSImage+MLNAdditions.h" +#import "NSImage+MLNAdditions.h" #endif const MLNExceptionName MLNInvalidStyleURLException = @"MLNInvalidStyleURLException"; const MLNExceptionName MLNRedundantLayerException = @"MLNRedundantLayerException"; -const MLNExceptionName MLNRedundantLayerIdentifierException = @"MLNRedundantLayerIdentifierException"; +const MLNExceptionName MLNRedundantLayerIdentifierException = + @"MLNRedundantLayerIdentifierException"; const MLNExceptionName MLNRedundantSourceException = @"MLNRedundantSourceException"; -const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSourceIdentifierException"; +const MLNExceptionName MLNRedundantSourceIdentifierException = + @"MLNRedundantSourceIdentifierException"; /** Model class for localization changes. */ -@interface MLNTextLanguage: NSObject +@interface MLNTextLanguage : NSObject @property (strong, nonatomic) NSString *originalTextField; @property (strong, nonatomic) NSString *updatedTextField; -- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField; +- (instancetype)initWithTextLanguage:(NSString *)originalTextField + updatedTextField:(NSString *)updatedTextField; @end @implementation MLNTextLanguage -- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField -{ - if (self = [super init]) { - _originalTextField = originalTextField; - _updatedTextField = updatedTextField; - } - return self; +- (instancetype)initWithTextLanguage:(NSString *)originalTextField + updatedTextField:(NSString *)updatedTextField { + if (self = [super init]) { + _originalTextField = originalTextField; + _updatedTextField = updatedTextField; + } + return self; } @end -@interface MLNStyle() +@interface MLNStyle () -@property (nonatomic, readonly, weak) id stylable; +@property (nonatomic, readonly, weak) id stylable; @property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (readonly, copy, nullable) NSURL *URL; -@property (nonatomic, readwrite, strong) NSMutableDictionary *customLayers; -@property (nonatomic) NSMutableDictionary *> *localizedLayersByIdentifier; +@property (nonatomic, readwrite, strong) + NSMutableDictionary *customLayers; +@property (nonatomic) NSMutableDictionary *> + *localizedLayersByIdentifier; @end @@ -89,557 +94,552 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou // MARK: Predefined style URLs -+ (NSArray*) predefinedStyles { - return MLNSettings.tileServerOptions.defaultStyles; ++ (NSArray *)predefinedStyles { + return MLNSettings.tileServerOptions.defaultStyles; } -+ (MLNDefaultStyle*) defaultStyle { - MLNTileServerOptions* opts = MLNSettings.tileServerOptions; - return opts.defaultStyle; ++ (MLNDefaultStyle *)defaultStyle { + MLNTileServerOptions *opts = MLNSettings.tileServerOptions; + return opts.defaultStyle; } -+ (NSURL*) defaultStyleURL { - MLNDefaultStyle* styleDefinition = [MLNStyle defaultStyle]; - if (styleDefinition != nil){ - return styleDefinition.url; ++ (NSURL *)defaultStyleURL { + MLNDefaultStyle *styleDefinition = [MLNStyle defaultStyle]; + if (styleDefinition != nil) { + return styleDefinition.url; + } + + return nil; +} + ++ (MLNDefaultStyle *)predefinedStyle:(NSString *)withStyleName { + for (MLNDefaultStyle *style in MLNSettings.tileServerOptions.defaultStyles) { + if ([style.name isEqualToString:withStyleName]) { + return style; } - - return nil; -} - -+ (MLNDefaultStyle*) predefinedStyle:(NSString*)withStyleName { - for (MLNDefaultStyle* style in MLNSettings.tileServerOptions.defaultStyles) { - if ([style.name isEqualToString:withStyleName]) { - return style; - } - } - return nil; + } + return nil; } // MARK: - -- (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle stylable:(id )stylable { - MLNLogInfo(@"Initializing %@ with stylable: %@", NSStringFromClass([self class]), stylable); - if (self = [super init]) { - _stylable = stylable; - _rawStyle = rawStyle; - _customLayers = [NSMutableDictionary dictionary]; - _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; - MLNLogDebug(@"Initializing with style name: %@ stylable: %@", self.name, stylable); - } - return self; +- (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle stylable:(id)stylable { + MLNLogInfo(@"Initializing %@ with stylable: %@", NSStringFromClass([self class]), stylable); + if (self = [super init]) { + _stylable = stylable; + _rawStyle = rawStyle; + _customLayers = [NSMutableDictionary dictionary]; + _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; + MLNLogDebug(@"Initializing with style name: %@ stylable: %@", self.name, stylable); + } + return self; } - (NSURL *)URL { - return [NSURL URLWithString:@(self.rawStyle->getURL().c_str())]; + return [NSURL URLWithString:@(self.rawStyle->getURL().c_str())]; } - (NSString *)name { - std::string name = self.rawStyle->getName(); - return name.empty() ? nil : @(name.c_str()); + std::string name = self.rawStyle->getName(); + return name.empty() ? nil : @(name.c_str()); } - (NSString *)styleJSON { - std::string styleJSON = self.rawStyle->getJSON(); - // Return empty string if no style is set - return styleJSON.empty() ? @"" : @(styleJSON.c_str()); + std::string styleJSON = self.rawStyle->getJSON(); + // Return empty string if no style is set + return styleJSON.empty() ? @"" : @(styleJSON.c_str()); } - (void)setStyleJSON:(NSString *)styleJSON { - self.rawStyle->loadJSON([styleJSON UTF8String]); + self.rawStyle->loadJSON([styleJSON UTF8String]); } // MARK: Sources - (NSSet<__kindof MLNSource *> *)sources { - auto rawSources = self.rawStyle->getSources(); - NSMutableSet<__kindof MLNSource *> *sources = [NSMutableSet setWithCapacity:rawSources.size()]; - for (auto rawSource = rawSources.begin(); rawSource != rawSources.end(); ++rawSource) { - MLNSource *source = [self sourceFromMBGLSource:*rawSource]; - [sources addObject:source]; - } - return sources; + auto rawSources = self.rawStyle->getSources(); + NSMutableSet<__kindof MLNSource *> *sources = [NSMutableSet setWithCapacity:rawSources.size()]; + for (auto rawSource = rawSources.begin(); rawSource != rawSources.end(); ++rawSource) { + MLNSource *source = [self sourceFromMBGLSource:*rawSource]; + [sources addObject:source]; + } + return sources; } - (void)setSources:(NSSet<__kindof MLNSource *> *)sources { - MLNLogDebug(@"Setting: %lu sources", sources.count); - for (MLNSource *source in self.sources) { - [self removeSource:source]; - } - for (MLNSource *source in sources) { - [self addSource:source]; - } + MLNLogDebug(@"Setting: %lu sources", sources.count); + for (MLNSource *source in self.sources) { + [self removeSource:source]; + } + for (MLNSource *source in sources) { + [self addSource:source]; + } } - (NSUInteger)countOfSources { - return self.rawStyle->getSources().size(); + return self.rawStyle->getSources().size(); } - (MLNSource *)memberOfSources:(MLNSource *)object { - return [self sourceWithIdentifier:object.identifier]; + return [self sourceWithIdentifier:object.identifier]; } -- (MLNSource *)sourceWithIdentifier:(NSString *)identifier -{ - MLNLogDebug(@"Querying source with identifier: %@", identifier); - auto rawSource = self.rawStyle->getSource(identifier.UTF8String); +- (MLNSource *)sourceWithIdentifier:(NSString *)identifier { + MLNLogDebug(@"Querying source with identifier: %@", identifier); + auto rawSource = self.rawStyle->getSource(identifier.UTF8String); - return rawSource ? [self sourceFromMBGLSource:rawSource] : nil; + return rawSource ? [self sourceFromMBGLSource:rawSource] : nil; } - (MLNSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { - if (MLNSource *source = rawSource->peer.has_value() ? rawSource->peer.get().source : nil) { - return source; - } + if (MLNSource *source = + rawSource->peer.has_value() ? rawSource->peer.get().source : nil) { + return source; + } - // TODO: Fill in options specific to the respective source classes - // https://github.com/mapbox/mapbox-gl-native/issues/6584 - if (auto vectorSource = rawSource->as()) { - return [[MLNVectorTileSource alloc] initWithRawSource:vectorSource stylable:self.stylable]; - } else if (auto geoJSONSource = rawSource->as()) { - return [[MLNShapeSource alloc] initWithRawSource:geoJSONSource stylable:self.stylable]; - } else if (auto rasterSource = rawSource->as()) { - return [[MLNRasterTileSource alloc] initWithRawSource:rasterSource stylable:self.stylable]; - } else if (auto rasterDEMSource = rawSource->as()) { - return [[MLNRasterDEMSource alloc] initWithRawSource:rasterDEMSource stylable:self.stylable]; - } else if (auto imageSource = rawSource->as()) { - return [[MLNImageSource alloc] initWithRawSource:imageSource stylable:self.stylable]; + // TODO: Fill in options specific to the respective source classes + // https://github.com/mapbox/mapbox-gl-native/issues/6584 + if (auto vectorSource = rawSource->as()) { + return [[MLNVectorTileSource alloc] initWithRawSource:vectorSource stylable:self.stylable]; + } else if (auto geoJSONSource = rawSource->as()) { + return [[MLNShapeSource alloc] initWithRawSource:geoJSONSource stylable:self.stylable]; + } else if (auto rasterSource = rawSource->as()) { + return [[MLNRasterTileSource alloc] initWithRawSource:rasterSource stylable:self.stylable]; + } else if (auto rasterDEMSource = rawSource->as()) { + return [[MLNRasterDEMSource alloc] initWithRawSource:rasterDEMSource stylable:self.stylable]; + } else if (auto imageSource = rawSource->as()) { + return [[MLNImageSource alloc] initWithRawSource:imageSource stylable:self.stylable]; + } else { + return [[MLNSource alloc] initWithRawSource:rawSource stylable:self.stylable]; + } +} + +- (void)addSource:(MLNSource *)source { + MLNLogDebug(@"Adding source: %@", source); + if (!source.rawSource) { + [NSException + raise:NSInvalidArgumentException + format:@"The source %@ cannot be added to the style. " + @"Make sure the source was created as a member of a concrete subclass of MLNSource.", + source]; + } + + try { + [source addToStylable:self.stylable]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantSourceIdentifierException format:@"%s", err.what()]; + } +} + +- (void)removeSource:(MLNSource *)source { + [self removeSource:source error:nil]; +} + +- (BOOL)removeSource:(MLNSource *)source error:(NSError *__nullable *__nullable)outError { + MLNLogDebug(@"Removing source: %@", source); + + if (!source.rawSource) { + NSString *errorMessage = [NSString + stringWithFormat: + @"The source %@ cannot be removed from the style. " + @"Make sure the source was created as a member of a concrete subclass of MLNSource." + @"Automatic re-addition of sources after style changes is not currently supported.", + source]; + + if (outError) { + *outError = [NSError errorWithDomain:MLNErrorDomain + code:MLNErrorCodeSourceCannotBeRemovedFromStyle + userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; + return NO; } else { - return [[MLNSource alloc] initWithRawSource:rawSource stylable:self.stylable]; + [NSException raise:NSInvalidArgumentException format:@"%@", errorMessage]; } + } + + return [source removeFromStylable:self.stylable error:outError]; } -- (void)addSource:(MLNSource *)source -{ - MLNLogDebug(@"Adding source: %@", source); - if (!source.rawSource) { - [NSException raise:NSInvalidArgumentException format: - @"The source %@ cannot be added to the style. " - @"Make sure the source was created as a member of a concrete subclass of MLNSource.", - source]; +- (nullable NSArray *)attributionInfosWithFontSize:(CGFloat)fontSize + linkColor: + (nullable MLNColor *)linkColor { + // It’d be incredibly convenient to use -sources here, but this operation + // depends on the sources being sorted in ascending order by creation, as + // with the std::vector used in mbgl. + auto rawSources = self.rawStyle->getSources(); + NSMutableArray *infos = [NSMutableArray arrayWithCapacity:rawSources.size()]; + for (auto rawSource = rawSources.begin(); rawSource != rawSources.end(); ++rawSource) { + MLNTileSource *source = (MLNTileSource *)[self sourceFromMBGLSource:*rawSource]; + if (![source isKindOfClass:[MLNTileSource class]]) { + continue; } - try { - [source addToStylable:self.stylable]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantSourceIdentifierException format:@"%s", err.what()]; - } -} - -- (void)removeSource:(MLNSource *)source -{ - [self removeSource:source error:nil]; -} - -- (BOOL)removeSource:(MLNSource *)source error:(NSError * __nullable * __nullable)outError { - MLNLogDebug(@"Removing source: %@", source); - - if (!source.rawSource) { - NSString *errorMessage = [NSString stringWithFormat: - @"The source %@ cannot be removed from the style. " - @"Make sure the source was created as a member of a concrete subclass of MLNSource." - @"Automatic re-addition of sources after style changes is not currently supported.", - source]; - - if (outError) { - *outError = [NSError errorWithDomain:MLNErrorDomain - code:MLNErrorCodeSourceCannotBeRemovedFromStyle - userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; - return NO; - } - else { - [NSException raise:NSInvalidArgumentException format:@"%@", errorMessage]; - } - } - - return [source removeFromStylable:self.stylable error:outError]; -} - - -- (nullable NSArray *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { - // It’d be incredibly convenient to use -sources here, but this operation - // depends on the sources being sorted in ascending order by creation, as - // with the std::vector used in mbgl. - auto rawSources = self.rawStyle->getSources(); - NSMutableArray *infos = [NSMutableArray arrayWithCapacity:rawSources.size()]; - for (auto rawSource = rawSources.begin(); rawSource != rawSources.end(); ++rawSource) { - MLNTileSource *source = (MLNTileSource *)[self sourceFromMBGLSource:*rawSource]; - if (![source isKindOfClass:[MLNTileSource class]]) { - continue; - } - - NSArray *tileSetInfos = [source attributionInfosWithFontSize:fontSize linkColor:linkColor]; - [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; - } - return infos; + NSArray *tileSetInfos = [source attributionInfosWithFontSize:fontSize linkColor:linkColor]; + [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; + } + return infos; } // MARK: Style layers -- (NSArray<__kindof MLNStyleLayer *> *)layers -{ - auto layers = self.rawStyle->getLayers(); - NSMutableArray<__kindof MLNStyleLayer *> *styleLayers = [NSMutableArray arrayWithCapacity:layers.size()]; - for (auto layer : layers) { - MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; - [styleLayers addObject:styleLayer]; - } - return styleLayers; +- (NSArray<__kindof MLNStyleLayer *> *)layers { + auto layers = self.rawStyle->getLayers(); + NSMutableArray<__kindof MLNStyleLayer *> *styleLayers = + [NSMutableArray arrayWithCapacity:layers.size()]; + for (auto layer : layers) { + MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; + [styleLayers addObject:styleLayer]; + } + return styleLayers; } - (void)setLayers:(NSArray<__kindof MLNStyleLayer *> *)layers { - MLNLogDebug(@"Setting: %lu layers", layers.count); - for (MLNStyleLayer *layer in self.layers) { - [self removeLayer:layer]; - } - for (MLNStyleLayer *layer in layers) { - [self addLayer:layer]; - } + MLNLogDebug(@"Setting: %lu layers", layers.count); + for (MLNStyleLayer *layer in self.layers) { + [self removeLayer:layer]; + } + for (MLNStyleLayer *layer in layers) { + [self addLayer:layer]; + } } -- (NSUInteger)countOfLayers -{ - return self.rawStyle->getLayers().size(); +- (NSUInteger)countOfLayers { + return self.rawStyle->getLayers().size(); } -- (MLNStyleLayer *)objectInLayersAtIndex:(NSUInteger)index -{ - auto layers = self.rawStyle->getLayers(); - if (index >= layers.size()) { - [NSException raise:NSRangeException - format:@"No style layer at index %lu.", (unsigned long)index]; - return nil; - } - auto layer = layers.at(index); - return [self layerFromMBGLLayer:layer]; +- (MLNStyleLayer *)objectInLayersAtIndex:(NSUInteger)index { + auto layers = self.rawStyle->getLayers(); + if (index >= layers.size()) { + [NSException raise:NSRangeException + format:@"No style layer at index %lu.", (unsigned long)index]; + return nil; + } + auto layer = layers.at(index); + return [self layerFromMBGLLayer:layer]; } -- (void)getLayers:(MLNStyleLayer **)buffer range:(NSRange)inRange -{ - auto layers = self.rawStyle->getLayers(); - if (NSMaxRange(inRange) > layers.size()) { - [NSException raise:NSRangeException - format:@"Style layer range %@ is out of bounds.", NSStringFromRange(inRange)]; - } - NSUInteger i = 0; - for (auto layer = *(layers.rbegin() + inRange.location); i < inRange.length; ++layer, ++i) { - MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; - buffer[i] = styleLayer; - } -} - -- (void)insertObject:(MLNStyleLayer *)styleLayer inLayersAtIndex:(NSUInteger)index -{ - if (!styleLayer.rawLayer) { - [NSException raise:NSInvalidArgumentException format: - @"The style layer %@ cannot be inserted into the style. " - @"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", - styleLayer]; - } - auto layers = self.rawStyle->getLayers(); - if (index > layers.size()) { - [NSException raise:NSRangeException - format:@"Cannot insert style layer at out-of-bounds index %lu.", (unsigned long)index]; - } else if (index == 0) { - try { - MLNStyleLayer *sibling = layers.size() ? [self layerFromMBGLLayer:layers.at(0)] : nil; - [styleLayer addToStyle:self belowLayer:sibling]; - } catch (const std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; - } - } else { - try { - MLNStyleLayer *sibling = [self layerFromMBGLLayer:layers.at(index)]; - [styleLayer addToStyle:self belowLayer:sibling]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; - } - } -} - -- (void)removeObjectFromLayersAtIndex:(NSUInteger)index -{ - auto layers = self.rawStyle->getLayers(); - if (index >= layers.size()) { - [NSException raise:NSRangeException - format:@"Cannot remove style layer at out-of-bounds index %lu.", (unsigned long)index]; - } - auto layer = layers.at(index); +- (void)getLayers:(MLNStyleLayer **)buffer range:(NSRange)inRange { + auto layers = self.rawStyle->getLayers(); + if (NSMaxRange(inRange) > layers.size()) { + [NSException raise:NSRangeException + format:@"Style layer range %@ is out of bounds.", NSStringFromRange(inRange)]; + } + NSUInteger i = 0; + for (auto layer = *(layers.rbegin() + inRange.location); i < inRange.length; ++layer, ++i) { MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; - [styleLayer removeFromStyle:self]; + buffer[i] = styleLayer; + } } -- (MLNStyleLayer *)layerFromMBGLLayer:(mbgl::style::Layer *)rawLayer -{ - NSParameterAssert(rawLayer); - - if (MLNStyleLayer *layer = rawLayer->peer.has_value() ? rawLayer->peer.get().layer : nil) { - return layer; - } - - return mbgl::LayerManagerDarwin::get()->createPeer(rawLayer); -} - -- (MLNStyleLayer *)layerWithIdentifier:(NSString *)identifier -{ - MLNLogDebug(@"Querying layerWithIdentifier: %@", identifier); - auto mbglLayer = self.rawStyle->getLayer(identifier.UTF8String); - return mbglLayer ? [self layerFromMBGLLayer:mbglLayer] : nil; -} - -- (void)removeLayer:(MLNStyleLayer *)layer -{ - MLNLogDebug(@"Removing layer: %@", layer); - if (!layer.rawLayer) { - [NSException raise:NSInvalidArgumentException format: - @"The style layer %@ cannot be removed from the style. " - @"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", - layer]; - } - [self willChangeValueForKey:@"layers"]; - [layer removeFromStyle:self]; - [self didChangeValueForKey:@"layers"]; -} - -- (void)addLayer:(MLNStyleLayer *)layer -{ - MLNLogDebug(@"Adding layer: %@", layer); - if (!layer.rawLayer) { - [NSException raise:NSInvalidArgumentException format: - @"The style layer %@ cannot be added to the style. " - @"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", - layer]; - } - [self willChangeValueForKey:@"layers"]; +- (void)insertObject:(MLNStyleLayer *)styleLayer inLayersAtIndex:(NSUInteger)index { + if (!styleLayer.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"The style layer %@ cannot be inserted into the style. " + @"Make sure the style layer was created as a member of a concrete subclass " + @"of MLNStyleLayer.", + styleLayer]; + } + auto layers = self.rawStyle->getLayers(); + if (index > layers.size()) { + [NSException + raise:NSRangeException + format:@"Cannot insert style layer at out-of-bounds index %lu.", (unsigned long)index]; + } else if (index == 0) { try { - [layer addToStyle:self belowLayer:nil]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; + MLNStyleLayer *sibling = layers.size() ? [self layerFromMBGLLayer:layers.at(0)] : nil; + [styleLayer addToStyle:self belowLayer:sibling]; + } catch (const std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; } - [self didChangeValueForKey:@"layers"]; + } else { + try { + MLNStyleLayer *sibling = [self layerFromMBGLLayer:layers.at(index)]; + [styleLayer addToStyle:self belowLayer:sibling]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; + } + } +} + +- (void)removeObjectFromLayersAtIndex:(NSUInteger)index { + auto layers = self.rawStyle->getLayers(); + if (index >= layers.size()) { + [NSException + raise:NSRangeException + format:@"Cannot remove style layer at out-of-bounds index %lu.", (unsigned long)index]; + } + auto layer = layers.at(index); + MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; + [styleLayer removeFromStyle:self]; +} + +- (MLNStyleLayer *)layerFromMBGLLayer:(mbgl::style::Layer *)rawLayer { + NSParameterAssert(rawLayer); + + if (MLNStyleLayer *layer = + rawLayer->peer.has_value() ? rawLayer->peer.get().layer : nil) { + return layer; + } + + return mbgl::LayerManagerDarwin::get()->createPeer(rawLayer); +} + +- (MLNStyleLayer *)layerWithIdentifier:(NSString *)identifier { + MLNLogDebug(@"Querying layerWithIdentifier: %@", identifier); + auto mbglLayer = self.rawStyle->getLayer(identifier.UTF8String); + return mbglLayer ? [self layerFromMBGLLayer:mbglLayer] : nil; +} + +- (void)removeLayer:(MLNStyleLayer *)layer { + MLNLogDebug(@"Removing layer: %@", layer); + if (!layer.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"The style layer %@ cannot be removed from the style. " + @"Make sure the style layer was created as a member of a concrete subclass " + @"of MLNStyleLayer.", + layer]; + } + [self willChangeValueForKey:@"layers"]; + [layer removeFromStyle:self]; + [self didChangeValueForKey:@"layers"]; +} + +- (void)addLayer:(MLNStyleLayer *)layer { + MLNLogDebug(@"Adding layer: %@", layer); + if (!layer.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"The style layer %@ cannot be added to the style. " + @"Make sure the style layer was created as a member of a concrete subclass " + @"of MLNStyleLayer.", + layer]; + } + [self willChangeValueForKey:@"layers"]; + try { + [layer addToStyle:self belowLayer:nil]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; + } + [self didChangeValueForKey:@"layers"]; } - (void)insertLayer:(MLNStyleLayer *)layer atIndex:(NSUInteger)index { - [self insertObject:layer inLayersAtIndex:index]; + [self insertObject:layer inLayersAtIndex:index]; } -- (void)insertLayer:(MLNStyleLayer *)layer belowLayer:(MLNStyleLayer *)sibling -{ - MLNLogDebug(@"Inseting layer: %@ belowLayer: %@", layer, sibling); - if (!layer.rawLayer) { - [NSException raise:NSInvalidArgumentException - format: - @"The style layer %@ cannot be added to the style. " - @"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", - layer]; - } - if (!sibling.rawLayer) { - [NSException raise:NSInvalidArgumentException - format: - @"A style layer cannot be placed below %@ in the style. " - @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", - sibling]; - } - [self willChangeValueForKey:@"layers"]; - try { - [layer addToStyle:self belowLayer:sibling]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; - } - [self didChangeValueForKey:@"layers"]; +- (void)insertLayer:(MLNStyleLayer *)layer belowLayer:(MLNStyleLayer *)sibling { + MLNLogDebug(@"Inseting layer: %@ belowLayer: %@", layer, sibling); + if (!layer.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"The style layer %@ cannot be added to the style. " + @"Make sure the style layer was created as a member of a concrete subclass " + @"of MLNStyleLayer.", + layer]; + } + if (!sibling.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"A style layer cannot be placed below %@ in the style. " + @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", + sibling]; + } + [self willChangeValueForKey:@"layers"]; + try { + [layer addToStyle:self belowLayer:sibling]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; + } + [self didChangeValueForKey:@"layers"]; } - (void)insertLayer:(MLNStyleLayer *)layer aboveLayer:(MLNStyleLayer *)sibling { - MLNLogDebug(@"Inseting layer: %@ aboveLayer: %@", layer, sibling); - if (!layer.rawLayer) { - [NSException raise:NSInvalidArgumentException - format: - @"The style layer %@ cannot be added to the style. " - @"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", - layer]; - } - if (!sibling.rawLayer) { - [NSException raise:NSInvalidArgumentException - format: - @"A style layer cannot be placed above %@ in the style. " - @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", - sibling]; - } + MLNLogDebug(@"Inseting layer: %@ aboveLayer: %@", layer, sibling); + if (!layer.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"The style layer %@ cannot be added to the style. " + @"Make sure the style layer was created as a member of a concrete subclass " + @"of MLNStyleLayer.", + layer]; + } + if (!sibling.rawLayer) { + [NSException raise:NSInvalidArgumentException + format:@"A style layer cannot be placed above %@ in the style. " + @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", + sibling]; + } - auto layers = self.rawStyle->getLayers(); - std::string siblingIdentifier = sibling.identifier.UTF8String; - NSUInteger index = 0; - for (auto siblingLayer : layers) { - if (siblingLayer->getID() == siblingIdentifier) { - break; - } - index++; + auto layers = self.rawStyle->getLayers(); + std::string siblingIdentifier = sibling.identifier.UTF8String; + NSUInteger index = 0; + for (auto siblingLayer : layers) { + if (siblingLayer->getID() == siblingIdentifier) { + break; } + index++; + } - [self willChangeValueForKey:@"layers"]; - if (index + 1 > layers.size()) { - [NSException raise:NSInvalidArgumentException - format: - @"A style layer cannot be placed above %@ in the style. " - @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", - sibling]; - } else if (index + 1 == layers.size()) { - try { - [layer addToStyle:self belowLayer:nil]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; - } - } else { - MLNStyleLayer *nextSibling = [self layerFromMBGLLayer:layers.at(index + 1)]; - try { - [layer addToStyle:self belowLayer:nextSibling]; - } catch (std::runtime_error & err) { - [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; - } + [self willChangeValueForKey:@"layers"]; + if (index + 1 > layers.size()) { + [NSException raise:NSInvalidArgumentException + format:@"A style layer cannot be placed above %@ in the style. " + @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", + sibling]; + } else if (index + 1 == layers.size()) { + try { + [layer addToStyle:self belowLayer:nil]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; } - [self didChangeValueForKey:@"layers"]; + } else { + MLNStyleLayer *nextSibling = [self layerFromMBGLLayer:layers.at(index + 1)]; + try { + [layer addToStyle:self belowLayer:nextSibling]; + } catch (std::runtime_error &err) { + [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; + } + } + [self didChangeValueForKey:@"layers"]; } // MARK: Style images -- (void)setImage:(MLNImage *)image forName:(NSString *)name -{ - MLNLogDebug(@"Setting image: %@ forName: %@", image, name); - if (!image) { - [NSException raise:NSInvalidArgumentException - format:@"Cannot assign nil image to “%@”.", name]; - } - if (!name) { - [NSException raise:NSInvalidArgumentException - format:@"Cannot assign image %@ to a nil name.", image]; - } +- (void)setImage:(MLNImage *)image forName:(NSString *)name { + MLNLogDebug(@"Setting image: %@ forName: %@", image, name); + if (!image) { + [NSException raise:NSInvalidArgumentException format:@"Cannot assign nil image to “%@”.", name]; + } + if (!name) { + [NSException raise:NSInvalidArgumentException + format:@"Cannot assign image %@ to a nil name.", image]; + } - self.rawStyle->addImage([image mgl_styleImageWithIdentifier:name]); + self.rawStyle->addImage([image mgl_styleImageWithIdentifier:name]); } -- (void)removeImageForName:(NSString *)name -{ - MLNLogDebug(@"Removing imageForName: %@", name); - if (!name) { - [NSException raise:NSInvalidArgumentException - format:@"Cannot remove image with nil name."]; - } +- (void)removeImageForName:(NSString *)name { + MLNLogDebug(@"Removing imageForName: %@", name); + if (!name) { + [NSException raise:NSInvalidArgumentException format:@"Cannot remove image with nil name."]; + } - self.rawStyle->removeImage([name UTF8String]); + self.rawStyle->removeImage([name UTF8String]); } -- (MLNImage *)imageForName:(NSString *)name -{ - MLNLogDebug(@"Querying imageForName: %@", name); - if (!name) { - [NSException raise:NSInvalidArgumentException - format:@"Cannot get image with nil name."]; - } +- (MLNImage *)imageForName:(NSString *)name { + MLNLogDebug(@"Querying imageForName: %@", name); + if (!name) { + [NSException raise:NSInvalidArgumentException format:@"Cannot get image with nil name."]; + } - auto styleImage = self.rawStyle->getImage([name UTF8String]); - return styleImage ? [[MLNImage alloc] initWithMLNStyleImage:*styleImage] : nil; + auto styleImage = self.rawStyle->getImage([name UTF8String]); + return styleImage ? [[MLNImage alloc] initWithMLNStyleImage:*styleImage] : nil; } // MARK: Style transitions -- (void)setTransition:(MLNTransition)transition -{ - self.rawStyle->setTransitionOptions(MLNOptionsFromTransition(transition)); +- (void)setTransition:(MLNTransition)transition { + self.rawStyle->setTransitionOptions(MLNOptionsFromTransition(transition)); } -- (MLNTransition)transition -{ - const mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); +- (MLNTransition)transition { + const mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); - return MLNTransitionFromOptions(transitionOptions); + return MLNTransitionFromOptions(transitionOptions); } -- (void)setPerformsPlacementTransitions:(BOOL)performsPlacementTransitions -{ - mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); - transitionOptions.enablePlacementTransitions = static_cast(performsPlacementTransitions); - self.rawStyle->setTransitionOptions(transitionOptions); +- (void)setPerformsPlacementTransitions:(BOOL)performsPlacementTransitions { + mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); + transitionOptions.enablePlacementTransitions = static_cast(performsPlacementTransitions); + self.rawStyle->setTransitionOptions(transitionOptions); } -- (BOOL)performsPlacementTransitions -{ - mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); - return transitionOptions.enablePlacementTransitions; +- (BOOL)performsPlacementTransitions { + mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); + return transitionOptions.enablePlacementTransitions; } // MARK: Style light -- (void)setLight:(MLNLight *)light -{ - std::unique_ptr mbglLight = std::make_unique([light mbglLight]); - self.rawStyle->setLight(std::move(mbglLight)); +- (void)setLight:(MLNLight *)light { + std::unique_ptr mbglLight = + std::make_unique([light mbglLight]); + self.rawStyle->setLight(std::move(mbglLight)); } -- (MLNLight *)light -{ - auto mbglLight = self.rawStyle->getLight(); - MLNLight *light = [[MLNLight alloc] initWithMBGLLight:mbglLight]; - return light; +- (MLNLight *)light { + auto mbglLight = self.rawStyle->getLight(); + MLNLight *light = [[MLNLight alloc] initWithMBGLLight:mbglLight]; + return light; } -- (NSString *)description -{ - return [NSString stringWithFormat:@"<%@: %p; name = %@, URL = %@>", - NSStringFromClass([self class]), (void *)self, - self.name ? [NSString stringWithFormat:@"\"%@\"", self.name] : self.name, - self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL]; +- (NSString *)description { + return [NSString + stringWithFormat:@"<%@: %p; name = %@, URL = %@>", NSStringFromClass([self class]), + (void *)self, + self.name ? [NSString stringWithFormat:@"\"%@\"", self.name] : self.name, + self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL]; } // MARK: Mapbox Streets source introspection - (void)localizeLabelsIntoLocale:(nullable NSLocale *)locale { - NSSet *streetsSources = - [self.sources filteredSetUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL(MLNVectorTileSource * _Nullable source, NSDictionary * _Nullable bindings) { - return [source isKindOfClass:[MLNVectorTileSource class]] && [source isMapboxStreets]; - }]]; - NSSet *streetsSourceIdentifiers = [streetsSources valueForKey:@"identifier"]; + NSSet *streetsSources = [self.sources + filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + MLNVectorTileSource *_Nullable source, + NSDictionary *_Nullable bindings) { + return [source isKindOfClass:[MLNVectorTileSource class]] && [source isMapboxStreets]; + }]]; + NSSet *streetsSourceIdentifiers = [streetsSources valueForKey:@"identifier"]; - for (MLNSymbolStyleLayer *layer in self.layers) { - if (![layer isKindOfClass:[MLNSymbolStyleLayer class]]) { - continue; - } - if (![streetsSourceIdentifiers containsObject:layer.sourceIdentifier]) { - continue; - } - - NSExpression *text = layer.text; - NSExpression *localizedText = [text mgl_expressionLocalizedIntoLocale:locale]; - if (![localizedText isEqual:text]) { - layer.text = localizedText; - } + for (MLNSymbolStyleLayer *layer in self.layers) { + if (![layer isKindOfClass:[MLNSymbolStyleLayer class]]) { + continue; } + if (![streetsSourceIdentifiers containsObject:layer.sourceIdentifier]) { + continue; + } + + NSExpression *text = layer.text; + NSExpression *localizedText = [text mgl_expressionLocalizedIntoLocale:locale]; + if (![localizedText isEqual:text]) { + layer.text = localizedText; + } + } } - (NSSet *)mapboxStreetsSources { - return [self.sources objectsPassingTest:^BOOL (__kindof MLNVectorTileSource * _Nonnull source, BOOL * _Nonnull stop) { + return [self.sources + objectsPassingTest:^BOOL(__kindof MLNVectorTileSource *_Nonnull source, BOOL *_Nonnull stop) { return [source isKindOfClass:[MLNVectorTileSource class]] && source.mapboxStreets; - }]; + }]; } - (NSArray *)placeStyleLayers { - NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; + NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; - NSSet *placeSourceLayerIdentifiers = [NSSet setWithObjects:@"marine_label", @"country_label", @"state_label", @"place_label", @"water_label", @"poi_label", @"rail_station_label", @"mountain_peak_label", @"natural_label", @"transit_stop_label", nil]; - NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MLNVectorStyleLayer * _Nullable layer, NSDictionary * _Nullable bindings) { - return [layer isKindOfClass:[MLNVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [placeSourceLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; - }]; - return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; + NSSet *placeSourceLayerIdentifiers = + [NSSet setWithObjects:@"marine_label", @"country_label", @"state_label", @"place_label", + @"water_label", @"poi_label", @"rail_station_label", + @"mountain_peak_label", @"natural_label", @"transit_stop_label", nil]; + NSPredicate *isPlacePredicate = + [NSPredicate predicateWithBlock:^BOOL(MLNVectorStyleLayer *_Nullable layer, + NSDictionary *_Nullable bindings) { + return [layer isKindOfClass:[MLNVectorStyleLayer class]] && + [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && + [placeSourceLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; + }]; + return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; } - (NSArray *)roadStyleLayers { - NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; + NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; - NSSet *roadStyleLayerIdentifiers = [NSSet setWithObjects:@"road_label", @"road", nil]; - NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MLNVectorStyleLayer * _Nullable layer, NSDictionary * _Nullable bindings) { - return [layer isKindOfClass:[MLNVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [roadStyleLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; - }]; - return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; + NSSet *roadStyleLayerIdentifiers = [NSSet setWithObjects:@"road_label", @"road", nil]; + NSPredicate *isPlacePredicate = + [NSPredicate predicateWithBlock:^BOOL(MLNVectorStyleLayer *_Nullable layer, + NSDictionary *_Nullable bindings) { + return [layer isKindOfClass:[MLNVectorStyleLayer class]] && + [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && + [roadStyleLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; + }]; + return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; } @end diff --git a/platform/darwin/src/MLNStyleLayer.mm b/platform/darwin/src/MLNStyleLayer.mm index 7b410345b..50bd27ad5 100644 --- a/platform/darwin/src/MLNStyleLayer.mm +++ b/platform/darwin/src/MLNStyleLayer.mm @@ -1,8 +1,8 @@ #import "MLNStyleLayer_Private.h" #import "MLNStyle_Private.h" -#include #include +#include const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerException"; @@ -13,111 +13,101 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc @end @implementation MLNStyleLayer { - std::unique_ptr _pendingLayer; - mapbox::base::WeakPtr _weakLayer; + std::unique_ptr _pendingLayer; + mapbox::base::WeakPtr _weakLayer; } - (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { - if (self = [super init]) { - _identifier = @(rawLayer->getID().c_str()); - _weakLayer = rawLayer->makeWeakPtr(); - rawLayer->peer = LayerWrapper { self }; - } - return self; + if (self = [super init]) { + _identifier = @(rawLayer->getID().c_str()); + _weakLayer = rawLayer->makeWeakPtr(); + rawLayer->peer = LayerWrapper{self}; + } + return self; } - (instancetype)initWithPendingLayer:(std::unique_ptr)pendingLayer { - if (self = [self initWithRawLayer:pendingLayer.get()]) { - _pendingLayer = std::move(pendingLayer); - } - return self; + if (self = [self initWithRawLayer:pendingLayer.get()]) { + _pendingLayer = std::move(pendingLayer); + } + return self; } -- (mbgl::style::Layer *)rawLayer -{ - return _weakLayer.get(); +- (mbgl::style::Layer *)rawLayer { + return _weakLayer.get(); } -- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer -{ - if (_pendingLayer == nullptr) { - [NSException raise:MLNRedundantLayerException - format:@"This instance %@ was already added to %@. Adding the same layer instance " \ - "to the style more than once is invalid.", self, style]; - } +- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer { + if (_pendingLayer == nullptr) { + [NSException raise:MLNRedundantLayerException + format:@"This instance %@ was already added to %@. Adding the same layer instance " + "to the style more than once is invalid.", + self, style]; + } - if (otherLayer) { - const std::optional belowLayerId{otherLayer.identifier.UTF8String}; - style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId); - } else { - style.rawStyle->addLayer(std::move(_pendingLayer)); - } + if (otherLayer) { + const std::optional belowLayerId{otherLayer.identifier.UTF8String}; + style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId); + } else { + style.rawStyle->addLayer(std::move(_pendingLayer)); + } } -- (void)removeFromStyle:(MLNStyle *)style -{ - if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) { - _pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String); - } +- (void)removeFromStyle:(MLNStyle *)style { + if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) { + _pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String); + } } -- (void)setVisible:(BOOL)visible -{ - MLNAssertStyleLayerIsValid(); +- (void)setVisible:(BOOL)visible { + MLNAssertStyleLayerIsValid(); - mbgl::style::VisibilityType v = visible - ? mbgl::style::VisibilityType::Visible - : mbgl::style::VisibilityType::None; - self.rawLayer->setVisibility(v); + mbgl::style::VisibilityType v = + visible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None; + self.rawLayer->setVisibility(v); } -- (BOOL)isVisible -{ - MLNAssertStyleLayerIsValid(); +- (BOOL)isVisible { + MLNAssertStyleLayerIsValid(); - mbgl::style::VisibilityType v = self.rawLayer->getVisibility(); - return (v == mbgl::style::VisibilityType::Visible); + mbgl::style::VisibilityType v = self.rawLayer->getVisibility(); + return (v == mbgl::style::VisibilityType::Visible); } -- (void)setMaximumZoomLevel:(float)maximumZoomLevel -{ - MLNAssertStyleLayerIsValid(); +- (void)setMaximumZoomLevel:(float)maximumZoomLevel { + MLNAssertStyleLayerIsValid(); - self.rawLayer->setMaxZoom(maximumZoomLevel); + self.rawLayer->setMaxZoom(maximumZoomLevel); } -- (float)maximumZoomLevel -{ - MLNAssertStyleLayerIsValid(); +- (float)maximumZoomLevel { + MLNAssertStyleLayerIsValid(); - return self.rawLayer->getMaxZoom(); + return self.rawLayer->getMaxZoom(); } -- (void)setMinimumZoomLevel:(float)minimumZoomLevel -{ - MLNAssertStyleLayerIsValid(); +- (void)setMinimumZoomLevel:(float)minimumZoomLevel { + MLNAssertStyleLayerIsValid(); - self.rawLayer->setMinZoom(minimumZoomLevel); + self.rawLayer->setMinZoom(minimumZoomLevel); } -- (float)minimumZoomLevel -{ - MLNAssertStyleLayerIsValid(); +- (float)minimumZoomLevel { + MLNAssertStyleLayerIsValid(); - return self.rawLayer->getMinZoom(); + return self.rawLayer->getMinZoom(); } -- (NSString *)description -{ - if (self.rawLayer) { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, - self.visible ? @"YES" : @"NO"]; - } - else { - return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>", - NSStringFromClass([self class]), (void *)self, self.identifier]; - } +- (NSString *)description { + if (self.rawLayer) { + return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier, self.visible ? @"YES" : @"NO"]; + } else { + return + [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>", + NSStringFromClass([self class]), (void *)self, self.identifier]; + } } @end diff --git a/platform/darwin/src/MLNStyleLayerManager.mm b/platform/darwin/src/MLNStyleLayerManager.mm index 4f5e0822b..cb7ee76f5 100644 --- a/platform/darwin/src/MLNStyleLayerManager.mm +++ b/platform/darwin/src/MLNStyleLayerManager.mm @@ -2,6 +2,7 @@ #import "MLNBackgroundStyleLayer_Private.h" #import "MLNCircleStyleLayer_Private.h" +#import "MLNCustomStyleLayer_Private.h" #import "MLNFillExtrusionStyleLayer_Private.h" #import "MLNFillStyleLayer_Private.h" #import "MLNHeatmapStyleLayer_Private.h" @@ -9,7 +10,6 @@ #import "MLNLineStyleLayer_Private.h" #import "MLNRasterStyleLayer_Private.h" #import "MLNSymbolStyleLayer_Private.h" -#import "MLNCustomStyleLayer_Private.h" #import "MLNCustomDrawableStyleLayer_Private.h" @@ -19,138 +19,138 @@ namespace mbgl { LayerManagerDarwin::LayerManagerDarwin() { #if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_FILL_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_LINE_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_LINE_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_CIRCLE_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_CIRCLE_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_SYMBOL_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_RASTER_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_RASTER_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_BACKGROUND_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_BACKGROUND_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_HILLSHADE_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_HEATMAP_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MBGL_LAYER_CUSTOM_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif #if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME) - addLayerTypeCoreOnly(std::make_unique()); + addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL) - addLayerType(std::make_unique()); + addLayerType(std::make_unique()); #endif } LayerManagerDarwin::~LayerManagerDarwin() = default; MLNStyleLayer* LayerManagerDarwin::createPeer(style::Layer* layer) { - if (auto* factory = getPeerFactory(layer->getTypeInfo())) { - return factory->createPeer(layer); - } - return nullptr; + if (auto* factory = getPeerFactory(layer->getTypeInfo())) { + return factory->createPeer(layer); + } + return nullptr; } void LayerManagerDarwin::addLayerType(std::unique_ptr factory) { - NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr, - @"A layer factory with the given info is already added."); - registerCoreFactory(factory->getCoreLayerFactory()); - peerFactories.emplace_back(std::move(factory)); + NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr, + @"A layer factory with the given info is already added."); + registerCoreFactory(factory->getCoreLayerFactory()); + peerFactories.emplace_back(std::move(factory)); } void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr factory) { - NSCAssert(getFactory(factory->getTypeInfo()) == nullptr, - @"A layer factory with the given info is already added."); - registerCoreFactory(factory.get()); - coreFactories.emplace_back(std::move(factory)); + NSCAssert(getFactory(factory->getTypeInfo()) == nullptr, + @"A layer factory with the given info is already added."); + registerCoreFactory(factory.get()); + coreFactories.emplace_back(std::move(factory)); } void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) { - std::string type{factory->getTypeInfo()->type}; - if (!type.empty()) { - NSCAssert(typeToFactory.find(type) == typeToFactory.end(), @"A layer type can be registered only once."); - typeToFactory.emplace(std::make_pair(std::move(type), factory)); - } + std::string type{factory->getTypeInfo()->type}; + if (!type.empty()) { + NSCAssert(typeToFactory.find(type) == typeToFactory.end(), + @"A layer type can be registered only once."); + typeToFactory.emplace(std::make_pair(std::move(type), factory)); + } } LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) { - for (const auto& factory: peerFactories) { - if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) { - return factory.get(); - } + for (const auto& factory : peerFactories) { + if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) { + return factory.get(); } - return nullptr; + } + return nullptr; } LayerFactory* LayerManagerDarwin::getFactory(const std::string& type) noexcept { - auto search = typeToFactory.find(type); - return (search != typeToFactory.end()) ? search->second : nullptr; + auto search = typeToFactory.find(type); + return (search != typeToFactory.end()) ? search->second : nullptr; } LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* info) noexcept { - if (LayerPeerFactory* peerFactory = getPeerFactory(info)) { - return peerFactory->getCoreLayerFactory(); - } + if (LayerPeerFactory* peerFactory = getPeerFactory(info)) { + return peerFactory->getCoreLayerFactory(); + } - for (const auto& factory: coreFactories) { - if (layerTypeInfoEquals(factory->getTypeInfo(), info)) { + for (const auto& factory : coreFactories) { + if (layerTypeInfoEquals(factory->getTypeInfo(), info)) { // if (factory->getTypeInfo() == info) { - return factory.get(); - } + return factory.get(); } + } - return nullptr; + return nullptr; } // static LayerManagerDarwin* LayerManagerDarwin::get() noexcept { - static LayerManagerDarwin impl; - return &impl; + static LayerManagerDarwin impl; + return &impl; } // static -LayerManager* LayerManager::get() noexcept { - return LayerManagerDarwin::get(); -} +LayerManager* LayerManager::get() noexcept { return LayerManagerDarwin::get(); } -#if defined(MBGL_LAYER_LINE_DISABLE_ALL) || defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) || defined(MBGL_LAYER_FILL_DISABLE_ALL) +#if defined(MBGL_LAYER_LINE_DISABLE_ALL) || defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) || \ + defined(MBGL_LAYER_FILL_DISABLE_ALL) const bool LayerManager::annotationsEnabled = false; #else const bool LayerManager::annotationsEnabled = true; #endif -} // namespace mbgl +} // namespace mbgl diff --git a/platform/darwin/src/MLNStyleLayer_Private.h b/platform/darwin/src/MLNStyleLayer_Private.h index c74b4eb85..24d503fe2 100644 --- a/platform/darwin/src/MLNStyleLayer_Private.h +++ b/platform/darwin/src/MLNStyleLayer_Private.h @@ -87,7 +87,7 @@ struct LayerWrapper { namespace mbgl { class LayerPeerFactory { - public: +public: virtual ~LayerPeerFactory() = default; /** Get the corresponding core layer factory. diff --git a/platform/darwin/src/MLNStyleValue.mm b/platform/darwin/src/MLNStyleValue.mm index 900da1102..8ce2a1bf9 100644 --- a/platform/darwin/src/MLNStyleValue.mm +++ b/platform/darwin/src/MLNStyleValue.mm @@ -2,60 +2,56 @@ #include -const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase = @"MLNStyleFunctionOptionInterpolationBase"; -const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue = @"MLNStyleFunctionOptionDefaultValue"; +const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase = + @"MLNStyleFunctionOptionInterpolationBase"; +const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue = + @"MLNStyleFunctionOptionDefaultValue"; id MLNJSONObjectFromMBGLValue(const mbgl::Value &value) { - return value.match([](const mbgl::NullValue) -> id { - return [NSNull null]; - }, [](const bool value) { - return @(value); - }, [](const float value) { - return @(value); - }, [](const int64_t value) { - return @(value); - }, [](const uint64_t value) { - return @(value); - }, [](const double value) { - return @(value); - }, [](const std::string &value) { - return @(value.c_str()); - }, [](const mbgl::Color &value) { - return [MLNColor mgl_colorWithColor:value]; - }, [](const mbgl::style::Position &value) { + return value.match( + [](const mbgl::NullValue) -> id { return [NSNull null]; }, + [](const bool value) { return @(value); }, [](const float value) { return @(value); }, + [](const int64_t value) { return @(value); }, [](const uint64_t value) { return @(value); }, + [](const double value) { return @(value); }, + [](const std::string &value) { return @(value.c_str()); }, + [](const mbgl::Color &value) { return [MLNColor mgl_colorWithColor:value]; }, + [](const mbgl::style::Position &value) { std::array spherical = value.getSpherical(); - MLNSphericalPosition position = MLNSphericalPositionMake(spherical[0], spherical[1], spherical[2]); + MLNSphericalPosition position = + MLNSphericalPositionMake(spherical[0], spherical[1], spherical[2]); return [NSValue valueWithMLNSphericalPosition:position]; - }, [&](const std::vector &vector) { + }, + [&](const std::vector &vector) { NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()]; for (auto value : vector) { - [array addObject:MLNJSONObjectFromMBGLValue(value)]; + [array addObject:MLNJSONObjectFromMBGLValue(value)]; } return array; - }, [&](const std::unordered_map &map) { + }, + [&](const std::unordered_map &map) { NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:map.size()]; for (auto &item : map) { - dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second); + dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second); } return dictionary; - }, [](const auto &) -> id { - return nil; - }); + }, + [](const auto &) -> id { return nil; }); } id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) { - return MLNJSONObjectFromMBGLValue(mbglExpression.serialize()); + return MLNJSONObjectFromMBGLValue(mbglExpression.serialize()); } +std::unique_ptr MLNClusterPropertyFromNSExpression( + NSExpression *expression) { + if (!expression) { + return nullptr; + } -std::unique_ptr MLNClusterPropertyFromNSExpression(NSExpression *expression) { - if (!expression) { - return nullptr; - } + NSArray *jsonExpression = expression.mgl_jsonExpressionObject; - NSArray *jsonExpression = expression.mgl_jsonExpressionObject; + auto expr = mbgl::style::expression::dsl::createExpression( + mbgl::style::conversion::makeConvertible(jsonExpression)); - auto expr = mbgl::style::expression::dsl::createExpression(mbgl::style::conversion::makeConvertible(jsonExpression)); - - return expr; + return expr; } diff --git a/platform/darwin/src/MLNStyleValue_Private.h b/platform/darwin/src/MLNStyleValue_Private.h index f95638eee..d9668f3ec 100644 --- a/platform/darwin/src/MLNStyleValue_Private.h +++ b/platform/darwin/src/MLNStyleValue_Private.h @@ -61,7 +61,7 @@ id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mb template class MLNStyleValueTransformer { - public: +public: /// Convert an mbgl property value into an mgl style value NSExpression *toExpression(const mbgl::style::PropertyValue &mbglValue) { PropertyExpressionEvaluator evaluator; @@ -139,7 +139,7 @@ class MLNStyleValueTransformer { return *value; } - private: // Private utilities for converting from mgl to mbgl values +private: // Private utilities for converting from mgl to mbgl values /** As hack to allow converting enum => string values, we accept a second, dummy parameter in the toRawStyleSpecValue() methods for converting 'atomic' (non-style-function) values. @@ -307,7 +307,7 @@ class MLNStyleValueTransformer { } } - private: // Private utilities for converting from mbgl to mgl values +private: // Private utilities for converting from mbgl to mgl values // Bool static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); } @@ -390,7 +390,7 @@ class MLNStyleValueTransformer { /// Converts all types of mbgl property values into an equivalent NSExpression. class PropertyExpressionEvaluator { - public: + public: NSExpression *operator()(const mbgl::style::Undefined) const { return nil; } NSExpression *operator()(const MBGLType &value) const { diff --git a/platform/darwin/src/MLNTilePyramidOfflineRegion.mm b/platform/darwin/src/MLNTilePyramidOfflineRegion.mm index e83d9d595..d21adff36 100644 --- a/platform/darwin/src/MLNTilePyramidOfflineRegion.mm +++ b/platform/darwin/src/MLNTilePyramidOfflineRegion.mm @@ -1,143 +1,159 @@ #import "MLNTilePyramidOfflineRegion.h" #if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR - #import +#import #endif -#import "MLNOfflineRegion_Private.h" -#import "MLNTilePyramidOfflineRegion_Private.h" #import "MLNGeometry_Private.h" -#import "MLNStyle.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNOfflineRegion_Private.h" +#import "MLNStyle.h" +#import "MLNTilePyramidOfflineRegion_Private.h" -@interface MLNTilePyramidOfflineRegion () +@interface MLNTilePyramidOfflineRegion () @end @implementation MLNTilePyramidOfflineRegion { - NSURL *_styleURL; + NSURL *_styleURL; } @synthesize styleURL = _styleURL; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; --(NSDictionary *)offlineStartEventAttributes { - return @{}; +- (NSDictionary *)offlineStartEventAttributes { + return @{}; } + (BOOL)supportsSecureCoding { - return YES; + return YES; } - (instancetype)init { - MLNLogInfo(@"Calling this initializer is not allowed."); - [NSException raise:NSGenericException format: - @"-[MLNTilePyramidOfflineRegion init] is unavailable. " - @"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."]; - return nil; + MLNLogInfo(@"Calling this initializer is not allowed."); + [NSException raise:NSGenericException + format:@"-[MLNTilePyramidOfflineRegion init] is unavailable. " + @"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."]; + return nil; } -- (instancetype)initWithStyleURL:(NSURL *)styleURL bounds:(MLNCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel { - MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel); - if (self = [super init]) { - if (!styleURL) { - styleURL = [MLNStyle defaultStyleURL]; - } - - if (!styleURL.scheme) { - [NSException raise:MLNInvalidStyleURLException format: - @"%@ does not support setting a relative file URL as the style URL. " - @"To download the online resources required by this style, " - @"specify a URL to an online copy of this style. " - @"For Mapbox-hosted styles, use the mapbox: scheme.", - NSStringFromClass([self class])]; - } - - _styleURL = styleURL; - _bounds = bounds; - _minimumZoomLevel = minimumZoomLevel; - _maximumZoomLevel = maximumZoomLevel; - _includesIdeographicGlyphs = NO; +- (instancetype)initWithStyleURL:(NSURL *)styleURL + bounds:(MLNCoordinateBounds)bounds + fromZoomLevel:(double)minimumZoomLevel + toZoomLevel:(double)maximumZoomLevel { + MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, + MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel); + if (self = [super init]) { + if (!styleURL) { + styleURL = [MLNStyle defaultStyleURL]; } - return self; + + if (!styleURL.scheme) { + [NSException raise:MLNInvalidStyleURLException + format:@"%@ does not support setting a relative file URL as the style URL. " + @"To download the online resources required by this style, " + @"specify a URL to an online copy of this style. " + @"For Mapbox-hosted styles, use the mapbox: scheme.", + NSStringFromClass([self class])]; + } + + _styleURL = styleURL; + _bounds = bounds; + _minimumZoomLevel = minimumZoomLevel; + _maximumZoomLevel = maximumZoomLevel; + _includesIdeographicGlyphs = NO; + } + return self; } -- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineTilePyramidRegionDefinition &)definition { - NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; - MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds); - MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; - result.includesIdeographicGlyphs = definition.includeIdeographs; - return result; +- (instancetype)initWithOfflineRegionDefinition: + (const mbgl::OfflineTilePyramidRegionDefinition &)definition { + NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; + MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds); + MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL + bounds:bounds + fromZoomLevel:definition.minZoom + toZoomLevel:definition.maxZoom]; + result.includesIdeographicGlyphs = definition.includeIdeographs; + return result; } - (const mbgl::OfflineRegionDefinition)offlineRegionDefinition { #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale]; + const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] + ? [[UIScreen mainScreen] nativeScale] + : [[UIScreen mainScreen] scale]; #elif TARGET_OS_MAC - const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; + const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; #endif - return mbgl::OfflineTilePyramidRegionDefinition(_styleURL.absoluteString.UTF8String, - MLNLatLngBoundsFromCoordinateBounds(_bounds), - _minimumZoomLevel, _maximumZoomLevel, - scaleFactor, _includesIdeographicGlyphs); + return mbgl::OfflineTilePyramidRegionDefinition( + _styleURL.absoluteString.UTF8String, MLNLatLngBoundsFromCoordinateBounds(_bounds), + _minimumZoomLevel, _maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs); } - (nullable instancetype)initWithCoder:(NSCoder *)coder { - MLNLogInfo(@"Initializing with coder."); - NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; - CLLocationCoordinate2D sw = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"], - [coder decodeDoubleForKey:@"southWestLongitude"]); - CLLocationCoordinate2D ne = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"], - [coder decodeDoubleForKey:@"northEastLongitude"]); - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne); - double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; - double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; + MLNLogInfo(@"Initializing with coder."); + NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; + CLLocationCoordinate2D sw = + CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"], + [coder decodeDoubleForKey:@"southWestLongitude"]); + CLLocationCoordinate2D ne = + CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"], + [coder decodeDoubleForKey:@"northEastLongitude"]); + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne); + double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; + double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; - MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel]; - result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; - return result; + MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL + bounds:bounds + fromZoomLevel:minimumZoomLevel + toZoomLevel:maximumZoomLevel]; + result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; + return result; } -- (void)encodeWithCoder:(NSCoder *)coder -{ - [coder encodeObject:_styleURL forKey:@"styleURL"]; - [coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"]; - [coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"]; - [coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"]; - [coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"]; - [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; - [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; - [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"]; +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:_styleURL forKey:@"styleURL"]; + [coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"]; + [coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"]; + [coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"]; + [coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"]; + [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; + [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; + [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"]; } - (id)copyWithZone:(nullable NSZone *)zone { - MLNTilePyramidOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL bounds:_bounds fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel]; - result.includesIdeographicGlyphs = _includesIdeographicGlyphs; - return result; + MLNTilePyramidOfflineRegion *result = + [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL + bounds:_bounds + fromZoomLevel:_minimumZoomLevel + toZoomLevel:_maximumZoomLevel]; + result.includesIdeographicGlyphs = _includesIdeographicGlyphs; + return result; } - (BOOL)isEqual:(id)other { - if (other == self) { - return YES; - } - if (![other isKindOfClass:[self class]]) { - return NO; - } + if (other == self) { + return YES; + } + if (![other isKindOfClass:[self class]]) { + return NO; + } - MLNTilePyramidOfflineRegion *otherRegion = other; - return (_minimumZoomLevel == otherRegion->_minimumZoomLevel - && _maximumZoomLevel == otherRegion->_maximumZoomLevel - && MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds) - && [_styleURL isEqual:otherRegion->_styleURL] - && _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); + MLNTilePyramidOfflineRegion *otherRegion = other; + return (_minimumZoomLevel == otherRegion->_minimumZoomLevel && + _maximumZoomLevel == otherRegion->_maximumZoomLevel && + MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds) && + [_styleURL isEqual:otherRegion->_styleURL] && + _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); } - (NSUInteger)hash { - return (_styleURL.hash - + @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash - + @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash - + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash - + @(_includesIdeographicGlyphs).hash); + return (_styleURL.hash + @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash + + @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash + @(_minimumZoomLevel).hash + + @(_maximumZoomLevel).hash + @(_includesIdeographicGlyphs).hash); } @end diff --git a/platform/darwin/src/MLNTileSource.mm b/platform/darwin/src/MLNTileSource.mm index 425f83408..98a1ae2bf 100644 --- a/platform/darwin/src/MLNTileSource.mm +++ b/platform/darwin/src/MLNTileSource.mm @@ -8,164 +8,180 @@ #import "NSValue+MLNAdditions.h" #if TARGET_OS_IPHONE - #import +#import #else - #import +#import #endif #include -const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel = @"MLNTileSourceOptionMinimumZoomLevel"; -const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel = @"MLNTileSourceOptionMaximumZoomLevel"; -const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds = @"MLNTileSourceOptionCoordinateBounds"; -const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString = @"MLNTileSourceOptionAttributionHTMLString"; -const MLNTileSourceOption MLNTileSourceOptionAttributionInfos = @"MLNTileSourceOptionAttributionInfos"; -const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = @"MLNTileSourceOptionTileCoordinateSystem"; +const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel = + @"MLNTileSourceOptionMinimumZoomLevel"; +const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel = + @"MLNTileSourceOptionMaximumZoomLevel"; +const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds = + @"MLNTileSourceOptionCoordinateBounds"; +const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString = + @"MLNTileSourceOptionAttributionHTMLString"; +const MLNTileSourceOption MLNTileSourceOptionAttributionInfos = + @"MLNTileSourceOptionAttributionInfos"; +const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = + @"MLNTileSourceOptionTileCoordinateSystem"; @implementation MLNTileSource - (NSURL *)configurationURL { - [NSException raise:MLNAbstractClassException - format:@"MLNTileSource is an abstract class"]; - return nil; + [NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"]; + return nil; } - (NSArray *)attributionInfos { - return [self attributionInfosWithFontSize:0 linkColor:nil]; + return [self attributionInfosWithFontSize:0 linkColor:nil]; } -- (NSArray *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { - return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString - fontSize:fontSize - linkColor:linkColor]; +- (NSArray *)attributionInfosWithFontSize:(CGFloat)fontSize + linkColor:(nullable MLNColor *)linkColor { + return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString + fontSize:fontSize + linkColor:linkColor]; } - (NSString *)attributionHTMLString { - [NSException raise:MLNAbstractClassException - format:@"MLNTileSource is an abstract class"]; - return nil; + [NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"]; + return nil; } @end -mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray *tileURLTemplates, NSDictionary * _Nullable options) { - mbgl::Tileset tileSet; +mbgl::Tileset MLNTileSetFromTileURLTemplates( + NSArray *tileURLTemplates, + NSDictionary *_Nullable options) { + mbgl::Tileset tileSet; - for (NSString *tileURLTemplate in tileURLTemplates) { - tileSet.tiles.push_back(tileURLTemplate.UTF8String); + for (NSString *tileURLTemplate in tileURLTemplates) { + tileSet.tiles.push_back(tileURLTemplate.UTF8String); + } + + // set the minimum / maximum zoom range to the values specified by this class if they + // were set. otherwise, use the core objects default values + if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) { + if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."]; + } + tileSet.zoomRange.min = minimumZoomLevel.integerValue; + } + if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) { + if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."]; + } + tileSet.zoomRange.max = maximumZoomLevel.integerValue; + } + if (tileSet.zoomRange.min > tileSet.zoomRange.max) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionMinimumZoomLevel must be less than " + @"MLNTileSourceOptionMaximumZoomLevel."]; + } + + if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) { + if (![coordinateBounds isKindOfClass:[NSValue class]] && + strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue " + @"containing an MLNCoordinateBounds."]; + } + tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue); + } + + if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) { + if (![attribution isKindOfClass:[NSString class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."]; + } + tileSet.attribution = attribution.UTF8String; + } + + if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) { + if (![attributionInfos isKindOfClass:[NSArray class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionAttributionInfos must be set to a string."]; } - // set the minimum / maximum zoom range to the values specified by this class if they - // were set. otherwise, use the core objects default values - if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) { - if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."]; - } - tileSet.zoomRange.min = minimumZoomLevel.integerValue; - } - if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) { - if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."]; - } - tileSet.zoomRange.max = maximumZoomLevel.integerValue; - } - if (tileSet.zoomRange.min > tileSet.zoomRange.max) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionMinimumZoomLevel must be less than MLNTileSourceOptionMaximumZoomLevel."]; - } - - if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) { - if (![coordinateBounds isKindOfClass:[NSValue class]] - && strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue containing an MLNCoordinateBounds."]; - } - tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue); - } - - if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) { - if (![attribution isKindOfClass:[NSString class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."]; - } - tileSet.attribution = attribution.UTF8String; - } - - if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) { - if (![attributionInfos isKindOfClass:[NSArray class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionAttributionInfos must be set to a string."]; - } - - NSAttributedString *attributedString = [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos]; + NSAttributedString *attributedString = + [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos]; #if TARGET_OS_IPHONE - static NSString * const NSExcludedElementsDocumentAttribute = @"ExcludedElements"; + static NSString *const NSExcludedElementsDocumentAttribute = @"ExcludedElements"; #endif - NSDictionary *documentAttributes = @{ - NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, - NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding), - // The attribution string is meant to be a simple, inline fragment, not a full-fledged, validating document. - NSExcludedElementsDocumentAttribute: @[@"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p"], - }; - NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange documentAttributes:documentAttributes error:NULL]; - NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - tileSet.attribution = html.UTF8String; - } + NSDictionary *documentAttributes = @{ + NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, + NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding), + // The attribution string is meant to be a simple, inline fragment, not a full-fledged, + // validating document. + NSExcludedElementsDocumentAttribute : + @[ @"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p" ], + }; + NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange + documentAttributes:documentAttributes + error:NULL]; + NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + tileSet.attribution = html.UTF8String; + } - if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) { - if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."]; - } - MLNTileCoordinateSystem tileCoordinateSystem; - [tileCoordinateSystemNumber getValue:&tileCoordinateSystem]; - switch (tileCoordinateSystem) { - case MLNTileCoordinateSystemXYZ: - tileSet.scheme = mbgl::Tileset::Scheme::XYZ; - break; - case MLNTileCoordinateSystemTMS: - tileSet.scheme = mbgl::Tileset::Scheme::TMS; - break; - } + if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) { + if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) { + [NSException + raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."]; } - - NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding]; - if (encodingNumber) { - if (![encodingNumber isKindOfClass:[NSValue class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."]; - } - MLNDEMEncoding encoding; - [encodingNumber getValue:&encoding]; - switch (encoding) { - case MLNDEMEncodingMapbox: - tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox; - break; - case MLNDEMEncodingTerrarium: - tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium; - break; - } + MLNTileCoordinateSystem tileCoordinateSystem; + [tileCoordinateSystemNumber getValue:&tileCoordinateSystem]; + switch (tileCoordinateSystem) { + case MLNTileCoordinateSystemXYZ: + tileSet.scheme = mbgl::Tileset::Scheme::XYZ; + break; + case MLNTileCoordinateSystemTMS: + tileSet.scheme = mbgl::Tileset::Scheme::TMS; + break; } + } - encodingNumber = options[MLNVectorTileSourceOptionEncoding]; - if (encodingNumber) { - if (![encodingNumber isKindOfClass:[NSValue class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."]; - } - MLNVectorTileSourceEncoding encoding; - [encodingNumber getValue:&encoding]; - switch (encoding) { - case MLNVectorTileSourceEncodingMapbox: - tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox; - break; - case MLNVectorTileSourceEncodingMLT: - tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT; - break; - } + NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding]; + if (encodingNumber) { + if (![encodingNumber isKindOfClass:[NSValue class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."]; } + MLNDEMEncoding encoding; + [encodingNumber getValue:&encoding]; + switch (encoding) { + case MLNDEMEncodingMapbox: + tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox; + break; + case MLNDEMEncodingTerrarium: + tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium; + break; + } + } - return tileSet; + encodingNumber = options[MLNVectorTileSourceOptionEncoding]; + if (encodingNumber) { + if (![encodingNumber isKindOfClass:[NSValue class]]) { + [NSException + raise:NSInvalidArgumentException + format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."]; + } + MLNVectorTileSourceEncoding encoding; + [encodingNumber getValue:&encoding]; + switch (encoding) { + case MLNVectorTileSourceEncodingMapbox: + tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox; + break; + case MLNVectorTileSourceEncodingMLT: + tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT; + break; + } + } + + return tileSet; } diff --git a/platform/darwin/src/MLNValueEvaluator.h b/platform/darwin/src/MLNValueEvaluator.h index 616c60143..903099df5 100644 --- a/platform/darwin/src/MLNValueEvaluator.h +++ b/platform/darwin/src/MLNValueEvaluator.h @@ -1,12 +1,13 @@ #import +#import #import /** Recursively transforms a C++ type into the corresponding Foundation type. */ class ValueEvaluator { - public: +public: id operator()(const mbgl::NullValue &) const { return [NSNull null]; } id operator()(const bool &value) const { return value ? @YES : @NO; } diff --git a/platform/darwin/src/MLNVectorStyleLayer.mm b/platform/darwin/src/MLNVectorStyleLayer.mm index 6be22736d..edc0bea5e 100644 --- a/platform/darwin/src/MLNVectorStyleLayer.mm +++ b/platform/darwin/src/MLNVectorStyleLayer.mm @@ -4,31 +4,29 @@ @implementation MLNVectorStyleLayer - (void)setPredicate:(NSPredicate *)predicate { - [NSException raise:MLNAbstractClassException - format:@"MLNVectorStyleLayer is an abstract class"]; + [NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"]; } - (NSPredicate *)predicate { - [NSException raise:MLNAbstractClassException - format:@"MLNVectorStyleLayer is an abstract class"]; - return nil; + [NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"]; + return nil; } - (NSString *)description { - if (self.rawLayer) { - return [NSString stringWithFormat: - @"<%@: %p; identifier = %@; sourceIdentifier = %@; " - @"sourceLayerIdentifier = %@; predicate = %@; visible = %@>", - NSStringFromClass([self class]), (void *)self, self.identifier, - self.sourceIdentifier, self.sourceLayerIdentifier, self.predicate, - self.visible ? @"YES" : @"NO"]; - } - else { - return [NSString stringWithFormat: - @"<%@: %p; identifier = %@; sourceIdentifier = ; " - @"sourceLayerIdentifier = ; predicate = ; visible = >", - NSStringFromClass([self class]), (void *)self, self.identifier]; - } + if (self.rawLayer) { + return + [NSString stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; " + @"sourceLayerIdentifier = %@; predicate = %@; visible = %@>", + NSStringFromClass([self class]), (void *)self, self.identifier, + self.sourceIdentifier, self.sourceLayerIdentifier, + self.predicate, self.visible ? @"YES" : @"NO"]; + } else { + return [NSString + stringWithFormat: + @"<%@: %p; identifier = %@; sourceIdentifier = ; " + @"sourceLayerIdentifier = ; predicate = ; visible = >", + NSStringFromClass([self class]), (void *)self, self.identifier]; + } } @end diff --git a/platform/darwin/src/MLNVectorTileSource.mm b/platform/darwin/src/MLNVectorTileSource.mm index d63a4a9fe..c0cbf8363 100644 --- a/platform/darwin/src/MLNVectorTileSource.mm +++ b/platform/darwin/src/MLNVectorTileSource.mm @@ -2,17 +2,17 @@ #import "MLNFeature_Private.h" #import "MLNLoggingConfiguration_Private.h" -#import "MLNSource_Private.h" -#import "MLNTileSource_Private.h" -#import "MLNStyle_Private.h" #import "MLNMapView_Private.h" +#import "MLNSource_Private.h" +#import "MLNStyle_Private.h" +#import "MLNTileSource_Private.h" #import "NSPredicate+MLNPrivateAdditions.h" #import "NSURL+MLNAdditions.h" #include -#include #include +#include const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSourceOptionEncoding"; @@ -24,72 +24,78 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou @implementation MLNVectorTileSource -- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL { - auto source = std::make_unique(identifier.UTF8String, - configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String); - return self = [super initWithPendingSource:std::move(source)]; +- (instancetype)initWithIdentifier:(NSString *)identifier + configurationURL:(NSURL *)configurationURL { + auto source = std::make_unique( + identifier.UTF8String, + configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String); + return self = [super initWithPendingSource:std::move(source)]; } - (instancetype)initWithIdentifier:(NSString *)identifier - configurationURLString:(NSString *)configurationURLString -{ - auto source = std::make_unique( - identifier.UTF8String, - configurationURLString.UTF8String - ); + configurationURLString:(NSString *)configurationURLString { + auto source = std::make_unique(identifier.UTF8String, + configurationURLString.UTF8String); - return self = [super initWithPendingSource:std::move(source)]; + return self = [super initWithPendingSource:std::move(source)]; } -- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray *)tileURLTemplates options:(nullable NSDictionary *)options { - mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); - auto source = std::make_unique(identifier.UTF8String, tileSet); - return self = [super initWithPendingSource:std::move(source)]; +- (instancetype)initWithIdentifier:(NSString *)identifier + tileURLTemplates:(NSArray *)tileURLTemplates + options:(nullable NSDictionary *)options { + mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); + auto source = std::make_unique(identifier.UTF8String, tileSet); + return self = [super initWithPendingSource:std::move(source)]; } - (mbgl::style::VectorSource *)rawSource { - return (mbgl::style::VectorSource *)super.rawSource; + return (mbgl::style::VectorSource *)super.rawSource; } - (NSURL *)configurationURL { - MLNAssertStyleSourceIsValid(); - auto url = self.rawSource->getURL(); - return url ? [NSURL URLWithString:@(url->c_str())] : nil; + MLNAssertStyleSourceIsValid(); + auto url = self.rawSource->getURL(); + return url ? [NSURL URLWithString:@(url->c_str())] : nil; } - (NSString *)attributionHTMLString { - if (!self.rawSource) { - MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); - return nil; - } + if (!self.rawSource) { + MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", + self.identifier); + return nil; + } - auto attribution = self.rawSource->getAttribution(); - return attribution ? @(attribution->c_str()) : nil; + auto attribution = self.rawSource->getAttribution(); + return attribution ? @(attribution->c_str()) : nil; } -- (NSArray> *)featuresInSourceLayersWithIdentifiers:(NSSet *)sourceLayerIdentifiers predicate:(nullable NSPredicate *)predicate { - MLNAssertStyleSourceIsValid(); - std::optional> optionalSourceLayerIDs; - if (sourceLayerIdentifiers) { - __block std::vector layerIDs; - layerIDs.reserve(sourceLayerIdentifiers.count); - [sourceLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); +- (NSArray> *) + featuresInSourceLayersWithIdentifiers:(NSSet *)sourceLayerIdentifiers + predicate:(nullable NSPredicate *)predicate { + MLNAssertStyleSourceIsValid(); + std::optional> optionalSourceLayerIDs; + if (sourceLayerIdentifiers) { + __block std::vector layerIDs; + layerIDs.reserve(sourceLayerIdentifiers.count); + [sourceLayerIdentifiers + enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) { + layerIDs.push_back(identifier.UTF8String); }]; - optionalSourceLayerIDs = layerIDs; - } + optionalSourceLayerIDs = layerIDs; + } - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features; - if ([self.stylable isKindOfClass:[MLNMapView class]]) { - MLNMapView *mapView = (MLNMapView *)self.stylable; - features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { optionalSourceLayerIDs, optionalFilter }); - } - return MLNFeaturesFromMBGLFeatures(features); + std::vector features; + if ([self.stylable isKindOfClass:[MLNMapView class]]) { + MLNMapView *mapView = (MLNMapView *)self.stylable; + features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), + {optionalSourceLayerIDs, optionalFilter}); + } + return MLNFeaturesFromMBGLFeatures(features); } @end @@ -102,67 +108,94 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou https://www.mapbox.com/vector-tiles/mapbox-streets-v8/ */ -static NSArray * const MLNMapboxStreetsLanguages = @[ - @"ar", @"de", @"en", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi", - @"zh-Hans", @"zh-Hant", +static NSArray *const MLNMapboxStreetsLanguages = @[ + @"ar", + @"de", + @"en", + @"es", + @"fr", + @"ja", + @"ko", + @"pt", + @"ru", + @"vi", + @"zh-Hans", + @"zh-Hant", ]; /** Like ``MLNMapboxStreetsLanguages``, but deanglicized for use with `+[NSBundle preferredLocalizationsFromArray:forPreferences:]`. */ -static NSArray * const MLNMapboxStreetsAlternativeLanguages = @[ - @"mul", @"ar", @"de", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi", - @"zh-Hans", @"zh-Hant", +static NSArray *const MLNMapboxStreetsAlternativeLanguages = @[ + @"mul", + @"ar", + @"de", + @"es", + @"fr", + @"ja", + @"ko", + @"pt", + @"ru", + @"vi", + @"zh-Hans", + @"zh-Hant", ]; + (NSSet *)mapboxStreetsLanguages { - static dispatch_once_t onceToken; - static NSSet *mapboxStreetsLanguages; - dispatch_once(&onceToken, ^{ - mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages]; - }); - return mapboxStreetsLanguages; + static dispatch_once_t onceToken; + static NSSet *mapboxStreetsLanguages; + dispatch_once(&onceToken, ^{ + mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages]; + }); + return mapboxStreetsLanguages; } + (NSString *)preferredMapboxStreetsLanguage { - return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]]; + return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]]; } + (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray *)preferencesArray { - BOOL acceptsEnglish = [preferencesArray filteredArrayUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL(NSString * _Nullable language, NSDictionary * _Nullable bindings) { - NSString *languageCode; + BOOL acceptsEnglish = + [preferencesArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL( + NSString *_Nullable language, + NSDictionary + *_Nullable bindings) { + NSString *languageCode; - if (@available(iOS 10.0, macOS 10.12.0, *)) { - languageCode = [NSLocale localeWithLocaleIdentifier:language].languageCode; - } - else { - languageCode = [[NSLocale localeWithLocaleIdentifier:language] objectForKey:NSLocaleLanguageCode]; - } + if (@available(iOS 10.0, macOS 10.12.0, *)) { + languageCode = + [NSLocale localeWithLocaleIdentifier:language].languageCode; + } else { + languageCode = [[NSLocale localeWithLocaleIdentifier:language] + objectForKey:NSLocaleLanguageCode]; + } - return [languageCode isEqualToString:@"en"]; - }]].count; + return [languageCode isEqualToString:@"en"]; + }]] + .count; - NSArray *availableLanguages = acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages; - NSArray *preferredLanguages = [NSBundle preferredLocalizationsFromArray:availableLanguages - forPreferences:preferencesArray]; - NSString *mostSpecificLanguage; - for (NSString *language in preferredLanguages) { - if (language.length > mostSpecificLanguage.length) { - mostSpecificLanguage = language; - } + NSArray *availableLanguages = + acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages; + NSArray *preferredLanguages = + [NSBundle preferredLocalizationsFromArray:availableLanguages forPreferences:preferencesArray]; + NSString *mostSpecificLanguage; + for (NSString *language in preferredLanguages) { + if (language.length > mostSpecificLanguage.length) { + mostSpecificLanguage = language; } - return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage; + } + return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage; } - (BOOL)isMapboxStreets { - NSURL *url = self.configurationURL; - if (![url.scheme isEqualToString:@"mapbox"]) { - return NO; - } - NSArray *identifiers = [url.host componentsSeparatedByString:@","]; - return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] || [identifiers containsObject:@"mapbox.mapbox-streets-v7"]; + NSURL *url = self.configurationURL; + if (![url.scheme isEqualToString:@"mapbox"]) { + return NO; + } + NSArray *identifiers = [url.host componentsSeparatedByString:@","]; + return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] || + [identifiers containsObject:@"mapbox.mapbox-streets-v7"]; } @end diff --git a/platform/darwin/src/NSArray+MLNAdditions.mm b/platform/darwin/src/NSArray+MLNAdditions.mm index 05beb289b..6c6d68db2 100644 --- a/platform/darwin/src/NSArray+MLNAdditions.mm +++ b/platform/darwin/src/NSArray+MLNAdditions.mm @@ -1,66 +1,67 @@ #import "NSArray+MLNAdditions.h" +#import "MLNLoggingConfiguration_Private.h" #import "NSDictionary+MLNAdditions.h" #import "NSExpression+MLNPrivateAdditions.h" -#import "MLNLoggingConfiguration_Private.h" @implementation NSArray (MLNAdditions) - (std::vector)mgl_vector { - std::vector vector; - vector.reserve(self.count); - for (id value in self) { - if ([value isKindOfClass:[NSArray class]]) { - std::vector innerVector = [value mgl_vector]; - vector.push_back(innerVector); - } else if ([value isKindOfClass:[NSDictionary class]]) { - mbgl::PropertyMap propertyMap = [value mgl_propertyMap]; - vector.push_back(propertyMap); - } else { - NSExpression *expression = [NSExpression expressionForConstantValue:value]; - vector.push_back(expression.mgl_constantMBGLValue); - } + std::vector vector; + vector.reserve(self.count); + for (id value in self) { + if ([value isKindOfClass:[NSArray class]]) { + std::vector innerVector = [value mgl_vector]; + vector.push_back(innerVector); + } else if ([value isKindOfClass:[NSDictionary class]]) { + mbgl::PropertyMap propertyMap = [value mgl_propertyMap]; + vector.push_back(propertyMap); + } else { + NSExpression *expression = [NSExpression expressionForConstantValue:value]; + vector.push_back(expression.mgl_constantMBGLValue); } - return vector; + } + return vector; } - (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator { - NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator]; - NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; - BOOL isSubsequentItem = NO; - for (NSAttributedString *component in self) { - MLNAssert([component isKindOfClass:[NSAttributedString class]], @"Items in array must be attributed strings."); - if (isSubsequentItem) { - [attributedString appendAttributedString:attributedSeparator]; - } - isSubsequentItem = YES; - [attributedString appendAttributedString:component]; + NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator]; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; + BOOL isSubsequentItem = NO; + for (NSAttributedString *component in self) { + MLNAssert([component isKindOfClass:[NSAttributedString class]], + @"Items in array must be attributed strings."); + if (isSubsequentItem) { + [attributedString appendAttributedString:attributedSeparator]; } - return attributedString; + isSubsequentItem = YES; + [attributedString appendAttributedString:component]; + } + return attributedString; } + (NSArray *)mgl_coordinatesFromCoordinates:(std::vector)coords { - NSMutableArray *coordinates = [NSMutableArray array]; - for (auto coord : coords) { - [coordinates addObject:@{@"latitude": @(coord.latitude), - @"longitude": @(coord.longitude)}]; - } - return coordinates; + NSMutableArray *coordinates = [NSMutableArray array]; + for (auto coord : coords) { + [coordinates addObject:@{@"latitude" : @(coord.latitude), @"longitude" : @(coord.longitude)}]; + } + return coordinates; } - (std::vector)mgl_coordinates { - NSUInteger numberOfCoordinates = [self count]; - CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D)); + NSUInteger numberOfCoordinates = [self count]; + CLLocationCoordinate2D *coords = + (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D)); - for (NSUInteger i = 0; i < numberOfCoordinates; i++) { - coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue], - [self[i][@"longitude"] doubleValue]); - } + for (NSUInteger i = 0; i < numberOfCoordinates; i++) { + coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue], + [self[i][@"longitude"] doubleValue]); + } - std::vector coordinates = { coords, coords + numberOfCoordinates }; - free(coords); + std::vector coordinates = {coords, coords + numberOfCoordinates}; + free(coords); - return coordinates; + return coordinates; } @end diff --git a/platform/darwin/src/NSBundle+MLNAdditions.mm b/platform/darwin/src/NSBundle+MLNAdditions.mm index 67887d0be..82886e3ed 100644 --- a/platform/darwin/src/NSBundle+MLNAdditions.mm +++ b/platform/darwin/src/NSBundle+MLNAdditions.mm @@ -7,46 +7,48 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException @implementation NSBundle (MLNAdditions) + (instancetype)mgl_frameworkBundle { - NSBundle *bundle = [self bundleForClass:[MLNSettings class]]; + NSBundle *bundle = [self bundleForClass:[MLNSettings class]]; #ifndef MLN_CUSTOM_COMBINED_BUNDLE - if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { - // For static frameworks, the bundle is the containing application - // bundle but the resources are in Mapbox.bundle. - NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"]; - if (bundlePath) { - bundle = [self bundleWithPath:bundlePath]; - } else { - [NSException raise:MLNBundleNotFoundException - format:@"The MapLibre framework bundle could not be found. If using MapLibre iOS as a static framework, make sure that MapLibre bundle is copied into the root of the app bundle."]; - } + if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { + // For static frameworks, the bundle is the containing application + // bundle but the resources are in Mapbox.bundle. + NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"]; + if (bundlePath) { + bundle = [self bundleWithPath:bundlePath]; + } else { + [NSException raise:MLNBundleNotFoundException + format:@"The MapLibre framework bundle could not be found. If using MapLibre " + @"iOS as a static framework, make sure that MapLibre bundle is copied " + @"into the root of the app bundle."]; } + } #endif - return bundle; + return bundle; } + (nullable NSString *)mgl_frameworkBundleIdentifier { - return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"]; + return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"]; } + (nullable NSDictionary *)mgl_frameworkInfoDictionary { - NSBundle *bundle = self.mgl_frameworkBundle; - return bundle.infoDictionary; + NSBundle *bundle = self.mgl_frameworkBundle; + return bundle.infoDictionary; } + (nullable NSString *)mgl_applicationBundleIdentifier { - NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \ (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101404)) - // Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a unit test bundle. - // 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool - if (!bundleIdentifier) { - bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier; - } + // Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a + // unit test bundle. 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool + if (!bundleIdentifier) { + bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier; + } #endif - return bundleIdentifier; + return bundleIdentifier; } @end diff --git a/platform/darwin/src/NSCoder+MLNAdditions.mm b/platform/darwin/src/NSCoder+MLNAdditions.mm index 4df5d1240..93bd82133 100644 --- a/platform/darwin/src/NSCoder+MLNAdditions.mm +++ b/platform/darwin/src/NSCoder+MLNAdditions.mm @@ -5,23 +5,26 @@ @implementation NSCoder (MLNAdditions) -- (void)mgl_encodeLocationCoordinates2D:(std::vector)coordinates forKey:(NSString *)key { - [self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key]; +- (void)mgl_encodeLocationCoordinates2D:(std::vector)coordinates + forKey:(NSString *)key { + [self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key]; } - (std::vector)mgl_decodeLocationCoordinates2DForKey:(NSString *)key { - NSSet *coordinateClasses = [NSSet setWithArray:@[[NSArray class], [NSDictionary class]]]; - NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key]; - return [coordinates mgl_coordinates]; + NSSet *coordinateClasses = [NSSet setWithArray:@[ [NSArray class], [NSDictionary class] ]]; + NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key]; + return [coordinates mgl_coordinates]; } - (void)encodeMLNCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key { - [self encodeObject:@{@"latitude": @(coordinate.latitude), @"longitude": @(coordinate.longitude)} forKey:key]; + [self encodeObject:@{@"latitude" : @(coordinate.latitude), @"longitude" : @(coordinate.longitude)} + forKey:key]; } - (CLLocationCoordinate2D)decodeMLNCoordinateForKey:(NSString *)key { - NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key]; - return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue], [coordinate[@"longitude"] doubleValue]); + NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key]; + return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue], + [coordinate[@"longitude"] doubleValue]); } @end diff --git a/platform/darwin/src/NSComparisonPredicate+MLNAdditions.mm b/platform/darwin/src/NSComparisonPredicate+MLNAdditions.mm index d29969411..f2eeb910a 100644 --- a/platform/darwin/src/NSComparisonPredicate+MLNAdditions.mm +++ b/platform/darwin/src/NSComparisonPredicate+MLNAdditions.mm @@ -2,76 +2,80 @@ #import "MLNStyleValue_Private.h" -#import "NSPredicate+MLNAdditions.h" #import "NSExpression+MLNPrivateAdditions.h" +#import "NSPredicate+MLNAdditions.h" @implementation NSComparisonPredicate (MLNAdditions) - (NSString *)mgl_keyPath { - NSExpression *leftExpression = self.leftExpression; - NSExpression *rightExpression = self.rightExpression; - NSExpressionType leftType = leftExpression.expressionType; - NSExpressionType rightType = rightExpression.expressionType; - if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { - return leftExpression.keyPath; - } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { - return rightExpression.keyPath; - } + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + return leftExpression.keyPath; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + return rightExpression.keyPath; + } - [NSException raise:NSInvalidArgumentException - format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a " + @"constant or vice versa."]; + return nil; } - (mbgl::Value)mgl_constantValue { - NSExpression *leftExpression = self.leftExpression; - NSExpression *rightExpression = self.rightExpression; - NSExpressionType leftType = leftExpression.expressionType; - NSExpressionType rightType = rightExpression.expressionType; - mbgl::Value value; - if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { - value = rightExpression.mgl_constantMBGLValue; - } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { - value = leftExpression.mgl_constantMBGLValue; - } else { - [NSException raise:NSInvalidArgumentException - format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; - } - return value; + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::Value value; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + value = rightExpression.mgl_constantMBGLValue; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + value = leftExpression.mgl_constantMBGLValue; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a " + @"constant or vice versa."]; + } + return value; } - (mbgl::FeatureType)mgl_featureType { - NSExpression *leftExpression = self.leftExpression; - NSExpression *rightExpression = self.rightExpression; - NSExpressionType leftType = leftExpression.expressionType; - NSExpressionType rightType = rightExpression.expressionType; - mbgl::FeatureType type; - if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { - type = rightExpression.mgl_featureType; - } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { - type = leftExpression.mgl_featureType; - } else { - [NSException raise:NSInvalidArgumentException - format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; - } - return type; + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::FeatureType type; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + type = rightExpression.mgl_featureType; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + type = leftExpression.mgl_featureType; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a " + @"constant or vice versa."]; + } + return type; } - (mbgl::FeatureIdentifier)mgl_featureIdentifier { - NSExpression *leftExpression = self.leftExpression; - NSExpression *rightExpression = self.rightExpression; - NSExpressionType leftType = leftExpression.expressionType; - NSExpressionType rightType = rightExpression.expressionType; - mbgl::FeatureIdentifier identifier; - if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { - identifier = rightExpression.mgl_featureIdentifier; - } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { - identifier = leftExpression.mgl_featureIdentifier; - } else { - [NSException raise:NSInvalidArgumentException - format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; - } - return identifier; + NSExpression *leftExpression = self.leftExpression; + NSExpression *rightExpression = self.rightExpression; + NSExpressionType leftType = leftExpression.expressionType; + NSExpressionType rightType = rightExpression.expressionType; + mbgl::FeatureIdentifier identifier; + if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { + identifier = rightExpression.mgl_featureIdentifier; + } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { + identifier = leftExpression.mgl_featureIdentifier; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Comparison predicate must compare an attribute (as a key path) to a " + @"constant or vice versa."]; + } + return identifier; } @end @@ -79,138 +83,158 @@ @implementation NSComparisonPredicate (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - switch (self.comparisonPredicateModifier) { - case NSDirectPredicateModifier: - break; + switch (self.comparisonPredicateModifier) { + case NSDirectPredicateModifier: + break; - case NSAllPredicateModifier: - // “ALL x != y” is logically equivalent to “NOT y IN x”. - if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) { - // https://github.com/mapbox/mapbox-gl-js/issues/9339 - if (self.options) { - [NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."]; - } - - NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression - rightExpression:self.leftExpression - modifier:NSDirectPredicateModifier - type:NSInPredicateOperatorType - options:self.options]; - NSPredicate *invertedPredicate = [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate]; - return invertedPredicate.mgl_jsonExpressionObject; - } else { - [NSException raise:NSInvalidArgumentException format:@"“ALL” is only supported for the “!=” operator."]; - } - - case NSAnyPredicateModifier: - // “ANY x = y” is logically equivalent to “y IN x”. - if (self.predicateOperatorType == NSEqualToPredicateOperatorType) { - // https://github.com/mapbox/mapbox-gl-js/issues/9339 - if (self.options) { - [NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."]; - } - - NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression - rightExpression:self.leftExpression - modifier:NSDirectPredicateModifier - type:NSInPredicateOperatorType - options:self.options]; - return directPredicate.mgl_jsonExpressionObject; - } else { - [NSException raise:NSInvalidArgumentException format:@"“ANY” or “SOME” is only supported for the “=” operator."]; - } - - default: - [NSException raise:NSInvalidArgumentException - format:@"NSComparisonPredicateModifier:%lu is not supported.", (unsigned long)self.comparisonPredicateModifier]; - } - - NSString *op; - switch (self.predicateOperatorType) { - case NSLessThanPredicateOperatorType: - op = @"<"; - break; - case NSLessThanOrEqualToPredicateOperatorType: - op = @"<="; - break; - case NSGreaterThanPredicateOperatorType: - op = @">"; - break; - case NSGreaterThanOrEqualToPredicateOperatorType: - op = @">="; - break; - case NSEqualToPredicateOperatorType: - op = @"=="; - break; - case NSNotEqualToPredicateOperatorType: - op = @"!="; - break; - case NSBetweenPredicateOperatorType: { - op = @"all"; - NSArray *limits = self.rightExpression.constantValue; - NSPredicate *leftHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:limits[0] - rightExpression:self.leftExpression - modifier:NSDirectPredicateModifier - type:NSLessThanOrEqualToPredicateOperatorType - options:0]; - NSPredicate *rightHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression - rightExpression:limits[1] - modifier:NSDirectPredicateModifier - type:NSLessThanOrEqualToPredicateOperatorType - options:0]; - return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject]; + case NSAllPredicateModifier: + // “ALL x != y” is logically equivalent to “NOT y IN x”. + if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) { + // https://github.com/mapbox/mapbox-gl-js/issues/9339 + if (self.options) { + [NSException + raise:NSInvalidArgumentException + format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."]; } - case NSInPredicateOperatorType: { - if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) { - return @[@"within", self.rightExpression.mgl_jsonExpressionObject]; - } - // An “in” expression comparing two string literals is unfortunately - // misinterpreted as a legacy “in” filter due to ambiguity. Wrap one - // argument in a “literal” expression to force an expression. - // https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077 - if (self.leftExpression.expressionType == NSConstantValueExpressionType && - self.rightExpression.expressionType == NSConstantValueExpressionType && - [self.leftExpression.constantValue isKindOfClass:[NSString class]] && - [self.rightExpression.constantValue isKindOfClass:[NSString class]]) { - NSExpression *rightLiteralExpression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression]; - NSPredicate *literalPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression - rightExpression:rightLiteralExpression - modifier:NSDirectPredicateModifier - type:NSInPredicateOperatorType - options:self.options]; - return literalPredicate.mgl_jsonExpressionObject; - } - op = @"in"; - break; + + NSPredicate *directPredicate = + [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression + rightExpression:self.leftExpression + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:self.options]; + NSPredicate *invertedPredicate = + [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate]; + return invertedPredicate.mgl_jsonExpressionObject; + } else { + [NSException raise:NSInvalidArgumentException + format:@"“ALL” is only supported for the “!=” operator."]; + } + + case NSAnyPredicateModifier: + // “ANY x = y” is logically equivalent to “y IN x”. + if (self.predicateOperatorType == NSEqualToPredicateOperatorType) { + // https://github.com/mapbox/mapbox-gl-js/issues/9339 + if (self.options) { + [NSException + raise:NSInvalidArgumentException + format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."]; } - case NSContainsPredicateOperatorType: { - NSPredicate *inPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression - rightExpression:self.leftExpression - modifier:self.comparisonPredicateModifier - type:NSInPredicateOperatorType - options:self.options]; - return inPredicate.mgl_jsonExpressionObject; - } - case NSMatchesPredicateOperatorType: - case NSLikePredicateOperatorType: - case NSBeginsWithPredicateOperatorType: - case NSEndsWithPredicateOperatorType: - case NSCustomSelectorPredicateOperatorType: - [NSException raise:NSInvalidArgumentException - format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType]; + + NSPredicate *directPredicate = + [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression + rightExpression:self.leftExpression + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:self.options]; + return directPredicate.mgl_jsonExpressionObject; + } else { + [NSException raise:NSInvalidArgumentException + format:@"“ANY” or “SOME” is only supported for the “=” operator."]; + } + + default: + [NSException raise:NSInvalidArgumentException + format:@"NSComparisonPredicateModifier:%lu is not supported.", + (unsigned long)self.comparisonPredicateModifier]; + } + + NSString *op; + switch (self.predicateOperatorType) { + case NSLessThanPredicateOperatorType: + op = @"<"; + break; + case NSLessThanOrEqualToPredicateOperatorType: + op = @"<="; + break; + case NSGreaterThanPredicateOperatorType: + op = @">"; + break; + case NSGreaterThanOrEqualToPredicateOperatorType: + op = @">="; + break; + case NSEqualToPredicateOperatorType: + op = @"=="; + break; + case NSNotEqualToPredicateOperatorType: + op = @"!="; + break; + case NSBetweenPredicateOperatorType: { + op = @"all"; + NSArray *limits = self.rightExpression.constantValue; + NSPredicate *leftHandPredicate = [NSComparisonPredicate + predicateWithLeftExpression:limits[0] + rightExpression:self.leftExpression + modifier:NSDirectPredicateModifier + type:NSLessThanOrEqualToPredicateOperatorType + options:0]; + NSPredicate *rightHandPredicate = [NSComparisonPredicate + predicateWithLeftExpression:self.leftExpression + rightExpression:limits[1] + modifier:NSDirectPredicateModifier + type:NSLessThanOrEqualToPredicateOperatorType + options:0]; + return @[ + op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject + ]; } - if (!op) { - return nil; + case NSInPredicateOperatorType: { + if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) { + return @[ @"within", self.rightExpression.mgl_jsonExpressionObject ]; + } + // An “in” expression comparing two string literals is unfortunately + // misinterpreted as a legacy “in” filter due to ambiguity. Wrap one + // argument in a “literal” expression to force an expression. + // https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077 + if (self.leftExpression.expressionType == NSConstantValueExpressionType && + self.rightExpression.expressionType == NSConstantValueExpressionType && + [self.leftExpression.constantValue isKindOfClass:[NSString class]] && + [self.rightExpression.constantValue isKindOfClass:[NSString class]]) { + NSExpression *rightLiteralExpression = [NSExpression + expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression]; + NSPredicate *literalPredicate = + [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression + rightExpression:rightLiteralExpression + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:self.options]; + return literalPredicate.mgl_jsonExpressionObject; + } + op = @"in"; + break; } - NSArray *comparisonArray = @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject]; - if (self.options) { - NSDictionary *collatorObject = @{ - @"case-sensitive": @(!(self.options & NSCaseInsensitivePredicateOption)), - @"diacritic-sensitive": @(!(self.options & NSDiacriticInsensitivePredicateOption)), - }; - return [comparisonArray arrayByAddingObject:@[@"collator", collatorObject]]; + case NSContainsPredicateOperatorType: { + NSPredicate *inPredicate = + [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression + rightExpression:self.leftExpression + modifier:self.comparisonPredicateModifier + type:NSInPredicateOperatorType + options:self.options]; + return inPredicate.mgl_jsonExpressionObject; } - return comparisonArray; + case NSMatchesPredicateOperatorType: + case NSLikePredicateOperatorType: + case NSBeginsWithPredicateOperatorType: + case NSEndsWithPredicateOperatorType: + case NSCustomSelectorPredicateOperatorType: + [NSException raise:NSInvalidArgumentException + format:@"NSPredicateOperatorType:%lu is not supported.", + (unsigned long)self.predicateOperatorType]; + } + if (!op) { + return nil; + } + NSArray *comparisonArray = @[ + op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject + ]; + if (self.options) { + NSDictionary *collatorObject = @{ + @"case-sensitive" : @(!(self.options & NSCaseInsensitivePredicateOption)), + @"diacritic-sensitive" : @(!(self.options & NSDiacriticInsensitivePredicateOption)), + }; + return [comparisonArray arrayByAddingObject:@[ @"collator", collatorObject ]]; + } + return comparisonArray; } @end diff --git a/platform/darwin/src/NSCompoundPredicate+MLNAdditions.mm b/platform/darwin/src/NSCompoundPredicate+MLNAdditions.mm index 8edc23181..24fff9930 100644 --- a/platform/darwin/src/NSCompoundPredicate+MLNAdditions.mm +++ b/platform/darwin/src/NSCompoundPredicate+MLNAdditions.mm @@ -2,21 +2,20 @@ #import "MLNStyleValue_Private.h" -#import "NSPredicate+MLNPrivateAdditions.h" -#import "NSExpression+MLNPrivateAdditions.h" #import "MLNLoggingConfiguration_Private.h" +#import "NSExpression+MLNPrivateAdditions.h" +#import "NSPredicate+MLNPrivateAdditions.h" #include @implementation NSCompoundPredicate (MLNAdditions) -- (std::vector)mgl_subfilters -{ - std::vectorfilters; - for (NSPredicate *predicate in self.subpredicates) { - filters.push_back(predicate.mgl_filter); - } - return filters; +- (std::vector)mgl_subfilters { + std::vector filters; + for (NSPredicate *predicate in self.subpredicates) { + filters.push_back(predicate.mgl_filter); + } + return filters; } @end @@ -24,27 +23,27 @@ @implementation NSCompoundPredicate (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - switch (self.compoundPredicateType) { - case NSNotPredicateType: { - MLNAssert(self.subpredicates.count <= 1, @"NOT predicate cannot have multiple subpredicates."); - NSPredicate *subpredicate = self.subpredicates.firstObject; - return @[@"!", subpredicate.mgl_jsonExpressionObject]; - } - - case NSAndPredicateType: { - NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; - return [@[@"all"] arrayByAddingObjectsFromArray:subarrays]; - } - - case NSOrPredicateType: { - NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; - return [@[@"any"] arrayByAddingObjectsFromArray:subarrays]; - } + switch (self.compoundPredicateType) { + case NSNotPredicateType: { + MLNAssert(self.subpredicates.count <= 1, + @"NOT predicate cannot have multiple subpredicates."); + NSPredicate *subpredicate = self.subpredicates.firstObject; + return @[ @"!", subpredicate.mgl_jsonExpressionObject ]; } - [NSException raise:@"Compound predicate type not handled" - format:@""]; - return nil; + case NSAndPredicateType: { + NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; + return [@[ @"all" ] arrayByAddingObjectsFromArray:subarrays]; + } + + case NSOrPredicateType: { + NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; + return [@[ @"any" ] arrayByAddingObjectsFromArray:subarrays]; + } + } + + [NSException raise:@"Compound predicate type not handled" format:@""]; + return nil; } @end diff --git a/platform/darwin/src/NSDate+MLNAdditions.mm b/platform/darwin/src/NSDate+MLNAdditions.mm index 5dcb36c28..8c78fcc74 100644 --- a/platform/darwin/src/NSDate+MLNAdditions.mm +++ b/platform/darwin/src/NSDate+MLNAdditions.mm @@ -1,12 +1,11 @@ -#import "NSDate+MLNAdditions.h" #import +#import "NSDate+MLNAdditions.h" -mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration) -{ - return std::chrono::duration_cast(std::chrono::duration(duration)); +mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration) { + return std::chrono::duration_cast( + std::chrono::duration(duration)); } -NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration) -{ - return std::chrono::duration>(duration).count(); +NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration) { + return std::chrono::duration>(duration).count(); } diff --git a/platform/darwin/src/NSDictionary+MLNAdditions.mm b/platform/darwin/src/NSDictionary+MLNAdditions.mm index 8a4b841b0..3afc3300f 100644 --- a/platform/darwin/src/NSDictionary+MLNAdditions.mm +++ b/platform/darwin/src/NSDictionary+MLNAdditions.mm @@ -1,24 +1,24 @@ #import "NSDictionary+MLNAdditions.h" -#import "NSExpression+MLNPrivateAdditions.h" #import "NSArray+MLNAdditions.h" +#import "NSExpression+MLNPrivateAdditions.h" @implementation NSDictionary (MLNAdditions) - (mbgl::PropertyMap)mgl_propertyMap { - mbgl::PropertyMap propertyMap; - for (NSString *key in self.allKeys) { - if ([self[key] isKindOfClass:[NSDictionary class]]) { - propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap]; - } else if ([self[key] isKindOfClass:[NSArray class]]) { - NSArray *array = self[key]; - propertyMap[[key UTF8String]] = [array mgl_vector]; - } else { - NSExpression *expression = [NSExpression expressionForConstantValue:self[key]]; - propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue; - } + mbgl::PropertyMap propertyMap; + for (NSString *key in self.allKeys) { + if ([self[key] isKindOfClass:[NSDictionary class]]) { + propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap]; + } else if ([self[key] isKindOfClass:[NSArray class]]) { + NSArray *array = self[key]; + propertyMap[[key UTF8String]] = [array mgl_vector]; + } else { + NSExpression *expression = [NSExpression expressionForConstantValue:self[key]]; + propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue; } - return propertyMap; + } + return propertyMap; } @end diff --git a/platform/darwin/src/NSExpression+MLNAdditions.mm b/platform/darwin/src/NSExpression+MLNAdditions.mm index b5f05924c..20b5e10ef 100644 --- a/platform/darwin/src/NSExpression+MLNAdditions.mm +++ b/platform/darwin/src/NSExpression+MLNAdditions.mm @@ -5,14 +5,14 @@ #import "MLNTypes.h" #if TARGET_OS_IPHONE - #import "UIColor+MLNAdditions.h" +#import "UIColor+MLNAdditions.h" #else - #import "NSColor+MLNAdditions.h" +#import "NSColor+MLNAdditions.h" #endif +#import "MLNAttributedExpression.h" +#import "MLNVectorTileSource_Private.h" #import "NSPredicate+MLNAdditions.h" #import "NSValue+MLNStyleAttributeAdditions.h" -#import "MLNVectorTileSource_Private.h" -#import "MLNAttributedExpression.h" #import @@ -22,73 +22,76 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeLinear = @"li const MLNExpressionInterpolationMode MLNExpressionInterpolationModeExponential = @"exponential"; const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @"cubic-bezier"; -@interface MLNAftermarketExpressionInstaller: NSObject +@interface MLNAftermarketExpressionInstaller : NSObject @end @implementation MLNAftermarketExpressionInstaller + (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [self installFunctions]; - }); + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [self installFunctions]; + }); } /** Adds to NSExpression’s built-in repertoire of functions. */ + (void)installFunctions { - Class MLNAftermarketExpressionInstaller = [self class]; + Class MLNAftermarketExpressionInstaller = [self class]; - // NSExpression’s built-in functions are backed by class methods on a - // private class, so use a function expression to get at the class. - // http://funwithobjc.tumblr.com/post/2922267976/using-custom-functions-with-nsexpression - NSExpression *functionExpression = [NSExpression expressionWithFormat:@"sum({})"]; - NSString *className = NSStringFromClass([functionExpression.operand.constantValue class]); + // NSExpression’s built-in functions are backed by class methods on a + // private class, so use a function expression to get at the class. + // http://funwithobjc.tumblr.com/post/2922267976/using-custom-functions-with-nsexpression + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"sum({})"]; + NSString *className = NSStringFromClass([functionExpression.operand.constantValue class]); - // Effectively categorize the class with some extra class methods. - Class NSPredicateUtilities = objc_getMetaClass(className.UTF8String); + // Effectively categorize the class with some extra class methods. + Class NSPredicateUtilities = objc_getMetaClass(className.UTF8String); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" - #define INSTALL_METHOD(sel) \ - { \ - Method method = class_getInstanceMethod(MLNAftermarketExpressionInstaller, @selector(sel)); \ - class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), method_getTypeEncoding(method)); \ - } - #define INSTALL_CONTROL_STRUCTURE(sel) \ - { \ - Method method = class_getInstanceMethod(MLNAftermarketExpressionInstaller, @selector(sel:)); \ - class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), method_getTypeEncoding(method)); \ - class_addMethod(NSPredicateUtilities, @selector(sel:), method_getImplementation(method), method_getTypeEncoding(method)); \ - } +#define INSTALL_METHOD(sel) \ + { \ + Method method = class_getInstanceMethod(MLNAftermarketExpressionInstaller, @selector(sel)); \ + class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), \ + method_getTypeEncoding(method)); \ + } +#define INSTALL_CONTROL_STRUCTURE(sel) \ + { \ + Method method = class_getInstanceMethod(MLNAftermarketExpressionInstaller, @selector(sel:)); \ + class_addMethod(NSPredicateUtilities, @selector(sel), method_getImplementation(method), \ + method_getTypeEncoding(method)); \ + class_addMethod(NSPredicateUtilities, @selector(sel:), method_getImplementation(method), \ + method_getTypeEncoding(method)); \ + } - // Install method-like functions, taking the number of arguments implied by - // the selector name. - INSTALL_METHOD(mgl_join:); - INSTALL_METHOD(mgl_round:); - INSTALL_METHOD(mgl_interpolate:withCurveType:parameters:stops:); - INSTALL_METHOD(mgl_step:from:stops:); - INSTALL_METHOD(mgl_coalesce:); - INSTALL_METHOD(mgl_does:have:); - INSTALL_METHOD(mgl_acos:); - INSTALL_METHOD(mgl_cos:); - INSTALL_METHOD(mgl_asin:); - INSTALL_METHOD(mgl_sin:); - INSTALL_METHOD(mgl_atan:); - INSTALL_METHOD(mgl_tan:); - INSTALL_METHOD(mgl_log2:); - INSTALL_METHOD(mgl_distanceFrom:); - INSTALL_METHOD(mgl_attributed:); + // Install method-like functions, taking the number of arguments implied by + // the selector name. + INSTALL_METHOD(mgl_join:); + INSTALL_METHOD(mgl_round:); + INSTALL_METHOD(mgl_interpolate:withCurveType:parameters:stops:); + INSTALL_METHOD(mgl_step:from:stops:); + INSTALL_METHOD(mgl_coalesce:); + INSTALL_METHOD(mgl_does:have:); + INSTALL_METHOD(mgl_acos:); + INSTALL_METHOD(mgl_cos:); + INSTALL_METHOD(mgl_asin:); + INSTALL_METHOD(mgl_sin:); + INSTALL_METHOD(mgl_atan:); + INSTALL_METHOD(mgl_tan:); + INSTALL_METHOD(mgl_log2:); + INSTALL_METHOD(mgl_distanceFrom:); + INSTALL_METHOD(mgl_attributed:); - // Install functions that resemble control structures, taking arbitrary - // numbers of arguments. Vararg aftermarket functions need to be declared - // with an explicit and implicit first argument. - INSTALL_CONTROL_STRUCTURE(MLN_LET); - INSTALL_CONTROL_STRUCTURE(MLN_MATCH); - INSTALL_CONTROL_STRUCTURE(MLN_IF); - INSTALL_CONTROL_STRUCTURE(MLN_FUNCTION); + // Install functions that resemble control structures, taking arbitrary + // numbers of arguments. Vararg aftermarket functions need to be declared + // with an explicit and implicit first argument. + INSTALL_CONTROL_STRUCTURE(MLN_LET); + INSTALL_CONTROL_STRUCTURE(MLN_MATCH); + INSTALL_CONTROL_STRUCTURE(MLN_IF); + INSTALL_CONTROL_STRUCTURE(MLN_FUNCTION); - #undef INSTALL_AFTERMARKET_FN +#undef INSTALL_AFTERMARKET_FN #pragma clang diagnostic pop } @@ -97,13 +100,13 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = in order. */ - (NSString *)mgl_join:(NSArray *)components { - return [components componentsJoinedByString:@""]; + return [components componentsJoinedByString:@""]; } - (NSString *)mgl_attributed:(NSArray *)attributedExpressions { - [NSException raise:NSInvalidArgumentException - format:@"Text format expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Text format expressions lack underlying Objective-C implementations."]; + return nil; } /** @@ -111,92 +114,95 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = between two integers, this method rounds it away from zero. */ - (NSNumber *)mgl_round:(NSNumber *)number { - return @(round(number.doubleValue)); + return @(round(number.doubleValue)); } /** Computes the principal value of the inverse cosine. */ - (NSNumber *)mgl_acos:(NSNumber *)number { - return @(acos(number.doubleValue)); + return @(acos(number.doubleValue)); } /** Computes the principal value of the cosine. */ - (NSNumber *)mgl_cos:(NSNumber *)number { - return @(cos(number.doubleValue)); + return @(cos(number.doubleValue)); } /** Computes the principal value of the inverse sine. */ - (NSNumber *)mgl_asin:(NSNumber *)number { - return @(asin(number.doubleValue)); + return @(asin(number.doubleValue)); } /** Computes the principal value of the sine. */ - (NSNumber *)mgl_sin:(NSNumber *)number { - return @(sin(number.doubleValue)); + return @(sin(number.doubleValue)); } /** Computes the principal value of the inverse tangent. */ - (NSNumber *)mgl_atan:(NSNumber *)number { - return @(atan(number.doubleValue)); + return @(atan(number.doubleValue)); } /** Computes the principal value of the tangent. */ - (NSNumber *)mgl_tan:(NSNumber *)number { - return @(tan(number.doubleValue)); + return @(tan(number.doubleValue)); } /** Computes the logarithm base two of the value. */ - (NSNumber *)mgl_log2:(NSNumber *)number { - return @(log2(number.doubleValue)); + return @(log2(number.doubleValue)); } /** Returns a straight-line distance from the given shape to the evaluated feature. */ - (NSNumber *)mgl_distanceFrom:(id)object { - [NSException raise:NSInvalidArgumentException - format:@"Shape distance expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Shape distance expressions lack underlying Objective-C implementations."]; + return nil; } /** A placeholder for a method that evaluates an interpolation expression. */ -- (id)mgl_interpolate:(id)inputExpression withCurveType:(NSString *)curveType parameters:(NSDictionary *)params stops:(NSDictionary *)stops { - [NSException raise:NSInvalidArgumentException - format:@"Interpolation expressions lack underlying Objective-C implementations."]; - return nil; +- (id)mgl_interpolate:(id)inputExpression + withCurveType:(NSString *)curveType + parameters:(NSDictionary *)params + stops:(NSDictionary *)stops { + [NSException raise:NSInvalidArgumentException + format:@"Interpolation expressions lack underlying Objective-C implementations."]; + return nil; } /** A placeholder for a method that evaluates a step expression. */ - (id)mgl_step:(id)inputExpression from:(id)minimumExpression stops:(NSDictionary *)stops { - [NSException raise:NSInvalidArgumentException - format:@"Step expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Step expressions lack underlying Objective-C implementations."]; + return nil; } /** A placeholder for a method that evaluates a coalesce expression. */ - (id)mgl_coalesce:(NSArray *)elements { - [NSException raise:NSInvalidArgumentException - format:@"Coalesce expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Coalesce expressions lack underlying Objective-C implementations."]; + return nil; } /** @@ -204,7 +210,7 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = key. */ - (BOOL)mgl_does:(id)object have:(NSString *)key { - return [object valueForKey:key] != nil; + return [object valueForKey:key] != nil; } /** @@ -212,40 +218,41 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = number of variable names and assigned expressions. */ - (id)MLN_LET:(NSString *)firstVariableName, ... { - [NSException raise:NSInvalidArgumentException - format:@"Assignment expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Assignment expressions lack underlying Objective-C implementations."]; + return nil; } /** A placeholder for a method that evaluates an expression and returns the matching element. */ - (id)MLN_MATCH:(id)firstCondition, ... { - [NSException raise:NSInvalidArgumentException - format:@"Assignment expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Assignment expressions lack underlying Objective-C implementations."]; + return nil; } /** A placeholder for a method that evaluates an expression and returns the matching element. */ - (id)MLN_IF:(id)firstCondition, ... { - va_list argumentList; - va_start(argumentList, firstCondition); + va_list argumentList; + va_start(argumentList, firstCondition); - for (id eachExpression = firstCondition; eachExpression; eachExpression = va_arg(argumentList, id)) { - if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { - id valueExpression = va_arg(argumentList, id); - if ([eachExpression evaluateWithObject:nil]) { - return valueExpression; - } - } else { - return eachExpression; - } + for (id eachExpression = firstCondition; eachExpression; + eachExpression = va_arg(argumentList, id)) { + if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { + id valueExpression = va_arg(argumentList, id); + if ([eachExpression evaluateWithObject:nil]) { + return valueExpression; + } + } else { + return eachExpression; } - va_end(argumentList); + } + va_end(argumentList); - return nil; + return nil; } /** @@ -254,9 +261,10 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = expression language. */ - (id)MLN_FUNCTION:(id)firstArgument, ... { - [NSException raise:NSInvalidArgumentException - format:@"Mapbox GL function expressions lack underlying Objective-C implementations."]; - return nil; + [NSException + raise:NSInvalidArgumentException + format:@"Mapbox GL function expressions lack underlying Objective-C implementations."]; + return nil; } @end @@ -264,147 +272,151 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSExpression (MLNPrivateAdditions) - (std::vector)mgl_aggregateMBGLValue { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_constantMBGLValue); - } - return convertedValues; + if ([self.constantValue isKindOfClass:[NSArray class]] || + [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector convertedValues; + for (id value in self.constantValue) { + NSExpression *expression = value; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + convertedValues.push_back(expression.mgl_constantMBGLValue); } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; + return convertedValues; + } + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; + return {}; } - (mbgl::Value)mgl_constantMBGLValue { - id value = self.constantValue; - if ([value isKindOfClass:NSString.class]) { - return { std::string([(NSString *)value UTF8String]) }; - } else if ([value isKindOfClass:NSNumber.class]) { - NSNumber *number = (NSNumber *)value; - if ((strcmp([number objCType], @encode(char)) == 0) || - (strcmp([number objCType], @encode(BOOL)) == 0)) { - // char: 32-bit boolean - // BOOL: 64-bit boolean - return { (bool)number.boolValue }; - } else if (strcmp([number objCType], @encode(double)) == 0) { - // Double values on all platforms are interpreted precisely. - return { (double)number.doubleValue }; - } else if (strcmp([number objCType], @encode(float)) == 0) { - // Float values when taken as double introduce precision problems, - // so warn the user to avoid them. This would require them to - // explicitly use -[NSNumber numberWithFloat:] arguments anyway. - // We still do this conversion in order to provide a valid value. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"Float value in expression will be converted to a double; some imprecision may result. " - @"Use double values explicitly when specifying constant expression values and " - @"when specifying arguments to predicate and expression format strings. " - @"This will be logged only once."); - }); - return { (double)number.doubleValue }; - } else if ([number compare:@(0)] == NSOrderedDescending || - [number compare:@(0)] == NSOrderedSame) { - // Positive integer or zero; use uint64_t per mbgl::Value definition. - // We use unsigned long long here to avoid any truncation. - return { (uint64_t)number.unsignedLongLongValue }; - } else if ([number compare:@(0)] == NSOrderedAscending) { - // Negative integer; use int64_t per mbgl::Value definition. - // We use long long here to avoid any truncation. - return { (int64_t)number.longLongValue }; - } - } else if ([value isKindOfClass:[MLNColor class]]) { - auto hexString = [(MLNColor *)value mgl_colorForPremultipliedValue].stringify(); - return { hexString }; - } else if (value && value != [NSNull null]) { - [NSException raise:NSInvalidArgumentException - format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value]; + id value = self.constantValue; + if ([value isKindOfClass:NSString.class]) { + return {std::string([(NSString *)value UTF8String])}; + } else if ([value isKindOfClass:NSNumber.class]) { + NSNumber *number = (NSNumber *)value; + if ((strcmp([number objCType], @encode(char)) == 0) || + (strcmp([number objCType], @encode(BOOL)) == 0)) { + // char: 32-bit boolean + // BOOL: 64-bit boolean + return {(bool)number.boolValue}; + } else if (strcmp([number objCType], @encode(double)) == 0) { + // Double values on all platforms are interpreted precisely. + return {(double)number.doubleValue}; + } else if (strcmp([number objCType], @encode(float)) == 0) { + // Float values when taken as double introduce precision problems, + // so warn the user to avoid them. This would require them to + // explicitly use -[NSNumber numberWithFloat:] arguments anyway. + // We still do this conversion in order to provide a valid value. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"Float value in expression will be converted to a double; some imprecision may " + @"result. " + @"Use double values explicitly when specifying constant expression values and " + @"when specifying arguments to predicate and expression format strings. " + @"This will be logged only once."); + }); + return {(double)number.doubleValue}; + } else if ([number compare:@(0)] == NSOrderedDescending || + [number compare:@(0)] == NSOrderedSame) { + // Positive integer or zero; use uint64_t per mbgl::Value definition. + // We use unsigned long long here to avoid any truncation. + return {(uint64_t)number.unsignedLongLongValue}; + } else if ([number compare:@(0)] == NSOrderedAscending) { + // Negative integer; use int64_t per mbgl::Value definition. + // We use long long here to avoid any truncation. + return {(int64_t)number.longLongValue}; } - return {}; + } else if ([value isKindOfClass:[MLNColor class]]) { + auto hexString = [(MLNColor *)value mgl_colorForPremultipliedValue].stringify(); + return {hexString}; + } else if (value && value != [NSNull null]) { + [NSException raise:NSInvalidArgumentException + format:@"Can’t convert %s:%@ to mbgl::Value", [value objCType], value]; + } + return {}; } - (std::vector)mgl_aggregateFeatureType { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_featureType); - } - return convertedValues; + if ([self.constantValue isKindOfClass:[NSArray class]] || + [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector convertedValues; + for (id value in self.constantValue) { + NSExpression *expression = value; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + convertedValues.push_back(expression.mgl_featureType); } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; + return convertedValues; + } + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; + return {}; } - (mbgl::FeatureType)mgl_featureType { - id value = self.constantValue; - if ([value isKindOfClass:NSString.class]) { - if ([value isEqualToString:@"Point"]) { - return mbgl::FeatureType::Point; - } - if ([value isEqualToString:@"LineString"]) { - return mbgl::FeatureType::LineString; - } - if ([value isEqualToString:@"Polygon"]) { - return mbgl::FeatureType::Polygon; - } - } else if ([value isKindOfClass:NSNumber.class]) { - switch ([value integerValue]) { - case 1: - return mbgl::FeatureType::Point; - case 2: - return mbgl::FeatureType::LineString; - case 3: - return mbgl::FeatureType::Polygon; - default: - break; - } + id value = self.constantValue; + if ([value isKindOfClass:NSString.class]) { + if ([value isEqualToString:@"Point"]) { + return mbgl::FeatureType::Point; } - return mbgl::FeatureType::Unknown; + if ([value isEqualToString:@"LineString"]) { + return mbgl::FeatureType::LineString; + } + if ([value isEqualToString:@"Polygon"]) { + return mbgl::FeatureType::Polygon; + } + } else if ([value isKindOfClass:NSNumber.class]) { + switch ([value integerValue]) { + case 1: + return mbgl::FeatureType::Point; + case 2: + return mbgl::FeatureType::LineString; + case 3: + return mbgl::FeatureType::Polygon; + default: + break; + } + } + return mbgl::FeatureType::Unknown; } - (std::vector)mgl_aggregateFeatureIdentifier { - if ([self.constantValue isKindOfClass:[NSArray class]] || [self.constantValue isKindOfClass:[NSSet class]]) { - std::vector convertedValues; - for (id value in self.constantValue) { - NSExpression *expression = value; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - convertedValues.push_back(expression.mgl_featureIdentifier); - } - return convertedValues; + if ([self.constantValue isKindOfClass:[NSArray class]] || + [self.constantValue isKindOfClass:[NSSet class]]) { + std::vector convertedValues; + for (id value in self.constantValue) { + NSExpression *expression = value; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + convertedValues.push_back(expression.mgl_featureIdentifier); } - [NSException raise:NSInvalidArgumentException - format:@"Constant value expression must contain an array or set."]; - return {}; + return convertedValues; + } + [NSException raise:NSInvalidArgumentException + format:@"Constant value expression must contain an array or set."]; + return {}; } - (mbgl::FeatureIdentifier)mgl_featureIdentifier { - mbgl::Value mbglValue = self.mgl_constantMBGLValue; + mbgl::Value mbglValue = self.mgl_constantMBGLValue; - if (mbglValue.is()) { - return mbglValue.get(); - } - if (mbglValue.is()) { - return mbglValue.get(); - } - if (mbglValue.is()) { - return mbglValue.get(); - } - if (mbglValue.is()) { - return mbglValue.get(); - } + if (mbglValue.is()) { + return mbglValue.get(); + } + if (mbglValue.is()) { + return mbglValue.get(); + } + if (mbglValue.is()) { + return mbglValue.get(); + } + if (mbglValue.is()) { + return mbglValue.get(); + } - return {}; + return {}; } @end @@ -412,23 +424,23 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSObject (MLNExpressionAdditions) - (NSNumber *)mgl_number { - return nil; + return nil; } - (NSNumber *)mgl_numberWithFallbackValues:(id)fallbackValue, ... { - if (self.mgl_number) { - return self.mgl_number; - } + if (self.mgl_number) { + return self.mgl_number; + } - va_list fallbackValues; - va_start(fallbackValues, fallbackValue); - for (id value = fallbackValue; value; value = va_arg(fallbackValues, id)) { - if ([value mgl_number]) { - return [value mgl_number]; - } + va_list fallbackValues; + va_start(fallbackValues, fallbackValue); + for (id value = fallbackValue; value; value = va_arg(fallbackValues, id)) { + if ([value mgl_number]) { + return [value mgl_number]; } + } - return nil; + return nil; } @end @@ -436,7 +448,7 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSNull (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - return self; + return self; } @end @@ -444,15 +456,16 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSString (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - return self; + return self; } - (NSNumber *)mgl_number { - if (self.doubleValue || ![[NSDecimalNumber decimalNumberWithString:self] isEqual:[NSDecimalNumber notANumber]]) { - return @(self.doubleValue); - } + if (self.doubleValue || + ![[NSDecimalNumber decimalNumberWithString:self] isEqual:[NSDecimalNumber notANumber]]) { + return @(self.doubleValue); + } - return nil; + return nil; } @end @@ -462,28 +475,28 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = - (id)mgl_interpolateWithCurveType:(NSString *)curveType parameters:(NSArray *)parameters stops:(NSDictionary *)stops { - [NSException raise:NSInvalidArgumentException - format:@"Interpolation expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Interpolation expressions lack underlying Objective-C implementations."]; + return nil; } - (id)mgl_stepWithMinimum:(id)minimum stops:(NSDictionary *)stops { - [NSException raise:NSInvalidArgumentException - format:@"Interpolation expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Interpolation expressions lack underlying Objective-C implementations."]; + return nil; } - (NSNumber *)mgl_number { - return self; + return self; } - (id)mgl_jsonExpressionObject { - if ([self isEqualToNumber:@(M_E)]) { - return @[@"e"]; - } else if ([self isEqualToNumber:@(M_PI)]) { - return @[@"pi"]; - } - return self; + if ([self isEqualToNumber:@(M_E)]) { + return @[ @"e" ]; + } else if ([self isEqualToNumber:@(M_PI)]) { + return @[ @"pi" ]; + } + return self; } @end @@ -491,11 +504,11 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation MLNColor (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - auto color = [self mgl_colorForPremultipliedValue]; - if (color.a == 1) { - return @[@"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255)]; - } - return @[@"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a)]; + auto color = [self mgl_colorForPremultipliedValue]; + if (color.a == 1) { + return @[ @"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255) ]; + } + return @[ @"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a) ]; } @end @@ -503,13 +516,13 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSArray (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - return [self valueForKeyPath:@"mgl_jsonExpressionObject"]; + return [self valueForKeyPath:@"mgl_jsonExpressionObject"]; } - (id)mgl_coalesce { - [NSException raise:NSInvalidArgumentException - format:@"Coalesce expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Coalesce expressions lack underlying Objective-C implementations."]; + return nil; } @end @@ -517,19 +530,18 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSDictionary (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - NSMutableDictionary *expressionObject = [NSMutableDictionary dictionaryWithCapacity:self.count]; - [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { - expressionObject[[key mgl_jsonExpressionObject]] = [obj mgl_jsonExpressionObject]; - }]; + NSMutableDictionary *expressionObject = [NSMutableDictionary dictionaryWithCapacity:self.count]; + [self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) { + expressionObject[[key mgl_jsonExpressionObject]] = [obj mgl_jsonExpressionObject]; + }]; - return expressionObject; + return expressionObject; } - (id)mgl_has:(id)element { - [NSException raise:NSInvalidArgumentException - format:@"Has expressions lack underlying Objective-C implementations."]; - return nil; - + [NSException raise:NSInvalidArgumentException + format:@"Has expressions lack underlying Objective-C implementations."]; + return nil; } @end @@ -537,15 +549,15 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSExpression (MLNExpressionAdditions) - (NSExpression *)mgl_expressionWithContext:(NSDictionary *)context { - [NSException raise:NSInternalInconsistencyException - format:@"Assignment expressions lack underlying Objective-C implementations."]; - return self; + [NSException raise:NSInternalInconsistencyException + format:@"Assignment expressions lack underlying Objective-C implementations."]; + return self; } - (id)mgl_has:(id)element { - [NSException raise:NSInvalidArgumentException - format:@"Has expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Has expressions lack underlying Objective-C implementations."]; + return nil; } @end @@ -553,7 +565,7 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation MLNShape (MLNExpressionAdditions) - (id)mgl_jsonExpressionObject { - return self.geoJSONDictionary; + return self.geoJSONDictionary; } @end @@ -561,956 +573,1061 @@ const MLNExpressionInterpolationMode MLNExpressionInterpolationModeCubicBezier = @implementation NSExpression (MLNAdditions) + (NSExpression *)expressionForFunctionHelper:(NSString *)name arguments:(NSArray *)parameters { - if (parameters.count) { - if (@available(iOS 15.5, macOS 12, *)) { - NSExpression *functionExpression = [NSExpression expressionWithFormat:@"sum({})"]; - return [NSExpression expressionForFunction:functionExpression.operand selectorName:name arguments:parameters]; - } + if (parameters.count) { + if (@available(iOS 15.5, macOS 12, *)) { + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"sum({})"]; + return [NSExpression expressionForFunction:functionExpression.operand + selectorName:name + arguments:parameters]; } - return [NSExpression expressionForFunction:name arguments:parameters]; + } + return [NSExpression expressionForFunction:name arguments:parameters]; } + (NSExpression *)zoomLevelVariableExpression { - return [NSExpression expressionForVariable:@"zoomLevel"]; + return [NSExpression expressionForVariable:@"zoomLevel"]; } + (NSExpression *)heatmapDensityVariableExpression { - return [NSExpression expressionForVariable:@"heatmapDensity"]; + return [NSExpression expressionForVariable:@"heatmapDensity"]; } + (NSExpression *)lineProgressVariableExpression { - return [NSExpression expressionForVariable:@"lineProgress"]; + return [NSExpression expressionForVariable:@"lineProgress"]; } + (NSExpression *)featureAccumulatedVariableExpression { - return [NSExpression expressionForVariable:@"featureAccumulated"]; + return [NSExpression expressionForVariable:@"featureAccumulated"]; } + (NSExpression *)geometryTypeVariableExpression { - return [NSExpression expressionForVariable:@"geometryType"]; + return [NSExpression expressionForVariable:@"geometryType"]; } + (NSExpression *)featureIdentifierVariableExpression { - return [NSExpression expressionForVariable:@"featureIdentifier"]; + return [NSExpression expressionForVariable:@"featureIdentifier"]; } + (NSExpression *)featureAttributesVariableExpression { - return [NSExpression expressionForVariable:@"featureAttributes"]; + return [NSExpression expressionForVariable:@"featureAttributes"]; } + (NSExpression *)featurePropertiesVariableExpression { - return [self featureAttributesVariableExpression]; + return [self featureAttributesVariableExpression]; } -+ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate trueExpression:(nonnull NSExpression *)trueExpression falseExpresssion:(nonnull NSExpression *)falseExpression { - return [NSExpression expressionForConditional:conditionPredicate trueExpression:trueExpression falseExpression:falseExpression]; ++ (instancetype)mgl_expressionForConditional:(nonnull NSPredicate *)conditionPredicate + trueExpression:(nonnull NSExpression *)trueExpression + falseExpresssion:(nonnull NSExpression *)falseExpression { + return [NSExpression expressionForConditional:conditionPredicate + trueExpression:trueExpression + falseExpression:falseExpression]; } -+ (instancetype)mgl_expressionForSteppingExpression:(nonnull NSExpression *)steppingExpression fromExpression:(nonnull NSExpression *)minimumExpression stops:(nonnull NSExpression *)stops { - NSString *selectorName = @"mgl_step:from:stops:"; ++ (instancetype)mgl_expressionForSteppingExpression:(nonnull NSExpression *)steppingExpression + fromExpression:(nonnull NSExpression *)minimumExpression + stops:(nonnull NSExpression *)stops { + NSString *selectorName = @"mgl_step:from:stops:"; - return [NSExpression expressionForFunctionHelper:selectorName arguments:@[steppingExpression, minimumExpression, stops]]; + return + [NSExpression expressionForFunctionHelper:selectorName + arguments:@[ steppingExpression, minimumExpression, stops ]]; } -+ (instancetype)mgl_expressionForInterpolatingExpression:(nonnull NSExpression *)inputExpression withCurveType:(nonnull MLNExpressionInterpolationMode)curveType parameters:(nullable NSExpression *)parameters stops:(nonnull NSExpression *)stops { - NSExpression *sanitizeParams = parameters ? parameters : [NSExpression expressionForConstantValue:nil]; ++ (instancetype)mgl_expressionForInterpolatingExpression:(nonnull NSExpression *)inputExpression + withCurveType: + (nonnull MLNExpressionInterpolationMode)curveType + parameters:(nullable NSExpression *)parameters + stops:(nonnull NSExpression *)stops { + NSExpression *sanitizeParams = + parameters ? parameters : [NSExpression expressionForConstantValue:nil]; - NSString *selectorName = @"mgl_interpolate:withCurveType:parameters:stops:"; - - return [NSExpression expressionForFunctionHelper:selectorName - arguments:@[inputExpression, [NSExpression expressionForConstantValue:curveType], sanitizeParams, stops]]; + NSString *selectorName = @"mgl_interpolate:withCurveType:parameters:stops:"; + return [NSExpression + expressionForFunctionHelper:selectorName + arguments:@[ + inputExpression, [NSExpression expressionForConstantValue:curveType], + sanitizeParams, stops + ]]; } -+ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)inputExpression inDictionary:(nonnull NSDictionary *)matchedExpressions defaultExpression:(nonnull NSExpression *)defaultExpression { - NSMutableArray *optionsArray = [NSMutableArray arrayWithObjects:inputExpression, nil]; ++ (instancetype)mgl_expressionForMatchingExpression:(nonnull NSExpression *)inputExpression + inDictionary: + (nonnull NSDictionary *) + matchedExpressions + defaultExpression:(nonnull NSExpression *)defaultExpression { + NSMutableArray *optionsArray = [NSMutableArray arrayWithObjects:inputExpression, nil]; - NSEnumerator *matchEnumerator = matchedExpressions.keyEnumerator; - while (NSExpression *key = matchEnumerator.nextObject) { - [optionsArray addObject:key]; - [optionsArray addObject:[matchedExpressions objectForKey:key]]; - } + NSEnumerator *matchEnumerator = matchedExpressions.keyEnumerator; + while (NSExpression *key = matchEnumerator.nextObject) { + [optionsArray addObject:key]; + [optionsArray addObject:[matchedExpressions objectForKey:key]]; + } - [optionsArray addObject:defaultExpression]; - return [NSExpression expressionForFunctionHelper:@"MLN_MATCH" - arguments:optionsArray]; + [optionsArray addObject:defaultExpression]; + return [NSExpression expressionForFunctionHelper:@"MLN_MATCH" arguments:optionsArray]; } -+ (instancetype)mgl_expressionForAttributedExpressions:(nonnull NSArray *)attributedExpressions { - return [NSExpression expressionForFunctionHelper:@"mgl_attributed:" arguments:attributedExpressions]; ++ (instancetype)mgl_expressionForAttributedExpressions: + (nonnull NSArray *)attributedExpressions { + return [NSExpression expressionForFunctionHelper:@"mgl_attributed:" + arguments:attributedExpressions]; } - (instancetype)mgl_expressionByAppendingExpression:(nonnull NSExpression *)expression { - NSExpression *subexpression = [NSExpression expressionForAggregate:@[self, expression]]; - return [NSExpression expressionForFunctionHelper:@"mgl_join:" arguments:@[subexpression]]; + NSExpression *subexpression = [NSExpression expressionForAggregate:@[ self, expression ]]; + return [NSExpression expressionForFunctionHelper:@"mgl_join:" arguments:@[ subexpression ]]; } static NSDictionary *MLNFunctionNamesByExpressionOperator; static NSDictionary *MLNExpressionOperatorsByFunctionNames; NSArray *MLNSubexpressionsWithJSONObjects(NSArray *objects) { - NSMutableArray *subexpressions = [NSMutableArray arrayWithCapacity:objects.count]; - for (id object in objects) { - NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; - [subexpressions addObject:expression]; - } - return subexpressions; + NSMutableArray *subexpressions = [NSMutableArray arrayWithCapacity:objects.count]; + for (id object in objects) { + NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; + [subexpressions addObject:expression]; + } + return subexpressions; } + (instancetype)expressionWithMLNJSONObject:(id)object { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - MLNFunctionNamesByExpressionOperator = @{ - @"+": @"add:to:", - @"-": @"from:subtract:", - @"*": @"multiply:by:", - @"/": @"divide:by:", - @"%": @"modulus:by:", - @"sqrt": @"sqrt:", - @"log10": @"log:", - @"ln": @"ln:", - @"abs": @"abs:", - @"round": @"mgl_round:", - @"acos" : @"mgl_acos:", - @"cos" : @"mgl_cos:", - @"asin" : @"mgl_asin:", - @"sin" : @"mgl_sin:", - @"atan" : @"mgl_atan:", - @"tan" : @"mgl_tan:", - @"log2" : @"mgl_log2:", - @"floor": @"floor:", - @"ceil": @"ceiling:", - @"^": @"raise:toPower:", - @"distance": @"mgl_distanceFrom:", - @"upcase": @"uppercase:", - @"downcase": @"lowercase:", - @"let": @"MLN_LET", - }; - }); - if (!object || object == [NSNull null]) { - return [NSExpression expressionForConstantValue:nil]; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + MLNFunctionNamesByExpressionOperator = @{ + @"+" : @"add:to:", + @"-" : @"from:subtract:", + @"*" : @"multiply:by:", + @"/" : @"divide:by:", + @"%" : @"modulus:by:", + @"sqrt" : @"sqrt:", + @"log10" : @"log:", + @"ln" : @"ln:", + @"abs" : @"abs:", + @"round" : @"mgl_round:", + @"acos" : @"mgl_acos:", + @"cos" : @"mgl_cos:", + @"asin" : @"mgl_asin:", + @"sin" : @"mgl_sin:", + @"atan" : @"mgl_atan:", + @"tan" : @"mgl_tan:", + @"log2" : @"mgl_log2:", + @"floor" : @"floor:", + @"ceil" : @"ceiling:", + @"^" : @"raise:toPower:", + @"distance" : @"mgl_distanceFrom:", + @"upcase" : @"uppercase:", + @"downcase" : @"lowercase:", + @"let" : @"MLN_LET", + }; + }); + if (!object || object == [NSNull null]) { + return [NSExpression expressionForConstantValue:nil]; + } + + if ([object isKindOfClass:[NSString class]] || [object isKindOfClass:[NSNumber class]] || + [object isKindOfClass:[NSValue class]] || [object isKindOfClass:[MLNColor class]] || + [object isKindOfClass:[MLNShape class]]) { + return [NSExpression expressionForConstantValue:object]; + } + + if ([object isKindOfClass:[NSDictionary class]]) { + if (object[@"type"]) { + NSError *error; + NSData *shapeData = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; + MLNShape *shape; + if (shapeData && !error) { + shape = [MLNShape shapeWithData:shapeData encoding:NSUTF8StringEncoding error:&error]; + } + if (shape && !error) { + return [NSExpression expressionForConstantValue:shape]; + } } - if ([object isKindOfClass:[NSString class]] || - [object isKindOfClass:[NSNumber class]] || - [object isKindOfClass:[NSValue class]] || - [object isKindOfClass:[MLNColor class]] || - [object isKindOfClass:[MLNShape class]]) { - return [NSExpression expressionForConstantValue:object]; - } - - if ([object isKindOfClass:[NSDictionary class]]) { - if (object[@"type"]) { - NSError *error; - NSData *shapeData = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; - MLNShape *shape; - if (shapeData && !error) { - shape = [MLNShape shapeWithData:shapeData encoding:NSUTF8StringEncoding error:&error]; - } - if (shape && !error) { - return [NSExpression expressionForConstantValue:shape]; - } - } - - NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[object count]]; - [object enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { - dictionary[key] = [NSExpression expressionWithMLNJSONObject:obj]; + NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:[object count]]; + [object + enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) { + dictionary[key] = [NSExpression expressionWithMLNJSONObject:obj]; }]; - return [NSExpression expressionForConstantValue:dictionary]; + return [NSExpression expressionForConstantValue:dictionary]; + } + if ([object isKindOfClass:[NSArray class]]) { + NSArray *array = (NSArray *)object; + NSString *op = array.firstObject; + + if (![op isKindOfClass:[NSString class]]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); + return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; } - if ([object isKindOfClass:[NSArray class]]) { - NSArray *array = (NSArray *)object; - NSString *op = array.firstObject; - if (![op isKindOfClass:[NSString class]]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; - } + NSArray *argumentObjects = [array subarrayWithRange:NSMakeRange(1, array.count - 1)]; - NSArray *argumentObjects = [array subarrayWithRange:NSMakeRange(1, array.count - 1)]; + NSString *functionName = MLNFunctionNamesByExpressionOperator[op]; + if (functionName) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + if ([op isEqualToString:@"+"] && argumentObjects.count > 2) { + NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; + return [NSExpression expressionForFunction:@"sum:" arguments:@[ subexpression ]]; + } else if ([op isEqualToString:@"^"] && [argumentObjects.firstObject isEqual:@[ @"e" ]]) { + functionName = @"exp:"; + subexpressions = + [subexpressions subarrayWithRange:NSMakeRange(1, subexpressions.count - 1)]; + } - NSString *functionName = MLNFunctionNamesByExpressionOperator[op]; - if (functionName) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - if ([op isEqualToString:@"+"] && argumentObjects.count > 2) { - NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; - return [NSExpression expressionForFunction:@"sum:" - arguments:@[subexpression]]; - } else if ([op isEqualToString:@"^"] && [argumentObjects.firstObject isEqual:@[@"e"]]) { - functionName = @"exp:"; - subexpressions = [subexpressions subarrayWithRange:NSMakeRange(1, subexpressions.count - 1)]; - } + return [NSExpression expressionForFunctionHelper:functionName arguments:subexpressions]; + } else if ([op isEqualToString:@"collator"]) { + // Avoid wrapping collator options object in literal expression. + return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:array]; + } else if ([op isEqualToString:@"literal"]) { + if ([argumentObjects.firstObject isKindOfClass:[NSArray class]]) { + return [NSExpression + expressionForAggregate:MLNSubexpressionsWithJSONObjects(argumentObjects.firstObject)]; + } + return [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + } else if ([op isEqualToString:@"to-boolean"]) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + return [NSExpression expressionForFunction:operand selectorName:@"boolValue" arguments:@[]]; + } else if ([op isEqualToString:@"to-number"] || [op isEqualToString:@"number"]) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + if (argumentObjects.count == 1) { + return [NSExpression expressionWithFormat:@"CAST(%@, 'NSNumber')", operand]; + } + argumentObjects = + [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + return [NSExpression expressionForFunction:operand + selectorName:@"mgl_numberWithFallbackValues:" + arguments:subexpressions]; + } else if ([op isEqualToString:@"to-string"] || [op isEqualToString:@"string"]) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + return [NSExpression expressionWithFormat:@"CAST(%@, 'NSString')", operand]; + } else if ([op isEqualToString:@"to-color"]) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionForFunctionHelper:functionName - arguments:subexpressions]; - } else if ([op isEqualToString:@"collator"]) { - // Avoid wrapping collator options object in literal expression. - return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:array]; - } else if ([op isEqualToString:@"literal"]) { - if ([argumentObjects.firstObject isKindOfClass:[NSArray class]]) { - return [NSExpression expressionForAggregate:MLNSubexpressionsWithJSONObjects(argumentObjects.firstObject)]; - } - return [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"to-boolean"]) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionForFunction:operand selectorName:@"boolValue" arguments:@[]]; - } else if ([op isEqualToString:@"to-number"] || [op isEqualToString:@"number"]) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - if (argumentObjects.count == 1) { - return [NSExpression expressionWithFormat:@"CAST(%@, 'NSNumber')", operand]; - } - argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression expressionForFunction:operand selectorName:@"mgl_numberWithFallbackValues:" arguments:subexpressions]; - } else if ([op isEqualToString:@"to-string"] || [op isEqualToString:@"string"]) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"CAST(%@, 'NSString')", operand]; - } else if ([op isEqualToString:@"to-color"]) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - - if (argumentObjects.count == 1) { + if (argumentObjects.count == 1) { #if TARGET_OS_IPHONE - return [NSExpression expressionWithFormat:@"CAST(%@, 'UIColor')", operand]; + return [NSExpression expressionWithFormat:@"CAST(%@, 'UIColor')", operand]; #else - return [NSExpression expressionWithFormat:@"CAST(%@, 'NSColor')", operand]; + return [NSExpression expressionWithFormat:@"CAST(%@, 'NSColor')", operand]; #endif - } - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; + } + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); + return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; - } else if ([op isEqualToString:@"to-rgba"]) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"CAST(noindex(%@), 'NSArray')", operand]; - } else if ([op isEqualToString:@"get"]) { - if (argumentObjects.count == 2) { - NSExpression *operand = [NSExpression expressionWithMLNJSONObject:argumentObjects.lastObject]; - if ([argumentObjects.firstObject isKindOfClass:[NSString class]]) { - return [NSExpression expressionWithFormat:@"%@.%K", operand, argumentObjects.firstObject]; - } - NSExpression *key = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - return [NSExpression expressionWithFormat:@"%@.%@", operand, key]; - } - return [NSExpression expressionForKeyPath:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"length"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSString *function = @"count:"; - if ([subexpressions.firstObject expressionType] == NSConstantValueExpressionType - && [[subexpressions.firstObject constantValue] isKindOfClass:[NSString class]]) { - function = @"length:"; - } - return [NSExpression expressionForFunction:function arguments:@[subexpressions.firstObject]]; - } else if ([op isEqualToString:@"rgb"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression mgl_expressionForRGBComponents:subexpressions]; - } else if ([op isEqualToString:@"rgba"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - return [NSExpression mgl_expressionForRGBAComponents:subexpressions]; - } else if ([op isEqualToString:@"min"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; - return [NSExpression expressionForFunction:@"min:" arguments:@[subexpression]]; - } else if ([op isEqualToString:@"max"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; - return [NSExpression expressionForFunction:@"max:" arguments:@[subexpression]]; - } else if ([op isEqualToString:@"e"]) { - return [NSExpression expressionForConstantValue:@(M_E)]; - } else if ([op isEqualToString:@"pi"]) { - return [NSExpression expressionForConstantValue:@(M_PI)]; - } else if ([op isEqualToString:@"concat"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; - return [NSExpression expressionForFunctionHelper:@"mgl_join:" arguments:@[subexpression]]; - } else if ([op isEqualToString:@"at"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *index = subexpressions.firstObject; - NSExpression *operand = subexpressions[1]; - return [NSExpression expressionForFunction:@"objectFrom:withIndex:" arguments:@[operand, index]]; - } else if ([op isEqualToString:@"has"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); - NSExpression *operand = argumentObjects.count > 1 ? subexpressions[1] : [NSExpression expressionForEvaluatedObject]; - NSExpression *key = subexpressions.firstObject; - return [NSExpression expressionForFunctionHelper:@"mgl_does:have:" arguments:@[operand, key]]; - } else if ([op isEqualToString:@"interpolate"]) { - NSArray *interpolationOptions = argumentObjects.firstObject; - NSString *curveType = interpolationOptions.firstObject; - MLNExpressionInterpolationMode interpolationMode = MLNExpressionInterpolationModeLinear; - id curveParameters; - if ([curveType isEqual:@"exponential"]) { - curveParameters = interpolationOptions[1]; - interpolationMode = MLNExpressionInterpolationModeExponential; - } else if ([curveType isEqualToString:@"cubic-bezier"]) { - curveParameters = @[@"literal", [interpolationOptions subarrayWithRange:NSMakeRange(1, 4)]]; - interpolationMode = MLNExpressionInterpolationModeCubicBezier; - } - else { - curveParameters = [NSNull null]; - } - NSExpression *curveParameterExpression = [NSExpression expressionWithMLNJSONObject:curveParameters]; - argumentObjects = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSExpression *inputExpression = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; - NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSMutableDictionary *stops = [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2]; - NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator; - while (NSNumber *key = stopEnumerator.nextObject) { - NSExpression *valueExpression = stopEnumerator.nextObject; - stops[key] = [NSExpression expressionWithMLNJSONObject:valueExpression]; - } - NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression mgl_expressionForInterpolatingExpression:inputExpression - withCurveType:interpolationMode - parameters:curveParameterExpression - stops:stopExpression]; - } else if ([op isEqualToString:@"step"]) { - NSExpression *inputExpression = [NSExpression expressionWithMLNJSONObject:argumentObjects[0]]; - NSArray *stopExpressions = [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; - NSExpression *minimum; - if (stopExpressions.count % 2) { - minimum = [NSExpression expressionWithMLNJSONObject:stopExpressions.firstObject]; - stopExpressions = [stopExpressions subarrayWithRange:NSMakeRange(1, stopExpressions.count - 1)]; - } - NSMutableDictionary *stops = [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2]; - NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator; - while (NSNumber *key = stopEnumerator.nextObject) { - NSExpression *valueExpression = stopEnumerator.nextObject; - if (minimum) { - stops[key] = [NSExpression expressionWithMLNJSONObject:valueExpression]; - } else { - minimum = [NSExpression expressionWithMLNJSONObject:valueExpression]; - } - } - - NSAssert(minimum, @"minimum should be non-nil"); - if (minimum) { - NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; - return [NSExpression mgl_expressionForSteppingExpression:inputExpression - fromExpression:minimum - stops:stopExpression]; - } - - } else if ([op isEqualToString:@"zoom"]) { - return NSExpression.zoomLevelVariableExpression; - } else if ([op isEqualToString:@"heatmap-density"]) { - return NSExpression.heatmapDensityVariableExpression; - } else if ([op isEqualToString:@"line-progress"]) { - return NSExpression.lineProgressVariableExpression; - } else if ([op isEqualToString:@"accumulated"]) { - return NSExpression.featureAccumulatedVariableExpression; - } else if ([op isEqualToString:@"geometry-type"]) { - return NSExpression.geometryTypeVariableExpression; - } else if ([op isEqualToString:@"id"]) { - return NSExpression.featureIdentifierVariableExpression; - } else if ([op isEqualToString:@"properties"]) { - return NSExpression.featureAttributesVariableExpression; - } else if ([op isEqualToString:@"var"]) { - return [NSExpression expressionForVariable:argumentObjects.firstObject]; - } else if ([op isEqualToString:@"case"]) { - NSMutableArray *arguments = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index++) { - if (index % 2 == 0 && index != argumentObjects.count - 1) { - NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:argumentObjects[index]]; - NSExpression *argument = [NSExpression expressionForConstantValue:predicate]; - [arguments addObject:argument]; - } else { - [arguments addObject:[NSExpression expressionWithMLNJSONObject:argumentObjects[index]]]; - } - } - - if (arguments.count == 3) { - NSPredicate *conditional = [arguments.firstObject constantValue]; - return [NSExpression expressionForConditional:conditional trueExpression:arguments[1] falseExpression:arguments[2]]; - } - return [NSExpression expressionForFunctionHelper:@"MLN_IF" arguments:arguments]; - } else if ([op isEqualToString:@"match"]) { - NSMutableArray *optionsArray = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index++) { - NSExpression *option = [NSExpression expressionWithMLNJSONObject:argumentObjects[index]]; - // match operators with arrays as matching values should not parse arrays as generic functions. - if (index > 0 && index < argumentObjects.count - 1 && !(index % 2 == 0) && [argumentObjects[index] isKindOfClass:[NSArray class]]) { - option = [NSExpression expressionForAggregate:MLNSubexpressionsWithJSONObjects(argumentObjects[index])]; - } - [optionsArray addObject:option]; - } - - return [NSExpression expressionForFunctionHelper:@"MLN_MATCH" - arguments:optionsArray]; - } else if ([op isEqualToString:@"format"]) { - NSMutableArray *attributedExpressions = [NSMutableArray array]; - - for (NSUInteger index = 0; index < argumentObjects.count; index+=2) { - NSExpression *expression = [NSExpression expressionWithMLNJSONObject:argumentObjects[index]]; - NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; - if ((index + 1) < argumentObjects.count) { - attrs = [NSMutableDictionary dictionaryWithDictionary:argumentObjects[index + 1]]; - } - - for (NSString *key in attrs.allKeys) { - attrs[key] = [NSExpression expressionWithMLNJSONObject:attrs[key]]; - } - MLNAttributedExpression *attributedExpression = [[MLNAttributedExpression alloc] initWithExpression:expression attributes:attrs]; - - [attributedExpressions addObject:[NSExpression expressionForConstantValue:attributedExpression]]; - } - return [NSExpression expressionForFunctionHelper:@"mgl_attributed:" arguments:attributedExpressions]; - - } else if ([op isEqualToString:@"coalesce"]) { - NSMutableArray *expressions = [NSMutableArray array]; - for (id operand in argumentObjects) { - [expressions addObject:[NSExpression expressionWithMLNJSONObject:operand]]; - } - - return [NSExpression expressionForFunctionHelper:@"mgl_coalesce:" arguments:@[[NSExpression expressionForConstantValue:expressions]]]; - } else { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); - return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; + } else if ([op isEqualToString:@"to-rgba"]) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + return [NSExpression expressionWithFormat:@"CAST(noindex(%@), 'NSArray')", operand]; + } else if ([op isEqualToString:@"get"]) { + if (argumentObjects.count == 2) { + NSExpression *operand = + [NSExpression expressionWithMLNJSONObject:argumentObjects.lastObject]; + if ([argumentObjects.firstObject isKindOfClass:[NSString class]]) { + return [NSExpression expressionWithFormat:@"%@.%K", operand, argumentObjects.firstObject]; } + NSExpression *key = [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + return [NSExpression expressionWithFormat:@"%@.%@", operand, key]; + } + return [NSExpression expressionForKeyPath:argumentObjects.firstObject]; + } else if ([op isEqualToString:@"length"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSString *function = @"count:"; + if ([subexpressions.firstObject expressionType] == NSConstantValueExpressionType && + [[subexpressions.firstObject constantValue] isKindOfClass:[NSString class]]) { + function = @"length:"; + } + return [NSExpression expressionForFunction:function + arguments:@[ subexpressions.firstObject ]]; + } else if ([op isEqualToString:@"rgb"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + return [NSExpression mgl_expressionForRGBComponents:subexpressions]; + } else if ([op isEqualToString:@"rgba"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + return [NSExpression mgl_expressionForRGBAComponents:subexpressions]; + } else if ([op isEqualToString:@"min"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; + return [NSExpression expressionForFunction:@"min:" arguments:@[ subexpression ]]; + } else if ([op isEqualToString:@"max"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; + return [NSExpression expressionForFunction:@"max:" arguments:@[ subexpression ]]; + } else if ([op isEqualToString:@"e"]) { + return [NSExpression expressionForConstantValue:@(M_E)]; + } else if ([op isEqualToString:@"pi"]) { + return [NSExpression expressionForConstantValue:@(M_PI)]; + } else if ([op isEqualToString:@"concat"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSExpression *subexpression = [NSExpression expressionForAggregate:subexpressions]; + return [NSExpression expressionForFunctionHelper:@"mgl_join:" arguments:@[ subexpression ]]; + } else if ([op isEqualToString:@"at"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSExpression *index = subexpressions.firstObject; + NSExpression *operand = subexpressions[1]; + return [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ operand, index ]]; + } else if ([op isEqualToString:@"has"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(argumentObjects); + NSExpression *operand = argumentObjects.count > 1 + ? subexpressions[1] + : [NSExpression expressionForEvaluatedObject]; + NSExpression *key = subexpressions.firstObject; + return [NSExpression expressionForFunctionHelper:@"mgl_does:have:" + arguments:@[ operand, key ]]; + } else if ([op isEqualToString:@"interpolate"]) { + NSArray *interpolationOptions = argumentObjects.firstObject; + NSString *curveType = interpolationOptions.firstObject; + MLNExpressionInterpolationMode interpolationMode = MLNExpressionInterpolationModeLinear; + id curveParameters; + if ([curveType isEqual:@"exponential"]) { + curveParameters = interpolationOptions[1]; + interpolationMode = MLNExpressionInterpolationModeExponential; + } else if ([curveType isEqualToString:@"cubic-bezier"]) { + curveParameters = + @[ @"literal", [interpolationOptions subarrayWithRange:NSMakeRange(1, 4)] ]; + interpolationMode = MLNExpressionInterpolationModeCubicBezier; + } else { + curveParameters = [NSNull null]; + } + NSExpression *curveParameterExpression = + [NSExpression expressionWithMLNJSONObject:curveParameters]; + argumentObjects = + [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; + NSExpression *inputExpression = + [NSExpression expressionWithMLNJSONObject:argumentObjects.firstObject]; + NSArray *stopExpressions = + [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; + NSMutableDictionary *stops = + [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2]; + NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator; + while (NSNumber *key = stopEnumerator.nextObject) { + NSExpression *valueExpression = stopEnumerator.nextObject; + stops[key] = [NSExpression expressionWithMLNJSONObject:valueExpression]; + } + NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; + return [NSExpression mgl_expressionForInterpolatingExpression:inputExpression + withCurveType:interpolationMode + parameters:curveParameterExpression + stops:stopExpression]; + } else if ([op isEqualToString:@"step"]) { + NSExpression *inputExpression = [NSExpression expressionWithMLNJSONObject:argumentObjects[0]]; + NSArray *stopExpressions = + [argumentObjects subarrayWithRange:NSMakeRange(1, argumentObjects.count - 1)]; + NSExpression *minimum; + if (stopExpressions.count % 2) { + minimum = [NSExpression expressionWithMLNJSONObject:stopExpressions.firstObject]; + stopExpressions = + [stopExpressions subarrayWithRange:NSMakeRange(1, stopExpressions.count - 1)]; + } + NSMutableDictionary *stops = + [NSMutableDictionary dictionaryWithCapacity:stopExpressions.count / 2]; + NSEnumerator *stopEnumerator = stopExpressions.objectEnumerator; + while (NSNumber *key = stopEnumerator.nextObject) { + NSExpression *valueExpression = stopEnumerator.nextObject; + if (minimum) { + stops[key] = [NSExpression expressionWithMLNJSONObject:valueExpression]; + } else { + minimum = [NSExpression expressionWithMLNJSONObject:valueExpression]; + } + } + + NSAssert(minimum, @"minimum should be non-nil"); + if (minimum) { + NSExpression *stopExpression = [NSExpression expressionForConstantValue:stops]; + return [NSExpression mgl_expressionForSteppingExpression:inputExpression + fromExpression:minimum + stops:stopExpression]; + } + + } else if ([op isEqualToString:@"zoom"]) { + return NSExpression.zoomLevelVariableExpression; + } else if ([op isEqualToString:@"heatmap-density"]) { + return NSExpression.heatmapDensityVariableExpression; + } else if ([op isEqualToString:@"line-progress"]) { + return NSExpression.lineProgressVariableExpression; + } else if ([op isEqualToString:@"accumulated"]) { + return NSExpression.featureAccumulatedVariableExpression; + } else if ([op isEqualToString:@"geometry-type"]) { + return NSExpression.geometryTypeVariableExpression; + } else if ([op isEqualToString:@"id"]) { + return NSExpression.featureIdentifierVariableExpression; + } else if ([op isEqualToString:@"properties"]) { + return NSExpression.featureAttributesVariableExpression; + } else if ([op isEqualToString:@"var"]) { + return [NSExpression expressionForVariable:argumentObjects.firstObject]; + } else if ([op isEqualToString:@"case"]) { + NSMutableArray *arguments = [NSMutableArray array]; + + for (NSUInteger index = 0; index < argumentObjects.count; index++) { + if (index % 2 == 0 && index != argumentObjects.count - 1) { + NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:argumentObjects[index]]; + NSExpression *argument = [NSExpression expressionForConstantValue:predicate]; + [arguments addObject:argument]; + } else { + [arguments addObject:[NSExpression expressionWithMLNJSONObject:argumentObjects[index]]]; + } + } + + if (arguments.count == 3) { + NSPredicate *conditional = [arguments.firstObject constantValue]; + return [NSExpression expressionForConditional:conditional + trueExpression:arguments[1] + falseExpression:arguments[2]]; + } + return [NSExpression expressionForFunctionHelper:@"MLN_IF" arguments:arguments]; + } else if ([op isEqualToString:@"match"]) { + NSMutableArray *optionsArray = [NSMutableArray array]; + + for (NSUInteger index = 0; index < argumentObjects.count; index++) { + NSExpression *option = [NSExpression expressionWithMLNJSONObject:argumentObjects[index]]; + // match operators with arrays as matching values should not parse arrays as generic + // functions. + if (index > 0 && index < argumentObjects.count - 1 && !(index % 2 == 0) && + [argumentObjects[index] isKindOfClass:[NSArray class]]) { + option = [NSExpression + expressionForAggregate:MLNSubexpressionsWithJSONObjects(argumentObjects[index])]; + } + [optionsArray addObject:option]; + } + + return [NSExpression expressionForFunctionHelper:@"MLN_MATCH" arguments:optionsArray]; + } else if ([op isEqualToString:@"format"]) { + NSMutableArray *attributedExpressions = [NSMutableArray array]; + + for (NSUInteger index = 0; index < argumentObjects.count; index += 2) { + NSExpression *expression = + [NSExpression expressionWithMLNJSONObject:argumentObjects[index]]; + NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; + if ((index + 1) < argumentObjects.count) { + attrs = [NSMutableDictionary dictionaryWithDictionary:argumentObjects[index + 1]]; + } + + for (NSString *key in attrs.allKeys) { + attrs[key] = [NSExpression expressionWithMLNJSONObject:attrs[key]]; + } + MLNAttributedExpression *attributedExpression = + [[MLNAttributedExpression alloc] initWithExpression:expression attributes:attrs]; + + [attributedExpressions + addObject:[NSExpression expressionForConstantValue:attributedExpression]]; + } + return [NSExpression expressionForFunctionHelper:@"mgl_attributed:" + arguments:attributedExpressions]; + + } else if ([op isEqualToString:@"coalesce"]) { + NSMutableArray *expressions = [NSMutableArray array]; + for (id operand in argumentObjects) { + [expressions addObject:[NSExpression expressionWithMLNJSONObject:operand]]; + } + + return [NSExpression + expressionForFunctionHelper:@"mgl_coalesce:" + arguments:@[ [NSExpression expressionForConstantValue:expressions] ]]; + } else { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(array); + return [NSExpression expressionForFunctionHelper:@"MLN_FUNCTION" arguments:subexpressions]; } + } - [NSException raise:NSInvalidArgumentException - format:@"Unable to convert JSON object %@ to an NSExpression.", object]; + [NSException raise:NSInvalidArgumentException + format:@"Unable to convert JSON object %@ to an NSExpression.", object]; - return nil; + return nil; } - (id)mgl_jsonExpressionObject { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - MLNExpressionOperatorsByFunctionNames = @{ - @"add:to:": @"+", - @"from:subtract:": @"-", - @"multiply:by:": @"*", - @"divide:by:": @"/", - @"modulus:by:": @"%", - @"sqrt:": @"sqrt", - @"log:": @"log10", - @"ln:": @"ln", - @"raise:toPower:": @"^", - @"ceiling:": @"ceil", - @"abs:": @"abs", - @"floor:": @"floor", - @"uppercase:": @"upcase", - @"lowercase:": @"downcase", - @"length:": @"length", - @"mgl_round:": @"round", - @"mgl_acos:" : @"acos", - @"mgl_cos:" : @"cos", - @"mgl_asin:" : @"asin", - @"mgl_sin:" : @"sin", - @"mgl_atan:" : @"atan", - @"mgl_tan:" : @"tan", - @"mgl_log2:" : @"log2", - @"mgl_distanceFrom:": @"distance", - // Vararg aftermarket expressions need to be declared with an explicit and implicit first argument. - @"MLN_LET": @"let", - @"MLN_LET:": @"let", - }; - }); + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + MLNExpressionOperatorsByFunctionNames = @{ + @"add:to:" : @"+", + @"from:subtract:" : @"-", + @"multiply:by:" : @"*", + @"divide:by:" : @"/", + @"modulus:by:" : @"%", + @"sqrt:" : @"sqrt", + @"log:" : @"log10", + @"ln:" : @"ln", + @"raise:toPower:" : @"^", + @"ceiling:" : @"ceil", + @"abs:" : @"abs", + @"floor:" : @"floor", + @"uppercase:" : @"upcase", + @"lowercase:" : @"downcase", + @"length:" : @"length", + @"mgl_round:" : @"round", + @"mgl_acos:" : @"acos", + @"mgl_cos:" : @"cos", + @"mgl_asin:" : @"asin", + @"mgl_sin:" : @"sin", + @"mgl_atan:" : @"atan", + @"mgl_tan:" : @"tan", + @"mgl_log2:" : @"log2", + @"mgl_distanceFrom:" : @"distance", + // Vararg aftermarket expressions need to be declared with an explicit and implicit first + // argument. + @"MLN_LET" : @"let", + @"MLN_LET:" : @"let", + }; + }); - switch (self.expressionType) { - case NSVariableExpressionType: { - if ([self.variable isEqualToString:@"heatmapDensity"]) { - return @[@"heatmap-density"]; - } - if ([self.variable isEqualToString:@"lineProgress"]) { - return @[@"line-progress"]; - } - if ([self.variable isEqualToString:@"zoomLevel"]) { - return @[@"zoom"]; - } - if ([self.variable isEqualToString:@"featureAccumulated"]) { - return @[@"accumulated"]; - } - if ([self.variable isEqualToString:@"geometryType"]) { - return @[@"geometry-type"]; - } - if ([self.variable isEqualToString:@"featureIdentifier"]) { - return @[@"id"]; - } - if ([self.variable isEqualToString:@"featureAttributes"]) { - return @[@"properties"]; - } - return @[@"var", self.variable]; + switch (self.expressionType) { + case NSVariableExpressionType: { + if ([self.variable isEqualToString:@"heatmapDensity"]) { + return @[ @"heatmap-density" ]; + } + if ([self.variable isEqualToString:@"lineProgress"]) { + return @[ @"line-progress" ]; + } + if ([self.variable isEqualToString:@"zoomLevel"]) { + return @[ @"zoom" ]; + } + if ([self.variable isEqualToString:@"featureAccumulated"]) { + return @[ @"accumulated" ]; + } + if ([self.variable isEqualToString:@"geometryType"]) { + return @[ @"geometry-type" ]; + } + if ([self.variable isEqualToString:@"featureIdentifier"]) { + return @[ @"id" ]; + } + if ([self.variable isEqualToString:@"featureAttributes"]) { + return @[ @"properties" ]; + } + return @[ @"var", self.variable ]; + } + + case NSConstantValueExpressionType: { + id constantValue = self.constantValue; + if (!constantValue || constantValue == [NSNull null]) { + return [NSNull null]; + } + if ([constantValue isEqual:@(M_E)]) { + return @[ @"e" ]; + } + if ([constantValue isEqual:@(M_PI)]) { + return @[ @"pi" ]; + } + if ([constantValue isKindOfClass:[NSArray class]] || + [constantValue isKindOfClass:[NSDictionary class]]) { + NSArray *collection = [constantValue mgl_jsonExpressionObject]; + return @[ @"literal", collection ]; + } + if ([constantValue isKindOfClass:[MLNColor class]]) { + auto color = [constantValue mgl_colorForPremultipliedValue]; + if (color.a == 1) { + return @[ @"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255) ]; + } + return @[ @"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a) ]; + } + if ([constantValue isKindOfClass:[NSValue class]]) { + const auto boxedValue = (NSValue *)constantValue; + if (strcmp([boxedValue objCType], @encode(CGVector)) == 0) { + // offset [x, y] + std::array mglValue = boxedValue.mgl_offsetArrayValue; + return @[ @"literal", @[ @(mglValue[0]), @(mglValue[1]) ] ]; + } + if (strcmp([boxedValue objCType], @encode(MLNEdgeInsets)) == 0) { + // padding [x, y] + std::array mglValue = boxedValue.mgl_paddingArrayValue; + return + @[ @"literal", @[ @(mglValue[0]), @(mglValue[1]), @(mglValue[2]), @(mglValue[3]) ] ]; + } + } + if ([constantValue isKindOfClass:[MLNAttributedExpression class]]) { + MLNAttributedExpression *attributedExpression = (MLNAttributedExpression *)constantValue; + id jsonObject = attributedExpression.expression.mgl_jsonExpressionObject; + NSMutableDictionary *attributedDictionary = + [NSMutableDictionary dictionary]; + + if (attributedExpression.attributes) { + attributedDictionary = + [NSMutableDictionary dictionaryWithDictionary:attributedExpression.attributes]; + + for (NSString *key in attributedExpression.attributes.allKeys) { + attributedDictionary[key] = + attributedExpression.attributes[key].mgl_jsonExpressionObject; + } + } + return @[ jsonObject, attributedDictionary ]; + } + if ([constantValue isKindOfClass:[MLNShape class]]) { + MLNShape *shape = (MLNShape *)constantValue; + return shape.geoJSONDictionary; + } + return self.constantValue; + } + + case NSKeyPathExpressionType: { + NSArray *expressionObject; + NSArray *keyPath = [self.keyPath componentsSeparatedByString:@"."]; + for (NSString *pathComponent in keyPath) { + if (expressionObject) { + expressionObject = @[ @"get", pathComponent, expressionObject ]; + } else { + expressionObject = @[ @"get", pathComponent ]; + } + } + + NSAssert(expressionObject.count > 0, @"expressionObject should be non-empty"); + + // Return a non-null value to quieten static analysis + return expressionObject ?: @[]; + } + + case NSFunctionExpressionType: { + NSString *function = self.function; + + BOOL hasCollectionProperty = + !(![self.arguments.firstObject isKindOfClass:[NSExpression class]] || + self.arguments.firstObject.expressionType != NSAggregateExpressionType || + self.arguments.firstObject.expressionType == NSSubqueryExpressionType); + NSString *op = MLNExpressionOperatorsByFunctionNames[function]; + if (op) { + NSArray *arguments = self.arguments.mgl_jsonExpressionObject; + return [@[ op ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"valueForKey:"] || + [function isEqualToString:@"valueForKeyPath:"]) { + return @[ + @"get", self.arguments.firstObject.mgl_jsonExpressionObject, + self.operand.mgl_jsonExpressionObject + ]; + } else if ([function isEqualToString:@"average:"]) { + NSExpression *sum = [NSExpression expressionForFunction:@"sum:" arguments:self.arguments]; + NSExpression *count = [NSExpression expressionForFunction:@"count:" + arguments:self.arguments]; + return [NSExpression expressionForFunction:@"divide:by:" arguments:@[ sum, count ]] + .mgl_jsonExpressionObject; + } else if ([function isEqualToString:@"sum:"]) { + NSArray *arguments; + if (hasCollectionProperty) { + arguments = + [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } + return [@[ @"+" ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"count:"]) { + NSArray *arguments = self.arguments.firstObject.mgl_jsonExpressionObject; + return @[ @"length", arguments ]; + } else if ([function isEqualToString:@"min:"]) { + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = + [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } + return [@[ @"min" ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"max:"]) { + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = + [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } + return [@[ @"max" ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"exp:"]) { + return [NSExpression expressionForFunction:@"raise:toPower:" + arguments:@[ @(M_E), self.arguments.firstObject ]] + .mgl_jsonExpressionObject; + } else if ([function isEqualToString:@"trunc:"]) { + return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)", + self.arguments.firstObject, + self.arguments.firstObject] + .mgl_jsonExpressionObject; + } else if ([function isEqualToString:@"mgl_join:"]) { + NSArray *arguments; + if (!hasCollectionProperty) { + arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; + } else { + arguments = + [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + } + return [@[ @"concat" ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"stringByAppendingString:"]) { + NSArray *arguments = self.arguments.mgl_jsonExpressionObject; + return [@[ @"concat", self.operand.mgl_jsonExpressionObject ] + arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"objectFrom:withIndex:"]) { + id index = self.arguments[1].mgl_jsonExpressionObject; + + if ([self.arguments[1] expressionType] == NSConstantValueExpressionType && + [[self.arguments[1] constantValue] isKindOfClass:[NSString class]]) { + id value = self.arguments[1].constantValue; + + if ([value isEqualToString:@"FIRST"]) { + index = [NSExpression expressionForConstantValue:@0].mgl_jsonExpressionObject; + } else if ([value isEqualToString:@"LAST"]) { + index = [NSExpression expressionWithFormat:@"count(%@) - 1", self.arguments[0]] + .mgl_jsonExpressionObject; + } else if ([value isEqualToString:@"SIZE"]) { + return [NSExpression expressionWithFormat:@"count(%@)", self.arguments[0]] + .mgl_jsonExpressionObject; + } } - case NSConstantValueExpressionType: { - id constantValue = self.constantValue; - if (!constantValue || constantValue == [NSNull null]) { - return [NSNull null]; - } - if ([constantValue isEqual:@(M_E)]) { - return @[@"e"]; - } - if ([constantValue isEqual:@(M_PI)]) { - return @[@"pi"]; - } - if ([constantValue isKindOfClass:[NSArray class]] || - [constantValue isKindOfClass:[NSDictionary class]]) { - NSArray *collection = [constantValue mgl_jsonExpressionObject]; - return @[@"literal", collection]; - } - if ([constantValue isKindOfClass:[MLNColor class]]) { - auto color = [constantValue mgl_colorForPremultipliedValue]; - if (color.a == 1) { - return @[@"rgb", @(color.r * 255), @(color.g * 255), @(color.b * 255)]; - } - return @[@"rgba", @(color.r * 255), @(color.g * 255), @(color.b * 255), @(color.a)]; - } - if ([constantValue isKindOfClass:[NSValue class]]) { - const auto boxedValue = (NSValue *)constantValue; - if (strcmp([boxedValue objCType], @encode(CGVector)) == 0) { - // offset [x, y] - std::array mglValue = boxedValue.mgl_offsetArrayValue; - return @[@"literal", @[@(mglValue[0]), @(mglValue[1])]]; - } - if (strcmp([boxedValue objCType], @encode(MLNEdgeInsets)) == 0) { - // padding [x, y] - std::array mglValue = boxedValue.mgl_paddingArrayValue; - return @[@"literal", @[@(mglValue[0]), @(mglValue[1]), @(mglValue[2]), @(mglValue[3])]]; - } - } - if ([constantValue isKindOfClass:[MLNAttributedExpression class]]) { - MLNAttributedExpression *attributedExpression = (MLNAttributedExpression *)constantValue; - id jsonObject = attributedExpression.expression.mgl_jsonExpressionObject; - NSMutableDictionary *attributedDictionary = [NSMutableDictionary dictionary]; - - if (attributedExpression.attributes) { - attributedDictionary = [NSMutableDictionary dictionaryWithDictionary:attributedExpression.attributes]; - - for (NSString *key in attributedExpression.attributes.allKeys) { - attributedDictionary[key] = attributedExpression.attributes[key].mgl_jsonExpressionObject; - } - - } - return @[jsonObject, attributedDictionary]; - } - if ([constantValue isKindOfClass:[MLNShape class]]) { - MLNShape *shape = (MLNShape *)constantValue; - return shape.geoJSONDictionary; - } - return self.constantValue; + return @[ @"at", index, self.arguments[0].mgl_jsonExpressionObject ]; + } else if ([function isEqualToString:@"boolValue"]) { + return @[ @"to-boolean", self.operand.mgl_jsonExpressionObject ]; + } else if ([function isEqualToString:@"mgl_number"] || + [function isEqualToString:@"mgl_numberWithFallbackValues:"] || + [function isEqualToString:@"decimalValue"] || + [function isEqualToString:@"floatValue"] || + [function isEqualToString:@"doubleValue"]) { + NSArray *arguments = self.arguments.mgl_jsonExpressionObject; + return [@[ @"to-number", self.operand.mgl_jsonExpressionObject ] + arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"stringValue"]) { + return @[ @"to-string", self.operand.mgl_jsonExpressionObject ]; + } else if ([function isEqualToString:@"noindex:"]) { + return self.arguments.firstObject.mgl_jsonExpressionObject; + } else if ([function isEqualToString:@"mgl_does:have:"] || + [function isEqualToString:@"mgl_has:"]) { + return self.mgl_jsonHasExpressionObject; + } else if ([function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"] || + [function isEqualToString:@"mgl_interpolateWithCurveType:parameters:stops:"]) { + return self.mgl_jsonInterpolationExpressionObject; + } else if ([function isEqualToString:@"mgl_step:from:stops:"] || + [function isEqualToString:@"mgl_stepWithMinimum:stops:"]) { + return self.mgl_jsonStepExpressionObject; + } else if ([function isEqualToString:@"mgl_expressionWithContext:"]) { + id context = self.arguments.firstObject; + if ([context isKindOfClass:[NSExpression class]]) { + context = [context constantValue]; } - - case NSKeyPathExpressionType: { - NSArray *expressionObject; - NSArray *keyPath = [self.keyPath componentsSeparatedByString:@"."]; - for (NSString *pathComponent in keyPath) { - if (expressionObject) { - expressionObject = @[@"get", pathComponent, expressionObject]; - } else { - expressionObject = @[@"get", pathComponent]; - } - } - - NSAssert(expressionObject.count > 0, @"expressionObject should be non-empty"); - - // Return a non-null value to quieten static analysis - return expressionObject ?: @[]; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"let", nil]; + [context enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, NSExpression *_Nonnull obj, + BOOL *_Nonnull stop) { + [expressionObject addObject:key]; + [expressionObject addObject:obj.mgl_jsonExpressionObject]; + }]; + [expressionObject addObject:self.operand.mgl_jsonExpressionObject]; + return expressionObject; + } else if ([function isEqualToString:@"MLN_IF"] || [function isEqualToString:@"MLN_IF:"] || + [function isEqualToString:@"mgl_if:"]) { + return self.mgl_jsonIfExpressionObject; + } else if ([function isEqualToString:@"MLN_MATCH"] || + [function isEqualToString:@"MLN_MATCH:"] || + [function isEqualToString:@"mgl_match:"]) { + return self.mgl_jsonMatchExpressionObject; + } else if ([function isEqualToString:@"mgl_coalesce:"] || + [function isEqualToString:@"mgl_coalesce"]) { + return self.mgl_jsonCoalesceExpressionObject; + } else if ([function isEqualToString:@"castObject:toType:"]) { + id object = self.arguments.firstObject.mgl_jsonExpressionObject; + NSString *type = self.arguments[1].mgl_jsonExpressionObject; + if ([type isEqualToString:@"NSString"]) { + return @[ @"to-string", object ]; + } else if ([type isEqualToString:@"NSNumber"]) { + return @[ @"to-number", object ]; } - - case NSFunctionExpressionType: { - NSString *function = self.function; - - BOOL hasCollectionProperty = !( ! [self.arguments.firstObject isKindOfClass: [NSExpression class]] || self.arguments.firstObject.expressionType != NSAggregateExpressionType || self.arguments.firstObject.expressionType == NSSubqueryExpressionType); - NSString *op = MLNExpressionOperatorsByFunctionNames[function]; - if (op) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[op] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"valueForKey:"] || [function isEqualToString:@"valueForKeyPath:"]) { - return @[@"get", self.arguments.firstObject.mgl_jsonExpressionObject, self.operand.mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"average:"]) { - NSExpression *sum = [NSExpression expressionForFunction:@"sum:" arguments:self.arguments]; - NSExpression *count = [NSExpression expressionForFunction:@"count:" arguments:self.arguments]; - return [NSExpression expressionForFunction:@"divide:by:" arguments:@[sum, count]].mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"sum:"]) { - NSArray *arguments; - if (hasCollectionProperty) { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } - return [@[@"+"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"count:"]) { - NSArray *arguments = self.arguments.firstObject.mgl_jsonExpressionObject; - return @[@"length", arguments]; - } else if ([function isEqualToString:@"min:"]) { - NSArray *arguments; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } - return [@[@"min"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"max:"]) { - NSArray *arguments; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } - return [@[@"max"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"exp:"]) { - return [NSExpression expressionForFunction:@"raise:toPower:" arguments:@[@(M_E), self.arguments.firstObject]].mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"trunc:"]) { - return [NSExpression expressionWithFormat:@"%@ - modulus:by:(%@, 1)", - self.arguments.firstObject, self.arguments.firstObject].mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"mgl_join:"]) { - NSArray *arguments; - if (!hasCollectionProperty) { - arguments = [self.arguments valueForKeyPath:@"mgl_jsonExpressionObject"]; - } else { - arguments = [self.arguments.firstObject.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - } - return [@[@"concat"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"stringByAppendingString:"]) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[@"concat", self.operand.mgl_jsonExpressionObject] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"objectFrom:withIndex:"]) { - id index = self.arguments[1].mgl_jsonExpressionObject; - - if ([self.arguments[1] expressionType] == NSConstantValueExpressionType - && [[self.arguments[1] constantValue] isKindOfClass:[NSString class]]) { - id value = self.arguments[1].constantValue; - - if ([value isEqualToString:@"FIRST"]) { - index = [NSExpression expressionForConstantValue:@0].mgl_jsonExpressionObject; - } else if ([value isEqualToString:@"LAST"]) { - index = [NSExpression expressionWithFormat:@"count(%@) - 1", self.arguments[0]].mgl_jsonExpressionObject; - } else if ([value isEqualToString:@"SIZE"]) { - return [NSExpression expressionWithFormat:@"count(%@)", self.arguments[0]].mgl_jsonExpressionObject; - } - } - - return @[@"at", index, self.arguments[0].mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"boolValue"]) { - return @[@"to-boolean", self.operand.mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"mgl_number"] || - [function isEqualToString:@"mgl_numberWithFallbackValues:"] || - [function isEqualToString:@"decimalValue"] || - [function isEqualToString:@"floatValue"] || - [function isEqualToString:@"doubleValue"]) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[@"to-number", self.operand.mgl_jsonExpressionObject] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"stringValue"]) { - return @[@"to-string", self.operand.mgl_jsonExpressionObject]; - } else if ([function isEqualToString:@"noindex:"]) { - return self.arguments.firstObject.mgl_jsonExpressionObject; - } else if ([function isEqualToString:@"mgl_does:have:"] || - [function isEqualToString:@"mgl_has:"]) { - return self.mgl_jsonHasExpressionObject; - } else if ([function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"] - || [function isEqualToString:@"mgl_interpolateWithCurveType:parameters:stops:"]) { - return self.mgl_jsonInterpolationExpressionObject; - } else if ([function isEqualToString:@"mgl_step:from:stops:"] - || [function isEqualToString:@"mgl_stepWithMinimum:stops:"]) { - return self.mgl_jsonStepExpressionObject; - } else if ([function isEqualToString:@"mgl_expressionWithContext:"]) { - id context = self.arguments.firstObject; - if ([context isKindOfClass:[NSExpression class]]) { - context = [context constantValue]; - } - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"let", nil]; - [context enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, NSExpression * _Nonnull obj, BOOL * _Nonnull stop) { - [expressionObject addObject:key]; - [expressionObject addObject:obj.mgl_jsonExpressionObject]; - }]; - [expressionObject addObject:self.operand.mgl_jsonExpressionObject]; - return expressionObject; - } else if ([function isEqualToString:@"MLN_IF"] || - [function isEqualToString:@"MLN_IF:"] || - [function isEqualToString:@"mgl_if:"]) { - return self.mgl_jsonIfExpressionObject; - } else if ([function isEqualToString:@"MLN_MATCH"] || - [function isEqualToString:@"MLN_MATCH:"] || - [function isEqualToString:@"mgl_match:"]) { - return self.mgl_jsonMatchExpressionObject; - } else if ([function isEqualToString:@"mgl_coalesce:"] || - [function isEqualToString:@"mgl_coalesce"]) { - - return self.mgl_jsonCoalesceExpressionObject; - } else if ([function isEqualToString:@"castObject:toType:"]) { - id object = self.arguments.firstObject.mgl_jsonExpressionObject; - NSString *type = self.arguments[1].mgl_jsonExpressionObject; - if ([type isEqualToString:@"NSString"]) { - return @[@"to-string", object]; - } else if ([type isEqualToString:@"NSNumber"]) { - return @[@"to-number", object]; - } #if TARGET_OS_IPHONE - else if ([type isEqualToString:@"UIColor"] || [type isEqualToString:@"MLNColor"]) { - return @[@"to-color", object]; - } + else if ([type isEqualToString:@"UIColor"] || [type isEqualToString:@"MLNColor"]) { + return @[ @"to-color", object ]; + } #else - else if ([type isEqualToString:@"NSColor"] || [type isEqualToString:@"MLNColor"]) { - return @[@"to-color", object]; - } + else if ([type isEqualToString:@"NSColor"] || [type isEqualToString:@"MLNColor"]) { + return @[ @"to-color", object ]; + } #endif - else if ([type isEqualToString:@"NSArray"]) { - NSExpression *operand = self.arguments.firstObject; - if ([operand expressionType] == NSFunctionExpressionType ) { - operand = self.arguments.firstObject.arguments.firstObject; - } - if (([operand expressionType] != NSConstantValueExpressionType) || - ([operand expressionType] == NSConstantValueExpressionType && - [[operand constantValue] isKindOfClass:[MLNColor class]])) { - return @[@"to-rgba", object]; - } - } - [NSException raise:NSInvalidArgumentException - format:@"Casting expression to %@ not yet implemented.", type]; - } else if ([function isEqualToString:@"mgl_attributed:"]) { - return [self mgl_jsonFormatExpressionObject]; - - } else if ([function isEqualToString:@"MLN_FUNCTION"] || - [function isEqualToString:@"MLN_FUNCTION:"]) { - NSExpression *firstOp = self.arguments.firstObject; - if (firstOp.expressionType == NSConstantValueExpressionType - && [firstOp.constantValue isEqualToString:@"collator"]) { - // Avoid wrapping collator options object in literal expression. - return @[@"collator", self.arguments[1].constantValue]; - } - if (firstOp.expressionType == NSConstantValueExpressionType - && [firstOp.constantValue isEqualToString:@"format"]) { - // Avoid wrapping format options object in literal expression. - NSMutableArray *expressionObject = [NSMutableArray array]; - [expressionObject addObject:@"format"]; - - for (NSUInteger index = 1; index < self.arguments.count; index++) { - if (index % 2 == 1) { - [expressionObject addObject:self.arguments[index].mgl_jsonExpressionObject]; - } else { - [expressionObject addObject:self.arguments[index].constantValue]; - } - - } - - return expressionObject; - } - - return self.arguments.mgl_jsonExpressionObject; - } else if (op == (NSString *) [MLNColor class] && [function isEqualToString:@"colorWithRed:green:blue:alpha:"]) { - NSArray *arguments = self.arguments.mgl_jsonExpressionObject; - return [@[@"rgba"] arrayByAddingObjectsFromArray:arguments]; - } else if ([function isEqualToString:@"median:"] || - [function isEqualToString:@"mode:"] || - [function isEqualToString:@"stddev:"] || - [function isEqualToString:@"random"] || - [function isEqualToString:@"randomn:"] || - [function isEqualToString:@"now"] || - [function isEqualToString:@"bitwiseAnd:with:"] || - [function isEqualToString:@"bitwiseOr:with:"] || - [function isEqualToString:@"bitwiseXor:with:"] || - [function isEqualToString:@"leftshift:by:"] || - [function isEqualToString:@"rightshift:by:"] || - [function isEqualToString:@"onesComplement:"] || - [function isEqualToString:@"distanceToLocation:fromLocation:"]) { - [NSException raise:NSInvalidArgumentException - format:@"Expression function %@ not yet implemented.", function]; - return nil; - } else { - [NSException raise:NSInvalidArgumentException - format:@"Unrecognized expression function %@.", function]; - return nil; - } + else if ([type isEqualToString:@"NSArray"]) { + NSExpression *operand = self.arguments.firstObject; + if ([operand expressionType] == NSFunctionExpressionType) { + operand = self.arguments.firstObject.arguments.firstObject; + } + if (([operand expressionType] != NSConstantValueExpressionType) || + ([operand expressionType] == NSConstantValueExpressionType && + [[operand constantValue] isKindOfClass:[MLNColor class]])) { + return @[ @"to-rgba", object ]; + } } - - case NSConditionalExpressionType: { - NSMutableArray *arguments = [NSMutableArray arrayWithObjects:@"case", self.predicate.mgl_jsonExpressionObject, nil]; - [arguments addObject:self.trueExpression.mgl_jsonExpressionObject]; - [arguments addObject:self.falseExpression.mgl_jsonExpressionObject]; - - return arguments; - } - - case NSAggregateExpressionType: { - NSArray *collection = [self.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; - return @[@"literal", collection]; - } - - case NSEvaluatedObjectExpressionType: - case NSUnionSetExpressionType: - case NSIntersectSetExpressionType: - case NSMinusSetExpressionType: - case NSSubqueryExpressionType: - case NSAnyKeyExpressionType: - case NSBlockExpressionType: - [NSException raise:NSInvalidArgumentException - format:@"Expression type %lu not yet implemented.", (unsigned long)self.expressionType]; - } - - // NSKeyPathSpecifierExpression - if (self.expressionType == 10) { - return self.description; - } - // An assignment expression type is present in the BNF grammar, but the - // corresponding NSExpressionType value and property getters are missing. - if (self.expressionType == 12) { [NSException raise:NSInvalidArgumentException - format:@"Assignment expressions not yet implemented."]; + format:@"Casting expression to %@ not yet implemented.", type]; + } else if ([function isEqualToString:@"mgl_attributed:"]) { + return [self mgl_jsonFormatExpressionObject]; + + } else if ([function isEqualToString:@"MLN_FUNCTION"] || + [function isEqualToString:@"MLN_FUNCTION:"]) { + NSExpression *firstOp = self.arguments.firstObject; + if (firstOp.expressionType == NSConstantValueExpressionType && + [firstOp.constantValue isEqualToString:@"collator"]) { + // Avoid wrapping collator options object in literal expression. + return @[ @"collator", self.arguments[1].constantValue ]; + } + if (firstOp.expressionType == NSConstantValueExpressionType && + [firstOp.constantValue isEqualToString:@"format"]) { + // Avoid wrapping format options object in literal expression. + NSMutableArray *expressionObject = [NSMutableArray array]; + [expressionObject addObject:@"format"]; + + for (NSUInteger index = 1; index < self.arguments.count; index++) { + if (index % 2 == 1) { + [expressionObject addObject:self.arguments[index].mgl_jsonExpressionObject]; + } else { + [expressionObject addObject:self.arguments[index].constantValue]; + } + } + + return expressionObject; + } + + return self.arguments.mgl_jsonExpressionObject; + } else if (op == (NSString *)[MLNColor class] && + [function isEqualToString:@"colorWithRed:green:blue:alpha:"]) { + NSArray *arguments = self.arguments.mgl_jsonExpressionObject; + return [@[ @"rgba" ] arrayByAddingObjectsFromArray:arguments]; + } else if ([function isEqualToString:@"median:"] || [function isEqualToString:@"mode:"] || + [function isEqualToString:@"stddev:"] || [function isEqualToString:@"random"] || + [function isEqualToString:@"randomn:"] || [function isEqualToString:@"now"] || + [function isEqualToString:@"bitwiseAnd:with:"] || + [function isEqualToString:@"bitwiseOr:with:"] || + [function isEqualToString:@"bitwiseXor:with:"] || + [function isEqualToString:@"leftshift:by:"] || + [function isEqualToString:@"rightshift:by:"] || + [function isEqualToString:@"onesComplement:"] || + [function isEqualToString:@"distanceToLocation:fromLocation:"]) { + [NSException raise:NSInvalidArgumentException + format:@"Expression function %@ not yet implemented.", function]; + return nil; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Unrecognized expression function %@.", function]; + return nil; + } } - return nil; + case NSConditionalExpressionType: { + NSMutableArray *arguments = + [NSMutableArray arrayWithObjects:@"case", self.predicate.mgl_jsonExpressionObject, nil]; + [arguments addObject:self.trueExpression.mgl_jsonExpressionObject]; + [arguments addObject:self.falseExpression.mgl_jsonExpressionObject]; + + return arguments; + } + + case NSAggregateExpressionType: { + NSArray *collection = [self.collection valueForKeyPath:@"mgl_jsonExpressionObject"]; + return @[ @"literal", collection ]; + } + + case NSEvaluatedObjectExpressionType: + case NSUnionSetExpressionType: + case NSIntersectSetExpressionType: + case NSMinusSetExpressionType: + case NSSubqueryExpressionType: + case NSAnyKeyExpressionType: + case NSBlockExpressionType: + [NSException + raise:NSInvalidArgumentException + format:@"Expression type %lu not yet implemented.", (unsigned long)self.expressionType]; + } + + // NSKeyPathSpecifierExpression + if (self.expressionType == 10) { + return self.description; + } + // An assignment expression type is present in the BNF grammar, but the + // corresponding NSExpressionType value and property getters are missing. + if (self.expressionType == 12) { + [NSException raise:NSInvalidArgumentException + format:@"Assignment expressions not yet implemented."]; + } + + return nil; } - (id)mgl_jsonInterpolationExpressionObject { - NSUInteger expectedArgumentCount = [self.function componentsSeparatedByString:@":"].count - 1; - if (self.arguments.count < expectedArgumentCount) { - [NSException raise:NSInvalidArgumentException format: - @"Too few arguments to ‘%@’ function; expected %lu arguments.", - self.function, (unsigned long)expectedArgumentCount]; - } else if (self.arguments.count > expectedArgumentCount) { - [NSException raise:NSInvalidArgumentException format: - @"%lu unexpected arguments to ‘%@’ function; expected %lu arguments.", - self.arguments.count - (unsigned long)expectedArgumentCount, self.function, (unsigned long)expectedArgumentCount]; - } + NSUInteger expectedArgumentCount = [self.function componentsSeparatedByString:@":"].count - 1; + if (self.arguments.count < expectedArgumentCount) { + [NSException raise:NSInvalidArgumentException + format:@"Too few arguments to ‘%@’ function; expected %lu arguments.", + self.function, (unsigned long)expectedArgumentCount]; + } else if (self.arguments.count > expectedArgumentCount) { + [NSException raise:NSInvalidArgumentException + format:@"%lu unexpected arguments to ‘%@’ function; expected %lu arguments.", + self.arguments.count - (unsigned long)expectedArgumentCount, self.function, + (unsigned long)expectedArgumentCount]; + } - BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"]; - NSUInteger curveTypeIndex = isAftermarketFunction ? 1 : 0; - NSString *curveType = self.arguments[curveTypeIndex].constantValue; - NSMutableArray *interpolationArray = [NSMutableArray arrayWithObject:curveType]; - if ([curveType isEqualToString:@"exponential"]) { - id base = [self.arguments[curveTypeIndex + 1] mgl_jsonExpressionObject]; - [interpolationArray addObject:base]; - } else if ([curveType isEqualToString:@"cubic-bezier"]) { - NSArray *controlPoints = [self.arguments[curveTypeIndex + 1].collection mgl_jsonExpressionObject]; - [interpolationArray addObjectsFromArray:controlPoints]; - } + BOOL isAftermarketFunction = + [self.function isEqualToString:@"mgl_interpolate:withCurveType:parameters:stops:"]; + NSUInteger curveTypeIndex = isAftermarketFunction ? 1 : 0; + NSString *curveType = self.arguments[curveTypeIndex].constantValue; + NSMutableArray *interpolationArray = [NSMutableArray arrayWithObject:curveType]; + if ([curveType isEqualToString:@"exponential"]) { + id base = [self.arguments[curveTypeIndex + 1] mgl_jsonExpressionObject]; + [interpolationArray addObject:base]; + } else if ([curveType isEqualToString:@"cubic-bezier"]) { + NSArray *controlPoints = + [self.arguments[curveTypeIndex + 1].collection mgl_jsonExpressionObject]; + [interpolationArray addObjectsFromArray:controlPoints]; + } - NSDictionary *stops = self.arguments[curveTypeIndex + 2].constantValue; + NSDictionary *stops = + self.arguments[curveTypeIndex + 2].constantValue; - if (stops.count == 0) { - [NSException raise:NSInvalidArgumentException format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; - } + if (stops.count == 0) { + [NSException + raise:NSInvalidArgumentException + format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; + } - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"interpolate", interpolationArray, nil]; - [expressionObject addObject:(isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject]; - for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { - [expressionObject addObject:key]; - [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; - } - return expressionObject; + NSMutableArray *expressionObject = + [NSMutableArray arrayWithObjects:@"interpolate", interpolationArray, nil]; + [expressionObject addObject:(isAftermarketFunction ? self.arguments.firstObject : self.operand) + .mgl_jsonExpressionObject]; + for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + [expressionObject addObject:key]; + [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; + } + return expressionObject; } - (id)mgl_jsonStepExpressionObject { - BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_step:from:stops:"]; - NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; - id minimum = self.arguments[minimumIndex].mgl_jsonExpressionObject; - NSDictionary *stops = self.arguments[minimumIndex + 1].constantValue; + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_step:from:stops:"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + id minimum = self.arguments[minimumIndex].mgl_jsonExpressionObject; + NSDictionary *stops = self.arguments[minimumIndex + 1].constantValue; - if (stops.count == 0) { - [NSException raise:NSInvalidArgumentException format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; - } + if (stops.count == 0) { + [NSException + raise:NSInvalidArgumentException + format:@"‘stops’ dictionary argument to ‘%@’ function must not be empty.", self.function]; + } - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"step", (isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject, minimum, nil]; + NSMutableArray *expressionObject = [NSMutableArray + arrayWithObjects:@"step", + (isAftermarketFunction ? self.arguments.firstObject : self.operand) + .mgl_jsonExpressionObject, + minimum, nil]; - for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { - [expressionObject addObject:key]; - [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; - } - return expressionObject; + for (NSNumber *key in [stops.allKeys sortedArrayUsingSelector:@selector(compare:)]) { + [expressionObject addObject:key]; + [expressionObject addObject:[stops[key] mgl_jsonExpressionObject]]; + } + return expressionObject; } - (id)mgl_jsonMatchExpressionObject { - BOOL isAftermarketFunction = [self.function hasPrefix:@"MLN_MATCH"]; - NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + BOOL isAftermarketFunction = [self.function hasPrefix:@"MLN_MATCH"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"match", (isAftermarketFunction ? self.arguments.firstObject : self.operand).mgl_jsonExpressionObject, nil]; - NSArray *arguments = isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; + NSMutableArray *expressionObject = [NSMutableArray + arrayWithObjects:@"match", + (isAftermarketFunction ? self.arguments.firstObject : self.operand) + .mgl_jsonExpressionObject, + nil]; + NSArray *arguments = + isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; - for (NSUInteger index = minimumIndex; index < arguments.count; index++) { - NSArray *argumentObject = arguments[index].mgl_jsonExpressionObject; - // match operators with arrays as matching values should not parse arrays using the literal operator. - if (index > 0 && index < arguments.count - 1 && !(index % 2 == 0)) { - NSExpression *expression = arguments[index]; - if (![expression isKindOfClass:[NSExpression class]]) { - expression = [NSExpression expressionForConstantValue:expression]; - } - if (expression.expressionType == NSAggregateExpressionType || - (expression.expressionType == NSConstantValueExpressionType && [expression.constantValue isKindOfClass:[NSArray class]])) { - argumentObject = argumentObject.count == 2 ? argumentObject[1] : argumentObject; - } - } - [expressionObject addObject:argumentObject]; + for (NSUInteger index = minimumIndex; index < arguments.count; index++) { + NSArray *argumentObject = arguments[index].mgl_jsonExpressionObject; + // match operators with arrays as matching values should not parse arrays using the literal + // operator. + if (index > 0 && index < arguments.count - 1 && !(index % 2 == 0)) { + NSExpression *expression = arguments[index]; + if (![expression isKindOfClass:[NSExpression class]]) { + expression = [NSExpression expressionForConstantValue:expression]; + } + if (expression.expressionType == NSAggregateExpressionType || + (expression.expressionType == NSConstantValueExpressionType && + [expression.constantValue isKindOfClass:[NSArray class]])) { + argumentObject = argumentObject.count == 2 ? argumentObject[1] : argumentObject; + } } + [expressionObject addObject:argumentObject]; + } - return expressionObject; + return expressionObject; } - (id)mgl_jsonIfExpressionObject { - BOOL isAftermarketFunction = [self.function hasPrefix:@"MLN_IF"]; - NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; - NSExpression *firstCondition; - id condition; + BOOL isAftermarketFunction = [self.function hasPrefix:@"MLN_IF"]; + NSUInteger minimumIndex = isAftermarketFunction ? 1 : 0; + NSExpression *firstCondition; + id condition; - if (isAftermarketFunction) { - firstCondition = self.arguments.firstObject; + if (isAftermarketFunction) { + firstCondition = self.arguments.firstObject; + } else { + firstCondition = self.operand; + } + + if ([firstCondition respondsToSelector:@selector(constantValue)] && + [firstCondition.constantValue isKindOfClass:[NSComparisonPredicate class]]) { + NSPredicate *predicate = (NSPredicate *)firstCondition.constantValue; + condition = predicate.mgl_jsonExpressionObject; + } else { + condition = firstCondition.mgl_jsonExpressionObject; + } + + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"case", condition, nil]; + NSArray *arguments = + isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; + + for (NSUInteger index = minimumIndex; index < arguments.count; index++) { + if ([arguments[index] respondsToSelector:@selector(constantValue)] && + [arguments[index].constantValue isKindOfClass:[NSComparisonPredicate class]]) { + NSPredicate *predicate = (NSPredicate *)arguments[index].constantValue; + [expressionObject addObject:predicate.mgl_jsonExpressionObject]; } else { - firstCondition = self.operand; + [expressionObject addObject:arguments[index].mgl_jsonExpressionObject]; } + } - if ([firstCondition respondsToSelector:@selector(constantValue)] && [firstCondition.constantValue isKindOfClass:[NSComparisonPredicate class]]) { - NSPredicate *predicate = (NSPredicate *)firstCondition.constantValue; - condition = predicate.mgl_jsonExpressionObject; - } else { - condition = firstCondition.mgl_jsonExpressionObject; - } - - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"case", condition, nil]; - NSArray *arguments = isAftermarketFunction ? self.arguments : self.arguments[minimumIndex].constantValue; - - for (NSUInteger index = minimumIndex; index < arguments.count; index++) { - if ([arguments[index] respondsToSelector:@selector(constantValue)] && [arguments[index].constantValue isKindOfClass:[NSComparisonPredicate class]]) { - NSPredicate *predicate = (NSPredicate *)arguments[index].constantValue; - [expressionObject addObject:predicate.mgl_jsonExpressionObject]; - } else { - [expressionObject addObject:arguments[index].mgl_jsonExpressionObject]; - } - } - - return expressionObject; + return expressionObject; } - (id)mgl_jsonCoalesceExpressionObject { - BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_coalesce:"]; - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"coalesce", nil]; + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_coalesce:"]; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"coalesce", nil]; - for (NSExpression *expression in (isAftermarketFunction ? self.arguments.firstObject : self.operand).constantValue) { - [expressionObject addObject:[expression mgl_jsonExpressionObject]]; - } + for (NSExpression *expression in (isAftermarketFunction ? self.arguments.firstObject + : self.operand) + .constantValue) { + [expressionObject addObject:[expression mgl_jsonExpressionObject]]; + } - return expressionObject; + return expressionObject; } - (id)mgl_jsonHasExpressionObject { - BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_does:have:"]; - NSExpression *operand = isAftermarketFunction ? self.arguments[0] : self.operand; - NSExpression *key = self.arguments[isAftermarketFunction ? 1 : 0]; + BOOL isAftermarketFunction = [self.function isEqualToString:@"mgl_does:have:"]; + NSExpression *operand = isAftermarketFunction ? self.arguments[0] : self.operand; + NSExpression *key = self.arguments[isAftermarketFunction ? 1 : 0]; - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"has", key.mgl_jsonExpressionObject, nil]; - if (operand.expressionType != NSEvaluatedObjectExpressionType) { - [expressionObject addObject:operand.mgl_jsonExpressionObject]; - } - return expressionObject; + NSMutableArray *expressionObject = + [NSMutableArray arrayWithObjects:@"has", key.mgl_jsonExpressionObject, nil]; + if (operand.expressionType != NSEvaluatedObjectExpressionType) { + [expressionObject addObject:operand.mgl_jsonExpressionObject]; + } + return expressionObject; } - (id)mgl_jsonFormatExpressionObject { - NSArray *attributedExpressions; - NSExpression *formatArray = self.arguments.firstObject; + NSArray *attributedExpressions; + NSExpression *formatArray = self.arguments.firstObject; - if ([formatArray respondsToSelector:@selector(constantValue)] && [formatArray.constantValue isKindOfClass:[NSArray class]]) { - attributedExpressions = (NSArray *)formatArray.constantValue; - } else { - attributedExpressions = self.arguments; - } + if ([formatArray respondsToSelector:@selector(constantValue)] && + [formatArray.constantValue isKindOfClass:[NSArray class]]) { + attributedExpressions = (NSArray *)formatArray.constantValue; + } else { + attributedExpressions = self.arguments; + } - NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"format", nil]; + NSMutableArray *expressionObject = [NSMutableArray arrayWithObjects:@"format", nil]; - for (NSUInteger index = 0; index < attributedExpressions.count; index++) { - [expressionObject addObjectsFromArray:attributedExpressions[index].mgl_jsonExpressionObject]; - } + for (NSUInteger index = 0; index < attributedExpressions.count; index++) { + [expressionObject addObjectsFromArray:attributedExpressions[index].mgl_jsonExpressionObject]; + } - return expressionObject; + return expressionObject; } // MARK: Localization @@ -1520,18 +1637,20 @@ NSArray *MLNSubexpressionsWithJSONObjects(NSArray *objects) { If no localization takes place, this method returns the original collection. */ -NSArray *MLNLocalizedCollection(NSArray *collection, NSLocale * _Nullable locale) { - __block NSMutableArray *localizedCollection; - [collection enumerateObjectsUsingBlock:^(NSExpression * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) { - NSExpression *localizedItem = [item mgl_expressionLocalizedIntoLocale:locale]; - if (localizedItem != item) { - if (!localizedCollection) { - localizedCollection = [collection mutableCopy]; - } - localizedCollection[idx] = localizedItem; - } - }]; - return localizedCollection ?: collection; +NSArray *MLNLocalizedCollection(NSArray *collection, + NSLocale *_Nullable locale) { + __block NSMutableArray *localizedCollection; + [collection enumerateObjectsUsingBlock:^(NSExpression *_Nonnull item, NSUInteger idx, + BOOL *_Nonnull stop) { + NSExpression *localizedItem = [item mgl_expressionLocalizedIntoLocale:locale]; + if (localizedItem != item) { + if (!localizedCollection) { + localizedCollection = [collection mutableCopy]; + } + localizedCollection[idx] = localizedItem; + } + }]; + return localizedCollection ?: collection; }; /** @@ -1540,135 +1659,153 @@ NSArray *MLNLocalizedCollection(NSArray *collect If no localization takes place, this method returns the original stop dictionary. */ -NSDictionary *MLNLocalizedStopDictionary(NSDictionary *stops, NSLocale * _Nullable locale) { - __block NSMutableDictionary *localizedStops; - [stops enumerateKeysAndObjectsUsingBlock:^(id _Nonnull zoomLevel, NSExpression * _Nonnull value, BOOL * _Nonnull stop) { - if (![value isKindOfClass:[NSExpression class]]) { - value = [NSExpression expressionForConstantValue:value]; - } - NSExpression *localizedValue = [value mgl_expressionLocalizedIntoLocale:locale]; - if (localizedValue != value) { - if (!localizedStops) { - localizedStops = [stops mutableCopy]; - } - localizedStops[zoomLevel] = localizedValue; - } - }]; - return localizedStops ?: stops; +NSDictionary *MLNLocalizedStopDictionary( + NSDictionary *stops, NSLocale *_Nullable locale) { + __block NSMutableDictionary *localizedStops; + [stops enumerateKeysAndObjectsUsingBlock:^(id _Nonnull zoomLevel, NSExpression *_Nonnull value, + BOOL *_Nonnull stop) { + if (![value isKindOfClass:[NSExpression class]]) { + value = [NSExpression expressionForConstantValue:value]; + } + NSExpression *localizedValue = [value mgl_expressionLocalizedIntoLocale:locale]; + if (localizedValue != value) { + if (!localizedStops) { + localizedStops = [stops mutableCopy]; + } + localizedStops[zoomLevel] = localizedValue; + } + }]; + return localizedStops ?: stops; }; - (NSExpression *)mgl_expressionLocalizedIntoLocale:(nullable NSLocale *)locale { - switch (self.expressionType) { - case NSConstantValueExpressionType: { - if ([self.constantValue isKindOfClass:[NSDictionary class]]) { - NSDictionary *localizedStops = MLNLocalizedStopDictionary(self.constantValue, locale); - if (localizedStops != self.constantValue) { - return [NSExpression expressionForConstantValue:localizedStops]; - } - } else if ([self.constantValue isKindOfClass:[NSArray class]]) { - NSArray *localizedValues = MLNLocalizedCollection(self.constantValue, locale); - if (localizedValues != self.constantValue) { - return [NSExpression expressionForConstantValue:localizedValues]; - } - } else if ([self.constantValue isKindOfClass:[MLNAttributedExpression class]]) { - MLNAttributedExpression *attributedExpression = (MLNAttributedExpression *)self.constantValue; - NSExpression *localizedExpression = [attributedExpression.expression mgl_expressionLocalizedIntoLocale:locale]; - MLNAttributedExpression *localizedAttributedExpression = [MLNAttributedExpression attributedExpression:localizedExpression attributes:attributedExpression.attributes]; - - return [NSExpression expressionForConstantValue:localizedAttributedExpression]; - } - return self; + switch (self.expressionType) { + case NSConstantValueExpressionType: { + if ([self.constantValue isKindOfClass:[NSDictionary class]]) { + NSDictionary *localizedStops = MLNLocalizedStopDictionary(self.constantValue, locale); + if (localizedStops != self.constantValue) { + return [NSExpression expressionForConstantValue:localizedStops]; } - - case NSKeyPathExpressionType: { - if ([self.keyPath isEqualToString:@"name"] || [self.keyPath hasPrefix:@"name_"]) { - NSString *localizedKeyPath = @"name"; - if (![locale.localeIdentifier isEqualToString:@"mul"]) { - NSArray *preferences = locale ? @[locale.localeIdentifier] : [NSLocale preferredLanguages]; - NSString *preferredLanguage = [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]; - if (preferredLanguage) { - localizedKeyPath = [NSString stringWithFormat:@"name_%@", preferredLanguage]; - } - } - // If the keypath is `name`, no need to fallback - if ([localizedKeyPath isEqualToString:@"name"]) { - return [NSExpression expressionForKeyPath:localizedKeyPath]; - } - // If the keypath is `name_zh-Hans`, fallback to `name_zh` to `name`. - // CN tiles might using `name_zh-CN` for Simplified Chinese. - if ([localizedKeyPath isEqualToString:@"name_zh-Hans"]) { - return [NSExpression expressionForFunctionHelper:@"mgl_coalesce:" arguments:@[[NSExpression expressionForConstantValue:@[ - [NSExpression expressionForKeyPath:localizedKeyPath], - [NSExpression expressionForKeyPath:@"name_zh-CN"], - [NSExpression expressionForKeyPath:@"name_zh"], - [NSExpression expressionForKeyPath:@"name"], - ]]]]; - } - // Mapbox Streets v8 has `name_zh-Hant`, we should fallback to Simplified Chinese if the field has no value. - if ([localizedKeyPath isEqualToString:@"name_zh-Hant"]) { - return [NSExpression expressionForFunctionHelper:@"mgl_coalesce:" arguments:@[[NSExpression expressionForConstantValue:@[ - [NSExpression expressionForKeyPath:localizedKeyPath], - [NSExpression expressionForKeyPath:@"name_zh-CN"], - [NSExpression expressionForKeyPath:@"name_zh"], - [NSExpression expressionForKeyPath:@"name"], - ]]]]; - } - - // Other keypath fallback to `name` - return [NSExpression expressionForFunctionHelper:@"mgl_coalesce:" arguments:@[[NSExpression expressionForConstantValue:@[ - [NSExpression expressionForKeyPath:localizedKeyPath], - [NSExpression expressionForKeyPath:@"name"], - ]]]]; - } - return self; + } else if ([self.constantValue isKindOfClass:[NSArray class]]) { + NSArray *localizedValues = MLNLocalizedCollection(self.constantValue, locale); + if (localizedValues != self.constantValue) { + return [NSExpression expressionForConstantValue:localizedValues]; } + } else if ([self.constantValue isKindOfClass:[MLNAttributedExpression class]]) { + MLNAttributedExpression *attributedExpression = + (MLNAttributedExpression *)self.constantValue; + NSExpression *localizedExpression = + [attributedExpression.expression mgl_expressionLocalizedIntoLocale:locale]; + MLNAttributedExpression *localizedAttributedExpression = + [MLNAttributedExpression attributedExpression:localizedExpression + attributes:attributedExpression.attributes]; - case NSFunctionExpressionType: { - NSExpression *operand = self.operand; - NSExpression *localizedOperand = [operand mgl_expressionLocalizedIntoLocale:locale]; - - NSArray *arguments = self.arguments; - NSArray *localizedArguments = MLNLocalizedCollection(arguments, locale); - if (localizedArguments != arguments) { - return [NSExpression expressionForFunction:localizedOperand - selectorName:self.function - arguments:localizedArguments]; - } - if (localizedOperand != operand) { - return [NSExpression expressionForFunction:localizedOperand - selectorName:self.function - arguments:self.arguments]; - } - return self; - } - - case NSConditionalExpressionType: { - NSExpression *trueExpression = self.trueExpression; - NSExpression *localizedTrueExpression = [trueExpression mgl_expressionLocalizedIntoLocale:locale]; - NSExpression *falseExpression = self.falseExpression; - NSExpression *localizedFalseExpression = [falseExpression mgl_expressionLocalizedIntoLocale:locale]; - if (localizedTrueExpression != trueExpression || localizedFalseExpression != falseExpression) { - return [NSExpression expressionForConditional:self.predicate - trueExpression:localizedTrueExpression - falseExpression:localizedFalseExpression]; - } - return self; - } - - case NSAggregateExpressionType: { - NSArray *collection = self.collection; - if ([collection isKindOfClass:[NSArray class]]) { - NSArray *localizedCollection = MLNLocalizedCollection(collection, locale); - if (localizedCollection != collection) { - return [NSExpression expressionForAggregate:localizedCollection]; - } - } - return self; - } - - default: - return self; + return [NSExpression expressionForConstantValue:localizedAttributedExpression]; + } + return self; } + + case NSKeyPathExpressionType: { + if ([self.keyPath isEqualToString:@"name"] || [self.keyPath hasPrefix:@"name_"]) { + NSString *localizedKeyPath = @"name"; + if (![locale.localeIdentifier isEqualToString:@"mul"]) { + NSArray *preferences = + locale ? @[ locale.localeIdentifier ] : [NSLocale preferredLanguages]; + NSString *preferredLanguage = + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]; + if (preferredLanguage) { + localizedKeyPath = [NSString stringWithFormat:@"name_%@", preferredLanguage]; + } + } + // If the keypath is `name`, no need to fallback + if ([localizedKeyPath isEqualToString:@"name"]) { + return [NSExpression expressionForKeyPath:localizedKeyPath]; + } + // If the keypath is `name_zh-Hans`, fallback to `name_zh` to `name`. + // CN tiles might using `name_zh-CN` for Simplified Chinese. + if ([localizedKeyPath isEqualToString:@"name_zh-Hans"]) { + return [NSExpression + expressionForFunctionHelper:@"mgl_coalesce:" + arguments:@[ [NSExpression expressionForConstantValue:@[ + [NSExpression expressionForKeyPath:localizedKeyPath], + [NSExpression expressionForKeyPath:@"name_zh-CN"], + [NSExpression expressionForKeyPath:@"name_zh"], + [NSExpression expressionForKeyPath:@"name"], + ]] ]]; + } + // Mapbox Streets v8 has `name_zh-Hant`, we should fallback to Simplified Chinese if the + // field has no value. + if ([localizedKeyPath isEqualToString:@"name_zh-Hant"]) { + return [NSExpression + expressionForFunctionHelper:@"mgl_coalesce:" + arguments:@[ [NSExpression expressionForConstantValue:@[ + [NSExpression expressionForKeyPath:localizedKeyPath], + [NSExpression expressionForKeyPath:@"name_zh-CN"], + [NSExpression expressionForKeyPath:@"name_zh"], + [NSExpression expressionForKeyPath:@"name"], + ]] ]]; + } + + // Other keypath fallback to `name` + return + [NSExpression expressionForFunctionHelper:@"mgl_coalesce:" + arguments:@[ [NSExpression expressionForConstantValue:@[ + [NSExpression expressionForKeyPath:localizedKeyPath], + [NSExpression expressionForKeyPath:@"name"], + ]] ]]; + } + return self; + } + + case NSFunctionExpressionType: { + NSExpression *operand = self.operand; + NSExpression *localizedOperand = [operand mgl_expressionLocalizedIntoLocale:locale]; + + NSArray *arguments = self.arguments; + NSArray *localizedArguments = MLNLocalizedCollection(arguments, locale); + if (localizedArguments != arguments) { + return [NSExpression expressionForFunction:localizedOperand + selectorName:self.function + arguments:localizedArguments]; + } + if (localizedOperand != operand) { + return [NSExpression expressionForFunction:localizedOperand + selectorName:self.function + arguments:self.arguments]; + } + return self; + } + + case NSConditionalExpressionType: { + NSExpression *trueExpression = self.trueExpression; + NSExpression *localizedTrueExpression = + [trueExpression mgl_expressionLocalizedIntoLocale:locale]; + NSExpression *falseExpression = self.falseExpression; + NSExpression *localizedFalseExpression = + [falseExpression mgl_expressionLocalizedIntoLocale:locale]; + if (localizedTrueExpression != trueExpression || + localizedFalseExpression != falseExpression) { + return [NSExpression expressionForConditional:self.predicate + trueExpression:localizedTrueExpression + falseExpression:localizedFalseExpression]; + } + return self; + } + + case NSAggregateExpressionType: { + NSArray *collection = self.collection; + if ([collection isKindOfClass:[NSArray class]]) { + NSArray *localizedCollection = MLNLocalizedCollection(collection, locale); + if (localizedCollection != collection) { + return [NSExpression expressionForAggregate:localizedCollection]; + } + } + return self; + } + + default: + return self; + } } @end diff --git a/platform/darwin/src/NSPredicate+MLNAdditions.mm b/platform/darwin/src/NSPredicate+MLNAdditions.mm index 5b96b22d9..2d91f1b56 100644 --- a/platform/darwin/src/NSPredicate+MLNAdditions.mm +++ b/platform/darwin/src/NSPredicate+MLNAdditions.mm @@ -1,37 +1,36 @@ #import "NSPredicate+MLNPrivateAdditions.h" -#import "MLNValueEvaluator.h" -#import "MLNStyleValue_Private.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNStyleValue_Private.h" +#import "MLNValueEvaluator.h" #include @implementation NSPredicate (MLNPrivateAdditions) -- (mbgl::style::Filter)mgl_filter -{ - mbgl::style::conversion::Error valueError; - NSArray *jsonObject = self.mgl_jsonExpressionObject; - auto value = mbgl::style::conversion::convert(mbgl::style::conversion::makeConvertible(jsonObject), valueError); +- (mbgl::style::Filter)mgl_filter { + mbgl::style::conversion::Error valueError; + NSArray *jsonObject = self.mgl_jsonExpressionObject; + auto value = mbgl::style::conversion::convert( + mbgl::style::conversion::makeConvertible(jsonObject), valueError); - if (!value) { - [NSException raise:NSInvalidArgumentException - format:@"Invalid filter value: %@", @(valueError.message.c_str())]; - return {}; - } - mbgl::style::Filter filter = std::move(*value); + if (!value) { + [NSException raise:NSInvalidArgumentException + format:@"Invalid filter value: %@", @(valueError.message.c_str())]; + return {}; + } + mbgl::style::Filter filter = std::move(*value); - return filter; + return filter; } -+ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter -{ - if (filter.expression) { - id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression); - return [NSPredicate predicateWithMLNJSONObject:jsonObject]; - } else { - return nil; - } ++ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter { + if (filter.expression) { + id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression); + return [NSPredicate predicateWithMLNJSONObject:jsonObject]; + } else { + return nil; + } } @end @@ -39,156 +38,173 @@ @implementation NSPredicate (MLNAdditions) NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) { - NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count]; - for (id object in objects) { - NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object]; - [subpredicates addObject:predicate]; - } - return subpredicates; + NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count]; + for (id object in objects) { + NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object]; + [subpredicates addObject:predicate]; + } + return subpredicates; } -static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{ - @"==": @(NSEqualToPredicateOperatorType), - @"!=": @(NSNotEqualToPredicateOperatorType), - @"<": @(NSLessThanPredicateOperatorType), - @"<=": @(NSLessThanOrEqualToPredicateOperatorType), - @">": @(NSGreaterThanPredicateOperatorType), - @">=": @(NSGreaterThanOrEqualToPredicateOperatorType), - @"in": @(NSInPredicateOperatorType), +static NSDictionary *const MLNPredicateOperatorTypesByJSONOperator = @{ + @"==" : @(NSEqualToPredicateOperatorType), + @"!=" : @(NSNotEqualToPredicateOperatorType), + @"<" : @(NSLessThanPredicateOperatorType), + @"<=" : @(NSLessThanOrEqualToPredicateOperatorType), + @">" : @(NSGreaterThanPredicateOperatorType), + @">=" : @(NSGreaterThanOrEqualToPredicateOperatorType), + @"in" : @(NSInPredicateOperatorType), }; + (instancetype)predicateWithMLNJSONObject:(id)object { - if ([object isEqual:@YES]) { - return [NSPredicate predicateWithValue:YES]; - } - if ([object isEqual:@NO]) { - return [NSPredicate predicateWithValue:NO]; - } + if ([object isEqual:@YES]) { + return [NSPredicate predicateWithValue:YES]; + } + if ([object isEqual:@NO]) { + return [NSPredicate predicateWithValue:NO]; + } - MLNAssert([object isKindOfClass:[NSArray class]], @"Condition for case expression should be an expression."); - NSArray *objects = (NSArray *)object; - NSString *op = objects.firstObject; + MLNAssert([object isKindOfClass:[NSArray class]], + @"Condition for case expression should be an expression."); + NSArray *objects = (NSArray *)object; + NSString *op = objects.firstObject; - NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op]; - if (operatorTypeNumber) { - NSPredicateOperatorType operatorType = (NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue]; + NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op]; + if (operatorTypeNumber) { + NSPredicateOperatorType operatorType = + (NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue]; - NSComparisonPredicateOptions options = 0; - if (objects.count > 3) { - NSArray *collatorExpression = objects[3]; - MLNCAssert([collatorExpression isKindOfClass:[NSArray class]], @"Collators must be dictionaries."); - MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression"); - NSDictionary *collator = collatorExpression[1]; - MLNCAssert([collator isKindOfClass:[NSDictionary class]], @"Malformed collator in collator expression"); + NSComparisonPredicateOptions options = 0; + if (objects.count > 3) { + NSArray *collatorExpression = objects[3]; + MLNCAssert([collatorExpression isKindOfClass:[NSArray class]], + @"Collators must be dictionaries."); + MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression"); + NSDictionary *collator = collatorExpression[1]; + MLNCAssert([collator isKindOfClass:[NSDictionary class]], + @"Malformed collator in collator expression"); - // Predicate options can’t express specific locales as collators can. - if (!collator[@"locale"]) { - if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) { - options |= NSCaseInsensitivePredicateOption; - } - if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) { - options |= NSDiacriticInsensitivePredicateOption; - } - } + // Predicate options can’t express specific locales as collators can. + if (!collator[@"locale"]) { + if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) { + options |= NSCaseInsensitivePredicateOption; } - - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); - return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0] - rightExpression:subexpressions[1] - modifier:NSDirectPredicateModifier - type:operatorType - options:options]; - } - - if ([op isEqualToString:@"!"]) { - NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); - if (subpredicates.count > 1) { - NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; - return [NSCompoundPredicate notPredicateWithSubpredicate:predicate]; + if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) { + options |= NSDiacriticInsensitivePredicateOption; } - if (subpredicates.count) { - return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject]; - } - return [NSPredicate predicateWithValue:YES]; - } - if ([op isEqualToString:@"all"]) { - NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); - if (subpredicates.count == 2) { - // Determine if the expression is of BETWEEN type - if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] && - [subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) { - NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0]; - NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1]; - - NSArray *limits; - NSExpression *leftConditionExpression; - - if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType && - rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) { - limits = @[leftCondition.rightExpression, rightCondition.rightExpression]; - leftConditionExpression = leftCondition.leftExpression; - - } else if (leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType && - rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) { - limits = @[leftCondition.leftExpression, rightCondition.rightExpression]; - leftConditionExpression = leftCondition.rightExpression; - - } else if(leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType && - rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) { - limits = @[leftCondition.leftExpression, rightCondition.leftExpression]; - leftConditionExpression = leftCondition.rightExpression; - - } else if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType && - rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) { - limits = @[leftCondition.rightExpression, rightCondition.leftExpression]; - leftConditionExpression = leftCondition.leftExpression; - } - - if (limits && leftConditionExpression) { - return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression, [NSExpression expressionForAggregate:limits]]; - } - } - } - return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; - } - if ([op isEqualToString:@"any"]) { - NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); - return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; - } - if ([op isEqualToString:@"within"]) { - NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); - return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject] - rightExpression:subexpressions[0] - modifier:NSDirectPredicateModifier - type:NSInPredicateOperatorType - options:0]; + } } - NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; - return [NSComparisonPredicate predicateWithLeftExpression:expression - rightExpression:[NSExpression expressionForConstantValue:@YES] + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects( + [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0] + rightExpression:subexpressions[1] modifier:NSDirectPredicateModifier - type:NSEqualToPredicateOperatorType - options:0]; + type:operatorType + options:options]; + } + if ([op isEqualToString:@"!"]) { + NSArray *subpredicates = MLNSubpredicatesWithJSONObjects( + [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + if (subpredicates.count > 1) { + NSCompoundPredicate *predicate = + [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; + return [NSCompoundPredicate notPredicateWithSubpredicate:predicate]; + } + if (subpredicates.count) { + return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject]; + } + return [NSPredicate predicateWithValue:YES]; + } + if ([op isEqualToString:@"all"]) { + NSArray *subpredicates = MLNSubpredicatesWithJSONObjects( + [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + if (subpredicates.count == 2) { + // Determine if the expression is of BETWEEN type + if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] && + [subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) { + NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0]; + NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1]; + + NSArray *limits; + NSExpression *leftConditionExpression; + + if (leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType && + rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) { + limits = @[ leftCondition.rightExpression, rightCondition.rightExpression ]; + leftConditionExpression = leftCondition.leftExpression; + + } else if (leftCondition.predicateOperatorType == + NSLessThanOrEqualToPredicateOperatorType && + rightCondition.predicateOperatorType == + NSLessThanOrEqualToPredicateOperatorType) { + limits = @[ leftCondition.leftExpression, rightCondition.rightExpression ]; + leftConditionExpression = leftCondition.rightExpression; + + } else if (leftCondition.predicateOperatorType == + NSLessThanOrEqualToPredicateOperatorType && + rightCondition.predicateOperatorType == + NSGreaterThanOrEqualToPredicateOperatorType) { + limits = @[ leftCondition.leftExpression, rightCondition.leftExpression ]; + leftConditionExpression = leftCondition.rightExpression; + + } else if (leftCondition.predicateOperatorType == + NSGreaterThanOrEqualToPredicateOperatorType && + rightCondition.predicateOperatorType == + NSGreaterThanOrEqualToPredicateOperatorType) { + limits = @[ leftCondition.rightExpression, rightCondition.leftExpression ]; + leftConditionExpression = leftCondition.leftExpression; + } + + if (limits && leftConditionExpression) { + return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression, + [NSExpression expressionForAggregate:limits]]; + } + } + } + return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; + } + if ([op isEqualToString:@"any"]) { + NSArray *subpredicates = MLNSubpredicatesWithJSONObjects( + [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; + } + if ([op isEqualToString:@"within"]) { + NSArray *subexpressions = MLNSubexpressionsWithJSONObjects( + [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); + return [NSComparisonPredicate + predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject] + rightExpression:subexpressions[0] + modifier:NSDirectPredicateModifier + type:NSInPredicateOperatorType + options:0]; + } + + NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; + return [NSComparisonPredicate + predicateWithLeftExpression:expression + rightExpression:[NSExpression expressionForConstantValue:@YES] + modifier:NSDirectPredicateModifier + type:NSEqualToPredicateOperatorType + options:0]; } - (id)mgl_jsonExpressionObject { - if ([self isEqual:[NSPredicate predicateWithValue:YES]]) { - return @YES; - } - if ([self isEqual:[NSPredicate predicateWithValue:NO]]) { - return @NO; - } - - if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) { - [NSException raise:NSInvalidArgumentException - format:@"Block-based predicates are not supported."]; - } + if ([self isEqual:[NSPredicate predicateWithValue:YES]]) { + return @YES; + } + if ([self isEqual:[NSPredicate predicateWithValue:NO]]) { + return @NO; + } + if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) { [NSException raise:NSInvalidArgumentException - format:@"Unrecognized predicate type."]; - return nil; + format:@"Block-based predicates are not supported."]; + } + + [NSException raise:NSInvalidArgumentException format:@"Unrecognized predicate type."]; + return nil; } @end @@ -196,34 +212,33 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{ @implementation NSPredicate (MLNExpressionAdditions) - (id)mgl_if:(id)firstValue, ... { + if ([self evaluateWithObject:nil]) { + return firstValue; + } - if ([self evaluateWithObject:nil]) { - return firstValue; - } + id eachExpression; + va_list argumentList; + va_start(argumentList, firstValue); - id eachExpression; - va_list argumentList; - va_start(argumentList, firstValue); + while ((eachExpression = va_arg(argumentList, id))) { + if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { + id valueExpression = va_arg(argumentList, id); + if ([eachExpression evaluateWithObject:nil]) { + return valueExpression; + } + } else { + return eachExpression; + } + } + va_end(argumentList); - while ((eachExpression = va_arg(argumentList, id))) { - if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) { - id valueExpression = va_arg(argumentList, id); - if ([eachExpression evaluateWithObject:nil]) { - return valueExpression; - } - } else { - return eachExpression; - } - } - va_end(argumentList); - - return nil; + return nil; } - (id)mgl_match:(NSExpression *)firstCase, ... { - [NSException raise:NSInvalidArgumentException - format:@"Match expressions lack underlying Objective-C implementations."]; - return nil; + [NSException raise:NSInvalidArgumentException + format:@"Match expressions lack underlying Objective-C implementations."]; + return nil; } @end diff --git a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm index 5a8151102..0082846de 100644 --- a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm +++ b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm @@ -1,76 +1,73 @@ -#import "NSValue+MLNStyleAttributeAdditions.h" +#import "MLNGeometry_Private.h" #import "MLNLight.h" #import "MLNLoggingConfiguration_Private.h" -#import "MLNGeometry_Private.h" +#import "NSValue+MLNStyleAttributeAdditions.h" #if TARGET_OS_IPHONE - #import +#import #endif @implementation NSValue (MLNStyleAttributeAdditions) -+ (instancetype)mgl_valueWithOffsetArray:(std::array)offsetArray -{ - CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]); ++ (instancetype)mgl_valueWithOffsetArray:(std::array)offsetArray { + CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]); #if !TARGET_OS_IPHONE - // Style specification assumes an origin at the upper-left corner. - // macOS defines an origin at the lower-left corner. - vector.dy *= -1; + // Style specification assumes an origin at the upper-left corner. + // macOS defines an origin at the lower-left corner. + vector.dy *= -1; #endif - return [NSValue value:&vector withObjCType:@encode(CGVector)]; + return [NSValue value:&vector withObjCType:@encode(CGVector)]; } -+ (instancetype)mgl_valueWithPaddingArray:(std::array)paddingArray -{ - // Style specification defines padding in clockwise order: top, right, bottom, left. - // Foundation defines padding in counterclockwise order: top, left, bottom, right. - MLNEdgeInsets insets = { - .top = paddingArray[0], - .right = paddingArray[1], - .bottom = paddingArray[2], - .left = paddingArray[3], - }; - return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)]; ++ (instancetype)mgl_valueWithPaddingArray:(std::array)paddingArray { + // Style specification defines padding in clockwise order: top, right, bottom, left. + // Foundation defines padding in counterclockwise order: top, left, bottom, right. + MLNEdgeInsets insets = { + .top = paddingArray[0], + .right = paddingArray[1], + .bottom = paddingArray[2], + .left = paddingArray[3], + }; + return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)]; } -- (std::array)mgl_offsetArrayValue -{ - MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector"); - CGVector vector; - [self getValue:&vector]; +- (std::array)mgl_offsetArrayValue { + MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector"); + CGVector vector; + [self getValue:&vector]; #if !TARGET_OS_IPHONE - vector.dy *= -1; + vector.dy *= -1; #endif - return { - static_cast(vector.dx), - static_cast(vector.dy), - }; + return { + static_cast(vector.dx), + static_cast(vector.dy), + }; } -- (std::array)mgl_paddingArrayValue -{ - MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0, @"Value does not represent an NSEdgeInsets/UIEdgeInsets"); - MLNEdgeInsets insets; - [self getValue:&insets]; - // Style specification defines padding in clockwise order: top, right, bottom, left. - return { - static_cast(insets.top), - static_cast(insets.right), - static_cast(insets.bottom), - static_cast(insets.left), - }; +- (std::array)mgl_paddingArrayValue { + MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0, + @"Value does not represent an NSEdgeInsets/UIEdgeInsets"); + MLNEdgeInsets insets; + [self getValue:&insets]; + // Style specification defines padding in clockwise order: top, right, bottom, left. + return { + static_cast(insets.top), + static_cast(insets.right), + static_cast(insets.bottom), + static_cast(insets.left), + }; } -- (std::array)mgl_lightPositionArrayValue -{ - MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, @"Value does not represent an MLNSphericalPosition"); - MLNSphericalPosition lightPosition; - [self getValue:&lightPosition]; - // Style specification defines padding in clockwise order: top, right, bottom, left. - return { - static_cast(lightPosition.radial), - static_cast(lightPosition.azimuthal), - static_cast(lightPosition.polar), - }; +- (std::array)mgl_lightPositionArrayValue { + MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, + @"Value does not represent an MLNSphericalPosition"); + MLNSphericalPosition lightPosition; + [self getValue:&lightPosition]; + // Style specification defines padding in clockwise order: top, right, bottom, left. + return { + static_cast(lightPosition.radial), + static_cast(lightPosition.azimuthal), + static_cast(lightPosition.polar), + }; } @end diff --git a/platform/darwin/src/headless_backend_cgl.mm b/platform/darwin/src/headless_backend_cgl.mm index b4c66076f..0403d20a4 100644 --- a/platform/darwin/src/headless_backend_cgl.mm +++ b/platform/darwin/src/headless_backend_cgl.mm @@ -1,12 +1,12 @@ #include #include -#include -#include #include +#include +#include -#include #include +#include namespace mbgl { namespace gl { @@ -15,113 +15,113 @@ namespace gl { // instantiating new headless rendering contexts. class CGLDisplayConfig { private: - // Key for singleton construction. - struct Key { explicit Key() = default; }; + // Key for singleton construction. + struct Key { + explicit Key() = default; + }; public: - CGLDisplayConfig(Key) { - // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported - // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. - CGLPixelFormatAttribute attributes[] = { - kCGLPFAOpenGLProfile, static_cast(kCGLOGLPVersion_Legacy), - // Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when running in VMs. - kCGLPFAClosestPolicy, - kCGLPFAAccumSize, static_cast(32), - kCGLPFAColorSize, static_cast(24), - kCGLPFAAlphaSize, static_cast(8), - kCGLPFADepthSize, static_cast(16), - kCGLPFAStencilSize, static_cast(8), - kCGLPFASupportsAutomaticGraphicsSwitching, - kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU - static_cast(0) - }; + CGLDisplayConfig(Key) { + // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported + // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. + CGLPixelFormatAttribute attributes[] = { + kCGLPFAOpenGLProfile, static_cast(kCGLOGLPVersion_Legacy), + // Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when + // running in VMs. + kCGLPFAClosestPolicy, kCGLPFAAccumSize, static_cast(32), + kCGLPFAColorSize, static_cast(24), kCGLPFAAlphaSize, + static_cast(8), kCGLPFADepthSize, + static_cast(16), kCGLPFAStencilSize, + static_cast(8), kCGLPFASupportsAutomaticGraphicsSwitching, + kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU + static_cast(0)}; - GLint num; - // TODO: obtain all configurations and choose the best one. - const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + "\n"); - } - if (num <= 0) { - throw std::runtime_error("No pixel formats found."); - } + GLint num; + // TODO: obtain all configurations and choose the best one. + const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); + if (error != kCGLNoError) { + throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + + "\n"); } + if (num <= 0) { + throw std::runtime_error("No pixel formats found."); + } + } - ~CGLDisplayConfig() { - const CGLError error = CGLDestroyPixelFormat(pixelFormat); - if (error != kCGLNoError) { - Log::Error(Event::OpenGL, std::string("Error destroying pixel format:") + CGLErrorString(error)); - } + ~CGLDisplayConfig() { + const CGLError error = CGLDestroyPixelFormat(pixelFormat); + if (error != kCGLNoError) { + Log::Error(Event::OpenGL, + std::string("Error destroying pixel format:") + CGLErrorString(error)); } + } - static std::shared_ptr create() { - static std::weak_ptr instance; - auto shared = instance.lock(); - if (!shared) { - instance = shared = std::make_shared(Key{}); - } - return shared; + static std::shared_ptr create() { + static std::weak_ptr instance; + auto shared = instance.lock(); + if (!shared) { + instance = shared = std::make_shared(Key{}); } + return shared; + } public: - CGLPixelFormatObj pixelFormat = nullptr; + CGLPixelFormatObj pixelFormat = nullptr; }; class CGLBackendImpl final : public HeadlessBackend::Impl { public: - CGLBackendImpl() { - CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Error creating GL context object:") + - CGLErrorString(error) + "\n"); - } - - error = CGLEnable(glContext, kCGLCEMPEngine); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") + - CGLErrorString(error) + "\n"); - } + CGLBackendImpl() { + CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext); + if (error != kCGLNoError) { + throw std::runtime_error(std::string("Error creating GL context object:") + + CGLErrorString(error) + "\n"); } - ~CGLBackendImpl() final { - CGLDestroyContext(glContext); + error = CGLEnable(glContext, kCGLCEMPEngine); + if (error != kCGLNoError) { + throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") + + CGLErrorString(error) + "\n"); + } + } + + ~CGLBackendImpl() final { CGLDestroyContext(glContext); } + + gl::ProcAddress getExtensionFunctionPointer(const char* name) final { + static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + if (!framework) { + throw std::runtime_error("Failed to load OpenGL framework."); } - gl::ProcAddress getExtensionFunctionPointer(const char* name) final { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } + return reinterpret_cast(CFBundleGetFunctionPointerForName( + framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); + } - return reinterpret_cast(CFBundleGetFunctionPointerForName( - framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); + void activateContext() final { + CGLError error = CGLSetCurrentContext(glContext); + if (error != kCGLNoError) { + throw std::runtime_error(std::string("Switching OpenGL context failed:") + + CGLErrorString(error) + "\n"); } + } - void activateContext() final { - CGLError error = CGLSetCurrentContext(glContext); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Switching OpenGL context failed:") + - CGLErrorString(error) + "\n"); - } - } - - void deactivateContext() final { - CGLError error = CGLSetCurrentContext(nullptr); - if (error != kCGLNoError) { - throw std::runtime_error(std::string("Removing OpenGL context failed:") + - CGLErrorString(error) + "\n"); - } + void deactivateContext() final { + CGLError error = CGLSetCurrentContext(nullptr); + if (error != kCGLNoError) { + throw std::runtime_error(std::string("Removing OpenGL context failed:") + + CGLErrorString(error) + "\n"); } + } private: - const std::shared_ptr cglDisplay = CGLDisplayConfig::create(); - CGLContextObj glContext = nullptr; + const std::shared_ptr cglDisplay = CGLDisplayConfig::create(); + CGLContextObj glContext = nullptr; }; void HeadlessBackend::createImpl() { - assert(!impl); - impl = std::make_unique(); + assert(!impl); + impl = std::make_unique(); } -} // namespace gl -} // namespace mbgl +} // namespace gl +} // namespace mbgl diff --git a/platform/darwin/src/headless_backend_eagl.mm b/platform/darwin/src/headless_backend_eagl.mm index ec34ba322..65a7ba0e3 100644 --- a/platform/darwin/src/headless_backend_eagl.mm +++ b/platform/darwin/src/headless_backend_eagl.mm @@ -9,43 +9,39 @@ namespace gl { class EAGLBackendImpl : public HeadlessBackend::Impl { public: - EAGLBackendImpl() { - glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; - if (glContext == nil) { - throw std::runtime_error("Error creating GL context object"); - } - glContext.multiThreaded = YES; + EAGLBackendImpl() { + glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + if (glContext == nil) { + throw std::runtime_error("Error creating GL context object"); + } + glContext.multiThreaded = YES; + } + + // Required for ARC to deallocate correctly. + ~EAGLBackendImpl() final = default; + + gl::ProcAddress getExtensionFunctionPointer(const char* name) final { + static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); + if (!framework) { + throw std::runtime_error("Failed to load OpenGL framework."); } - // Required for ARC to deallocate correctly. - ~EAGLBackendImpl() final = default; + return reinterpret_cast(CFBundleGetFunctionPointerForName( + framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); + } - gl::ProcAddress getExtensionFunctionPointer(const char* name) final { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } + void activateContext() final { [EAGLContext setCurrentContext:glContext]; } - return reinterpret_cast(CFBundleGetFunctionPointerForName( - framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); - } - - void activateContext() final { - [EAGLContext setCurrentContext:glContext]; - } - - void deactivateContext() final { - [EAGLContext setCurrentContext:nil]; - } + void deactivateContext() final { [EAGLContext setCurrentContext:nil]; } private: - EAGLContext* glContext = nullptr; + EAGLContext* glContext = nullptr; }; void HeadlessBackend::createImpl() { - assert(!impl); - impl = std::make_unique(); + assert(!impl); + impl = std::make_unique(); } -} // namespace gl -} // namespace mbgl +} // namespace gl +} // namespace mbgl diff --git a/platform/darwin/test/MLNCodingTests.mm b/platform/darwin/test/MLNCodingTests.mm index 0979bca77..3e8cfbb20 100644 --- a/platform/darwin/test/MLNCodingTests.mm +++ b/platform/darwin/test/MLNCodingTests.mm @@ -1,8 +1,8 @@ #import #import -#import "MLNFoundation_Private.h" #import "MLNCluster.h" +#import "MLNFoundation_Private.h" #if TARGET_OS_IPHONE #import "MLNUserLocation_Private.h" @@ -14,601 +14,641 @@ @implementation MLNCodingTests - (NSString *)temporaryFilePathForClass:(Class)clazz { - return [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromClass(clazz)]; + return [NSTemporaryDirectory() stringByAppendingPathComponent:NSStringFromClass(clazz)]; } - (void)testPointAnnotation { - MLNPointAnnotation *annotation = [[MLNPointAnnotation alloc] init]; - annotation.coordinate = CLLocationCoordinate2DMake(0.5, 0.5); - annotation.title = @"title"; - annotation.subtitle = @"subtitle"; + MLNPointAnnotation *annotation = [[MLNPointAnnotation alloc] init]; + annotation.coordinate = CLLocationCoordinate2DMake(0.5, 0.5); + annotation.title = @"title"; + annotation.subtitle = @"subtitle"; - NSString *filePath = [self temporaryFilePathForClass:MLNPointAnnotation.class]; - [NSKeyedArchiver archiveRootObject:annotation toFile:filePath]; - MLNPointAnnotation *unarchivedAnnotation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:MLNPointAnnotation.class]; + [NSKeyedArchiver archiveRootObject:annotation toFile:filePath]; + MLNPointAnnotation *unarchivedAnnotation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(annotation, unarchivedAnnotation); + XCTAssertEqualObjects(annotation, unarchivedAnnotation); } - (void)testPointFeature { - MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; - pointFeature.title = @"title"; - pointFeature.subtitle = @"subtitle"; - pointFeature.identifier = @(123); - pointFeature.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; + pointFeature.title = @"title"; + pointFeature.subtitle = @"subtitle"; + pointFeature.identifier = @(123); + pointFeature.attributes = @{@"bbox" : @[ @1, @2, @3, @4 ]}; - NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class]; - [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; - MLNPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class]; + [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; + MLNPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); + XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); } - (void)testPointFeatureCluster { - MLNPointFeature *pointFeature = [[MLNPointFeatureCluster alloc] init]; - pointFeature.title = @"title"; - pointFeature.subtitle = @"subtitle"; - pointFeature.identifier = @(123); - pointFeature.attributes = @{ - @"cluster" : @(YES), - @"cluster_id" : @(456), - @"point_count" : @(2), - }; + MLNPointFeature *pointFeature = [[MLNPointFeatureCluster alloc] init]; + pointFeature.title = @"title"; + pointFeature.subtitle = @"subtitle"; + pointFeature.identifier = @(123); + pointFeature.attributes = @{ + @"cluster" : @(YES), + @"cluster_id" : @(456), + @"point_count" : @(2), + }; - XCTAssert([pointFeature isKindOfClass:[MLNPointFeature class]], @""); + XCTAssert([pointFeature isKindOfClass:[MLNPointFeature class]], @""); - NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class]; - [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; - MLNPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class]; + [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; + MLNPointFeature *unarchivedPointFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); + XCTAssertEqualObjects(pointFeature, unarchivedPointFeature); - // Unarchive process should ensure we still have a cluster - XCTAssert([unarchivedPointFeature isMemberOfClass:[MLNPointFeatureCluster class]]); + // Unarchive process should ensure we still have a cluster + XCTAssert([unarchivedPointFeature isMemberOfClass:[MLNPointFeatureCluster class]]); - id cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(unarchivedPointFeature, MLNCluster); + id cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(unarchivedPointFeature, MLNCluster); - XCTAssert(cluster); - XCTAssert(cluster.clusterIdentifier == 456); - XCTAssert(cluster.clusterPointCount == 2); + XCTAssert(cluster); + XCTAssert(cluster.clusterIdentifier == 456); + XCTAssert(cluster.clusterPointCount == 2); } - - (void)testPolyline { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), - CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392) - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), + CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392)}; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polyline.title = @"title"; - polyline.subtitle = @"subtitle"; + MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates + count:numberOfCoordinates]; + polyline.title = @"title"; + polyline.subtitle = @"subtitle"; - NSString *filePath = [self temporaryFilePathForClass:[MLNPolyline class]]; - [NSKeyedArchiver archiveRootObject:polyline toFile:filePath]; - MLNPolyline *unarchivedPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPolyline class]]; + [NSKeyedArchiver archiveRootObject:polyline toFile:filePath]; + MLNPolyline *unarchivedPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(polyline, unarchivedPolyline); + XCTAssertEqualObjects(polyline, unarchivedPolyline); - CLLocationCoordinate2D otherCoordinates[] = { - CLLocationCoordinate2DMake(-1, -2) - }; + CLLocationCoordinate2D otherCoordinates[] = {CLLocationCoordinate2DMake(-1, -2)}; - [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; + [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; - XCTAssertNotEqualObjects(polyline, unarchivedPolyline); + XCTAssertNotEqualObjects(polyline, unarchivedPolyline); - CLLocationCoordinate2D multiLineCoordinates[] = { - CLLocationCoordinate2DMake(51.000000, 0.000000), - CLLocationCoordinate2DMake(51.000000, 1.000000), - CLLocationCoordinate2DMake(51.000000, 2.000000), - }; + CLLocationCoordinate2D multiLineCoordinates[] = { + CLLocationCoordinate2DMake(51.000000, 0.000000), + CLLocationCoordinate2DMake(51.000000, 1.000000), + CLLocationCoordinate2DMake(51.000000, 2.000000), + }; - NSUInteger multiLineCoordinatesCount = sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D); - MLNPolyline *multiLine = [MLNPolyline polylineWithCoordinates:multiLineCoordinates count:multiLineCoordinatesCount]; - CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000); + NSUInteger multiLineCoordinatesCount = + sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D); + MLNPolyline *multiLine = [MLNPolyline polylineWithCoordinates:multiLineCoordinates + count:multiLineCoordinatesCount]; + CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000); - XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude); - XCTAssertEqual([multiLine coordinate].longitude, multiLineCenter.longitude); + XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude); + XCTAssertEqual([multiLine coordinate].longitude, multiLineCenter.longitude); - CLLocationCoordinate2D segmentCoordinates[] = { - CLLocationCoordinate2DMake(35.040390, -85.311477), - CLLocationCoordinate2DMake(35.040390, -85.209510), - }; + CLLocationCoordinate2D segmentCoordinates[] = { + CLLocationCoordinate2DMake(35.040390, -85.311477), + CLLocationCoordinate2DMake(35.040390, -85.209510), + }; - NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D); - MLNPolyline *segmentLine = [MLNPolyline polylineWithCoordinates:segmentCoordinates count:segmentCoordinatesCount]; - CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935); + NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D); + MLNPolyline *segmentLine = [MLNPolyline 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); + 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), - }; + 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); - MLNPolyline *sfToBerkeleyLine = [MLNPolyline 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); + NSUInteger sfToBerkeleyCoordinatesCount = + sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D); + MLNPolyline *sfToBerkeleyLine = + [MLNPolyline 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) - }; + 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); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - polygon.title = nil; - polygon.subtitle = @"subtitle"; + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygon.title = nil; + polygon.subtitle = @"subtitle"; - NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]]; - [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; - MLNPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - [unarchivedPolygon coordinate]; + MLNPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + [unarchivedPolygon coordinate]; - XCTAssertEqualObjects(polygon, unarchivedPolygon); + 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), - }; + 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); - MLNPolygon *squarePolygon = [MLNPolygon polygonWithCoordinates:squareCoordinates count:squareCoordinatesCount]; - CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); - - XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude); - XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude); + NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D); + MLNPolygon *squarePolygon = [MLNPolygon 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) - }; + CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20)}; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - CLLocationCoordinate2D interiorCoordinates[] = { - CLLocationCoordinate2DMake(4, 4), - CLLocationCoordinate2DMake(6, 6) - }; + CLLocationCoordinate2D interiorCoordinates[] = {CLLocationCoordinate2DMake(4, 4), + CLLocationCoordinate2DMake(6, 6)}; - NSUInteger numberOfInteriorCoordinates = sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfInteriorCoordinates = + sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); - MLNPolygon *interiorPolygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:numberOfInteriorCoordinates]; - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates interiorPolygons:@[interiorPolygon]]; + MLNPolygon *interiorPolygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates + count:numberOfInteriorCoordinates]; + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates + count:numberOfCoordinates + interiorPolygons:@[ interiorPolygon ]]; - NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]]; - [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]]; + [NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; - MLNPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNPolygon *unarchivedPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(polygon, unarchivedPolygon); + XCTAssertEqualObjects(polygon, unarchivedPolygon); } - (void)testPolylineFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 20) - }; + CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20)}; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polylineFeature.attributes = @{@"bbox": @[@0, @1, @2, @3]}; - polylineFeature.identifier = @"identifier"; + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MLNPolylineFeature *polylineFeature = + [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.attributes = @{@"bbox" : @[ @0, @1, @2, @3 ]}; + polylineFeature.identifier = @"identifier"; - NSString *filePath = [self temporaryFilePathForClass:[MLNPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; - MLNPolylineFeature *unarchivedPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNPolylineFeature *unarchivedPolylineFeature = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(polylineFeature, unarchivedPolylineFeature); + XCTAssertEqualObjects(polylineFeature, unarchivedPolylineFeature); - unarchivedPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + unarchivedPolylineFeature.attributes = @{@"bbox" : @[ @4, @3, @2, @1 ]}; - XCTAssertNotEqualObjects(polylineFeature, unarchivedPolylineFeature); + XCTAssertNotEqualObjects(polylineFeature, unarchivedPolylineFeature); } - (void)testPolygonFeature { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 20) - }; + CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 20)}; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + MLNPolygonFeature *polygonFeature = + [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; - NSString *filePath = [self temporaryFilePathForClass:[MLNPolygonFeature class]]; - [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPolygonFeature class]]; + [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; - MLNPolygonFeature *unarchivedPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNPolygonFeature *unarchivedPolygonFeature = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); + XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); - unarchivedPolygonFeature.identifier = @"test"; + unarchivedPolygonFeature.identifier = @"test"; - XCTAssertNotEqualObjects(polygonFeature, unarchivedPolygonFeature); + XCTAssertNotEqualObjects(polygonFeature, unarchivedPolygonFeature); } - (void)testPointCollection { - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(0, 1), - CLLocationCoordinate2DMake(10, 11), - CLLocationCoordinate2DMake(20, 21), - CLLocationCoordinate2DMake(30, 31), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPointCollection *pointCollection = [MLNPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; - CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1); + MLNPointCollection *pointCollection = + [MLNPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; + CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1); - XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude); - XCTAssertEqual([pointCollection coordinate].longitude, pointsCenter.longitude); + XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude); + XCTAssertEqual([pointCollection coordinate].longitude, pointsCenter.longitude); - NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollection class]]; - [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollection class]]; + [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; - MLNPointCollection *unarchivedPointCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNPointCollection *unarchivedPointCollection = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); + XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); } - (void)testPointCollectionFeature { - NSMutableArray *features = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - MLNPointFeature *feature = [[MLNPointFeature alloc] init]; - feature.coordinate = CLLocationCoordinate2DMake(arc4random() % 90, arc4random() % 180); - [features addObject:feature]; - } + NSMutableArray *features = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MLNPointFeature *feature = [[MLNPointFeature 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), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPointCollectionFeature *collection = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; - collection.identifier = @"identifier"; - collection.attributes = @{@"bbox": @[@1, @2, @3, @4]}; + MLNPointCollectionFeature *collection = + [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates + count:numberOfCoordinates]; + collection.identifier = @"identifier"; + collection.attributes = @{@"bbox" : @[ @1, @2, @3, @4 ]}; - NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollectionFeature class]]; - [NSKeyedArchiver archiveRootObject:collection toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:collection toFile:filePath]; - MLNPointCollectionFeature *unarchivedCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNPointCollectionFeature *unarchivedCollection = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(collection, unarchivedCollection); + XCTAssertEqualObjects(collection, unarchivedCollection); - unarchivedCollection.identifier = @"newIdentifier"; + unarchivedCollection.identifier = @"newIdentifier"; - XCTAssertNotEqualObjects(collection, unarchivedCollection); + XCTAssertNotEqualObjects(collection, unarchivedCollection); } - (void)testMultiPolyline { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; - 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 line1[] = { - CLLocationCoordinate2DMake(100, 40), - CLLocationCoordinate2DMake(105, 45), - CLLocationCoordinate2DMake(110, 55) - }; + CLLocationCoordinate2D line2[] = {CLLocationCoordinate2DMake(105, 40), + CLLocationCoordinate2DMake(110, 45), + CLLocationCoordinate2DMake(115, 55)}; - CLLocationCoordinate2D line2[] = { - CLLocationCoordinate2DMake(105, 40), - CLLocationCoordinate2DMake(110, 45), - CLLocationCoordinate2DMake(115, 55) - }; + NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D); + NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D); - NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D); - NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D); + MLNPolyline *road1Polyline = [MLNPolyline polylineWithCoordinates:line1 + count:road1CoordinatesCount]; + MLNPolyline *road2Polyline = [MLNPolyline polylineWithCoordinates:line1 + count:road2CoordinatesCount]; - MLNPolyline *road1Polyline = [MLNPolyline polylineWithCoordinates:line1 count:road1CoordinatesCount]; - MLNPolyline *road2Polyline = [MLNPolyline polylineWithCoordinates:line1 count:road2CoordinatesCount]; + MLNMultiPolyline *roads = + [MLNMultiPolyline multiPolylineWithPolylines:@[ road1Polyline, road2Polyline ]]; + CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40); - MLNMultiPolyline *roads = [MLNMultiPolyline multiPolylineWithPolylines:@[road1Polyline, road2Polyline]]; - CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40); + XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude); + XCTAssertEqual([roads coordinate].longitude, roadCenter.longitude); - XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude); - XCTAssertEqual([roads coordinate].longitude, roadCenter.longitude); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSMutableArray *polylines = [NSMutableArray array]; - NSMutableArray *polylines = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates + count:numberOfCoordinates]; + [polylines addObject:polyline]; + } - for (NSUInteger i = 0; i < 100; i++) { - MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - [polylines addObject:polyline]; - } + MLNMultiPolyline *multiPolyline = [MLNMultiPolyline multiPolylineWithPolylines:polylines]; - MLNMultiPolyline *multiPolyline = [MLNMultiPolyline multiPolylineWithPolylines:polylines]; + NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolyline class]]; + [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; - NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolyline class]]; - [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; + MLNMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNMultiPolyline *anotherMultipolyline = [MLNMultiPolyline + multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count / 2)]]; - MLNMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MLNMultiPolyline *anotherMultipolyline = [MLNMultiPolyline multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; - - XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); - XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); + XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); + XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); } - (void)testMultiPolygon { + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(0, 1), + CLLocationCoordinate2DMake(10, 11), + CLLocationCoordinate2DMake(20, 21), + CLLocationCoordinate2DMake(30, 31), + }; - 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 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), + }; - 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); - NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D); - NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D); + MLNPolygon *innerPolygonSquare = [MLNPolygon polygonWithCoordinates:innerSquare + count:innerCoordinatesCount]; + MLNPolygon *outerPolygonSquare = [MLNPolygon polygonWithCoordinates:outerSquare + count:outerCoordinatesCount + interiorPolygons:@[ innerPolygonSquare ]]; + MLNMultiPolygon *squares = + [MLNMultiPolygon multiPolygonWithPolygons:@[ outerPolygonSquare, innerPolygonSquare ]]; + CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); - MLNPolygon *innerPolygonSquare = [MLNPolygon polygonWithCoordinates:innerSquare count:innerCoordinatesCount]; - MLNPolygon *outerPolygonSquare = [MLNPolygon polygonWithCoordinates:outerSquare count:outerCoordinatesCount interiorPolygons:@[innerPolygonSquare]]; - MLNMultiPolygon *squares = [MLNMultiPolygon multiPolygonWithPolygons:@[outerPolygonSquare, innerPolygonSquare]]; - CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); + XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude); + XCTAssertEqual([squares coordinate].longitude, squareCenter.longitude); - XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude); - XCTAssertEqual([squares coordinate].longitude, squareCenter.longitude); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSMutableArray *polygons = [NSMutableArray array]; - NSMutableArray *polygons = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + [polygons addObject:polygon]; + } - for (NSUInteger i = 0; i < 100; i++) { - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - [polygons addObject:polygon]; - } + MLNMultiPolygon *multiPolygon = [MLNMultiPolygon multiPolygonWithPolygons:polygons]; - MLNMultiPolygon *multiPolygon = [MLNMultiPolygon multiPolygonWithPolygons:polygons]; + NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolygon class]]; + [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; - NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolygon class]]; - [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; - - MLNMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MLNMultiPolygon *anotherMultiPolygon = [MLNMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; - - XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon); - XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon); + MLNMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNMultiPolygon *anotherMultiPolygon = [MLNMultiPolygon + 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), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; + MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates + count:numberOfCoordinates]; + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates]; - MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:@[polyline, polygon]]; + MLNShapeCollection *shapeCollection = + [MLNShapeCollection shapeCollectionWithShapes:@[ polyline, polygon ]]; - NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollection class]]; - [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollection class]]; + [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; - MLNShapeCollection *unarchivedShapeCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MLNShapeCollection *anotherShapeCollection = [MLNShapeCollection shapeCollectionWithShapes:@[polygon]]; + MLNShapeCollection *unarchivedShapeCollection = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNShapeCollection *anotherShapeCollection = + [MLNShapeCollection shapeCollectionWithShapes:@[ polygon ]]; - XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection); - XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection); + 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), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - NSMutableArray *polylines = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++) { - MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - polylineFeature.identifier = @(arc4random() % 100).stringValue; - [polylines addObject:polylineFeature]; - } + NSMutableArray *polylines = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MLNPolylineFeature *polylineFeature = + [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; + polylineFeature.identifier = @(arc4random() % 100).stringValue; + [polylines addObject:polylineFeature]; + } - MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:polylines]; - multiPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; + MLNMultiPolylineFeature *multiPolylineFeature = + [MLNMultiPolylineFeature multiPolylineWithPolylines:polylines]; + multiPolylineFeature.attributes = @{@"bbox" : @[ @4, @3, @2, @1 ]}; - NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; - MLNMultiPolylineFeature *unarchivedMultiPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MLNMultiPolylineFeature *anotherMultiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; + MLNMultiPolylineFeature *unarchivedMultiPolylineFeature = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNMultiPolylineFeature *anotherMultiPolylineFeature = [MLNMultiPolylineFeature + multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count / 2)]]; - XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature); - XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature); + 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), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451185), + CLLocationCoordinate2DMake(20.88471238, 21.93684215), + CLLocationCoordinate2DMake(30.15697236, 31.32988123), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - NSMutableArray *polygons = [NSMutableArray array]; - for (NSUInteger i = 0; i < 100; i++ ) { - MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; - polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; - [polygons addObject:polygonFeature]; - } + NSMutableArray *polygons = [NSMutableArray array]; + for (NSUInteger i = 0; i < 100; i++) { + MLNPolygonFeature *polygonFeature = + [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; + [polygons addObject:polygonFeature]; + } - MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:polygons]; - multiPolygonFeature.attributes = @{@"bbox": @[@(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100))]}; + MLNMultiPolygonFeature *multiPolygonFeature = + [MLNMultiPolygonFeature multiPolygonWithPolygons:polygons]; + multiPolygonFeature.attributes = @{ + @"bbox" : @[ + @(arc4random_uniform(100)), @(arc4random_uniform(100)), @(arc4random_uniform(100)), + @(arc4random_uniform(100)) + ] + }; - NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; - [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; + [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; - MLNMultiPolygonFeature *unarchivedMultiPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - MLNMultiPolygonFeature *anotherMultiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; + MLNMultiPolygonFeature *unarchivedMultiPolygonFeature = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNMultiPolygonFeature *anotherMultiPolygonFeature = [MLNMultiPolygonFeature + multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count / 2)]]; - XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature); - XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature); + 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), - }; + CLLocationCoordinate2D coordinates[] = { + CLLocationCoordinate2DMake(10.12315786, 11.23451186), + CLLocationCoordinate2DMake(20.91836515, 21.93689215), + CLLocationCoordinate2DMake(30.55697246, 31.33988123), + }; - NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); + NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); - MLNPolylineFeature *polyline = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; - MLNPolygonFeature *polygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; + MLNPolylineFeature *polyline = [MLNPolylineFeature polylineWithCoordinates:coordinates + count:numberOfCoordinates]; + MLNPolygonFeature *polygon = [MLNPolygonFeature polygonWithCoordinates:coordinates + count:numberOfCoordinates]; - MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[polyline, polygon]]; - shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue; - shapeCollectionFeature.attributes = @{@"bbox":@[@(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100)), - @(arc4random_uniform(100))]}; + MLNShapeCollectionFeature *shapeCollectionFeature = + [MLNShapeCollectionFeature 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:[MLNShapeCollectionFeature class]]; - [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollectionFeature class]]; + [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; - MLNShapeCollectionFeature *unarchivedShapeCollectionFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNShapeCollectionFeature *unarchivedShapeCollectionFeature = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature); + 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(); + 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]; + NSImage *image = [[NSImage alloc] initWithSize:CGSizeMake(10, 10)]; + [image lockFocus]; + [[NSColor redColor] drawSwatchInRect:CGRectMake(0, 0, 10, 10)]; + [image unlockFocus]; #endif - MLNAnnotationImage *annotationImage = [MLNAnnotationImage annotationImageWithImage:image reuseIdentifier:@(arc4random_uniform(100)).stringValue]; + MLNAnnotationImage *annotationImage = + [MLNAnnotationImage annotationImageWithImage:image + reuseIdentifier:@(arc4random_uniform(100)).stringValue]; - NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationImage class]]; - [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationImage class]]; + [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; - MLNAnnotationImage *unarchivedAnnotationImage = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNAnnotationImage *unarchivedAnnotationImage = + [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); + XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); } #if TARGET_OS_IPHONE - (void)testAnnotationView { - MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithReuseIdentifier:@"id"]; - annotationView.enabled = NO; - annotationView.selected = YES; - annotationView.draggable = YES; - annotationView.centerOffset = CGVectorMake(10, 10); - annotationView.scalesWithViewingDistance = NO; + MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithReuseIdentifier:@"id"]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; - NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationView class]]; - [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; - MLNAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNAnnotationView *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); + 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 { - MLNUserLocation *userLocation = [[MLNUserLocation alloc] init]; - userLocation.location = [[CLLocation alloc] initWithLatitude:1 longitude:1]; + MLNUserLocation *userLocation = [[MLNUserLocation alloc] init]; + userLocation.location = [[CLLocation alloc] initWithLatitude:1 longitude:1]; - NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocation class]]; - [NSKeyedArchiver archiveRootObject:userLocation toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocation class]]; + [NSKeyedArchiver archiveRootObject:userLocation toFile:filePath]; - MLNUserLocation *unarchivedUserLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNUserLocation *unarchivedUserLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; - XCTAssertEqualObjects(userLocation, unarchivedUserLocation); - unarchivedUserLocation.location = [[CLLocation alloc] initWithLatitude:10 longitude:10]; - XCTAssertNotEqualObjects(userLocation, unarchivedUserLocation); + XCTAssertEqualObjects(userLocation, unarchivedUserLocation); + unarchivedUserLocation.location = [[CLLocation alloc] initWithLatitude:10 longitude:10]; + XCTAssertNotEqualObjects(userLocation, unarchivedUserLocation); } #endif #if TARGET_OS_IPHONE - (void)testUserLocationAnnotationView { - MLNUserLocationAnnotationView *annotationView = [[MLNUserLocationAnnotationView alloc] init]; - annotationView.enabled = NO; - annotationView.selected = YES; - annotationView.draggable = YES; - annotationView.centerOffset = CGVectorMake(10, 10); - annotationView.scalesWithViewingDistance = NO; + MLNUserLocationAnnotationView *annotationView = [[MLNUserLocationAnnotationView alloc] init]; + annotationView.enabled = NO; + annotationView.selected = YES; + annotationView.draggable = YES; + annotationView.centerOffset = CGVectorMake(10, 10); + annotationView.scalesWithViewingDistance = NO; - NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocationAnnotationView class]]; - [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; + NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocationAnnotationView class]]; + [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; - MLNUserLocationAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; + MLNUserLocationAnnotationView *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); + 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 diff --git a/platform/darwin/test/MLNExpressionTests.mm b/platform/darwin/test/MLNExpressionTests.mm index de86cbd29..4f3d35203 100644 --- a/platform/darwin/test/MLNExpressionTests.mm +++ b/platform/darwin/test/MLNExpressionTests.mm @@ -14,26 +14,27 @@ #endif #import "MLNAttributedExpression.h" -#define MLNAssertEqualValues(actual, expected, ...) \ - XCTAssertTrue(actual.is<__typeof__(expected)>()); \ - if (actual.is<__typeof__(expected)>()) { \ - XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \ - } +#define MLNAssertEqualValues(actual, expected, ...) \ + XCTAssertTrue(actual.is<__typeof__(expected)>()); \ + if (actual.is<__typeof__(expected)>()) { \ + XCTAssertEqual(actual.get<__typeof__(expected)>(), expected, __VA_ARGS__); \ + } -#define MLNAssertEqualValuesWithAccuracy(actual, expected, accuracy, ...) \ - XCTAssertTrue(actual.is<__typeof__(expected)>()); \ - if (actual.is<__typeof__(expected)>()) { \ - XCTAssertEqualWithAccuracy(actual.get<__typeof__(expected)>(), expected, accuracy, __VA_ARGS__); \ - } +#define MLNAssertEqualValuesWithAccuracy(actual, expected, accuracy, ...) \ + XCTAssertTrue(actual.is<__typeof__(expected)>()); \ + if (actual.is<__typeof__(expected)>()) { \ + XCTAssertEqualWithAccuracy(actual.get<__typeof__(expected)>(), expected, accuracy, \ + __VA_ARGS__); \ + } -#define MLNConstantExpression(constant) \ - [NSExpression expressionForConstantValue:constant] +#define MLNConstantExpression(constant) [NSExpression expressionForConstantValue:constant] #define MLNAssertConstantEqualsValue(constant, value, ...) \ - MLNAssertEqualValues(MLNConstantExpression(constant).mgl_constantMBGLValue, value, __VA_ARGS__); + MLNAssertEqualValues(MLNConstantExpression(constant).mgl_constantMBGLValue, value, __VA_ARGS__); -#define MLNAssertConstantEqualsValueWithAccuracy(constant, value, accuracy, ...) \ - MLNAssertEqualValuesWithAccuracy(MLNConstantExpression(constant).mgl_constantMBGLValue, value, accuracy, __VA_ARGS__); +#define MLNAssertConstantEqualsValueWithAccuracy(constant, value, accuracy, ...) \ + MLNAssertEqualValuesWithAccuracy(MLNConstantExpression(constant).mgl_constantMBGLValue, value, \ + accuracy, __VA_ARGS__); using namespace std::string_literals; @@ -45,1517 +46,1901 @@ using namespace std::string_literals; // MARK: - Utility -- (NSComparisonPredicate *)equalityComparisonPredicateWithRightConstantValue:(id)rightConstantValue -{ - NSComparisonPredicate *predicate = [NSComparisonPredicate - predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"foo"] - rightExpression:[NSExpression expressionForConstantValue:rightConstantValue] - modifier:NSDirectPredicateModifier - type:NSEqualToPredicateOperatorType - options:0]; - return predicate; +- (NSComparisonPredicate *)equalityComparisonPredicateWithRightConstantValue: + (id)rightConstantValue { + NSComparisonPredicate *predicate = [NSComparisonPredicate + predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"foo"] + rightExpression:[NSExpression expressionForConstantValue:rightConstantValue] + modifier:NSDirectPredicateModifier + type:NSEqualToPredicateOperatorType + options:0]; + return predicate; } // MARK: - Objective-C Example Code for MLN Expressions - (void)testSteppingExpression { - //#-example-code - // Objective-C sample of how to create a stepping expression with multiple stops. - NSNumber *initialValue = @4.0f; - NSDictionary *stops = @{@11.0f: initialValue, - @14.0f: @6.0f, - @20.0f: @18.0f }; + // #-example-code + // Objective-C sample of how to create a stepping expression with multiple stops. + NSNumber *initialValue = @4.0f; + NSDictionary *stops = @{@11.0f : initialValue, @14.0f : @6.0f, @20.0f : @18.0f}; - NSExpression *steppingExpression = [ - NSExpression mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression - fromExpression:[NSExpression expressionForConstantValue:initialValue] - stops:[NSExpression expressionForConstantValue:stops] - ]; - //#-end-example-code + NSExpression *steppingExpression = [NSExpression + mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression + fromExpression:[NSExpression expressionForConstantValue:initialValue] + stops:[NSExpression expressionForConstantValue:stops]]; + // #-end-example-code - XCTAssertNotNil(steppingExpression); + XCTAssertNotNil(steppingExpression); } - (void)testSteppingExpression_ColorOverZoom { - //#-example-code - // Objective-C sample of how to create a stepping expression with color. - NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; - NSExpression *stops = [NSExpression expressionForConstantValue:@{@18: constantExpression}]; - NSExpression *functionExpression; + // #-example-code + // Objective-C sample of how to create a stepping expression with color. + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; + NSExpression *stops = [NSExpression expressionForConstantValue:@{@18 : constantExpression}]; + NSExpression *functionExpression; - if (@available(iOS 15, *)) { - // How to create expressions with iOS 15 - functionExpression = [NSExpression - mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression - fromExpression:constantExpression - stops:stops]; - } else { - // How to create expressions up to iOS 14 - functionExpression = [NSExpression - expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", - constantExpression, - stops]; - } - //#-end-example-code + if (@available(iOS 15, *)) { + // How to create expressions with iOS 15 + functionExpression = + [NSExpression mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression + fromExpression:constantExpression + stops:stops]; + } else { + // How to create expressions up to iOS 14 + functionExpression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", + constantExpression, stops]; + } + // #-end-example-code - XCTAssertNotNil(functionExpression); - NSLog(@"%s %@", __FUNCTION__, functionExpression); + XCTAssertNotNil(functionExpression); + NSLog(@"%s %@", __FUNCTION__, functionExpression); } - (void)testInterpolatingExpression { - //#-example-code - // Objective-C sample of how to create an interpolating expression with multiple stops. + // #-example-code + // Objective-C sample of how to create an interpolating expression with multiple stops. - NSDictionary *opacityStops = @{@5.0f: @0.0f, - @14.0: @0.7f, - @20.0f: @1.0f }; - NSExpression *stops = [NSExpression expressionForConstantValue:opacityStops]; - NSExpression *functionExpression; + NSDictionary *opacityStops = @{@5.0f : @0.0f, @14.0 : @0.7f, @20.0f : @1.0f}; + NSExpression *stops = [NSExpression expressionForConstantValue:opacityStops]; + NSExpression *functionExpression; - if (@available(iOS 15, *)) { - // How to create expressions with iOS 15 - functionExpression = [NSExpression - mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression - withCurveType:MLNExpressionInterpolationModeLinear - parameters:nil - stops:stops]; - } else { - // How to create expressions up to iOS 14 - functionExpression = [NSExpression - expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", - stops]; - } - //#-end-example-code + if (@available(iOS 15, *)) { + // How to create expressions with iOS 15 + functionExpression = [NSExpression + mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression + withCurveType:MLNExpressionInterpolationModeLinear + parameters:nil + stops:stops]; + } else { + // How to create expressions up to iOS 14 + functionExpression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + stops]; + } + // #-end-example-code - XCTAssertNotNil(functionExpression); - NSLog(@"%s %@", __FUNCTION__, functionExpression); + XCTAssertNotNil(functionExpression); + NSLog(@"%s %@", __FUNCTION__, functionExpression); } // MARK: - Valuation tests - (void)testStringValuation { - MLNAssertConstantEqualsValue(@"bar", "bar"s, @"NSString should convert to std::string."); - MLNAssertConstantEqualsValue(@"🆔🆗🇦🇶", "🆔🆗🇦🇶"s, @"NSString with non-ASCII characters should convert losslessly to std::string."); + MLNAssertConstantEqualsValue(@"bar", "bar"s, @"NSString should convert to std::string."); + MLNAssertConstantEqualsValue( + @"🆔🆗🇦🇶", "🆔🆗🇦🇶"s, + @"NSString with non-ASCII characters should convert losslessly to std::string."); } - (void)testColorValuation { - MLNAssertConstantEqualsValue([MLNColor redColor], "rgba(255,0,0,1)"s, @"MLNColor should convert to std::string containing CSS color string."); + MLNAssertConstantEqualsValue( + [MLNColor redColor], "rgba(255,0,0,1)"s, + @"MLNColor should convert to std::string containing CSS color string."); } - (void)testBooleanValuation { - MLNAssertConstantEqualsValue(@NO, false, @"Boolean NSNumber should convert to bool."); - MLNAssertConstantEqualsValue(@YES, true, @"Boolean NSNumber should convert to bool."); + MLNAssertConstantEqualsValue(@NO, false, @"Boolean NSNumber should convert to bool."); + MLNAssertConstantEqualsValue(@YES, true, @"Boolean NSNumber should convert to bool."); } -- (void)testDoubleValuation -{ - MLNAssertConstantEqualsValue(@DBL_MIN, DBL_MIN, @"Double NSNumber should convert to double."); - MLNAssertConstantEqualsValue(@DBL_MAX, DBL_MAX, @"Double NSNumber should convert to double."); +- (void)testDoubleValuation { + MLNAssertConstantEqualsValue(@DBL_MIN, DBL_MIN, @"Double NSNumber should convert to double."); + MLNAssertConstantEqualsValue(@DBL_MAX, DBL_MAX, @"Double NSNumber should convert to double."); } - (void)testFloatValuation { - // Because we can't guarantee precision when using float, and because - // we warn the user to this effect in -[NSExpression mgl_constantMBGLValue], - // we just check that things are in the ballpark here with integer values - // and some lower-precision checks. + // Because we can't guarantee precision when using float, and because + // we warn the user to this effect in -[NSExpression mgl_constantMBGLValue], + // we just check that things are in the ballpark here with integer values + // and some lower-precision checks. - MLNAssertConstantEqualsValue(@-1.0f, -1.0, @"Float NSNumber should convert to double."); - MLNAssertConstantEqualsValue(@1.0f, 1.0, @"Float NSNumber should convert to double."); - MLNAssertConstantEqualsValueWithAccuracy(@-23.232342f, -23.232342, 0.000001, @"Float NSNumber should convert to double."); - MLNAssertConstantEqualsValueWithAccuracy(@23.232342f, 23.232342, 0.000001, @"Float NSNumber should convert to double."); - MLNAssertConstantEqualsValueWithAccuracy(@-FLT_MAX, static_cast(-FLT_MAX), 0.000001, @"Float NSNumber should convert to double."); - MLNAssertConstantEqualsValueWithAccuracy(@FLT_MAX, static_cast(FLT_MAX), 0.000001, @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValue(@-1.0f, -1.0, @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValue(@1.0f, 1.0, @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValueWithAccuracy(@-23.232342f, -23.232342, 0.000001, + @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValueWithAccuracy(@23.232342f, 23.232342, 0.000001, + @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValueWithAccuracy(@-FLT_MAX, static_cast(-FLT_MAX), 0.000001, + @"Float NSNumber should convert to double."); + MLNAssertConstantEqualsValueWithAccuracy(@FLT_MAX, static_cast(FLT_MAX), 0.000001, + @"Float NSNumber should convert to double."); } - (void)testIntegerValuation { - // Negative integers should always come back as int64_t per mbgl::Value definition. - MLNAssertConstantEqualsValue(@SHRT_MIN, static_cast(SHRT_MIN), @"Negative short NSNumber should convert to int64_t."); - MLNAssertConstantEqualsValue(@INT_MIN, static_cast(INT_MIN), @"Negative int NSNumber should convert to int64_t."); - MLNAssertConstantEqualsValue(@LONG_MIN, static_cast(LONG_MIN), @"Negative long NSNumber should convert to int64_t."); - MLNAssertConstantEqualsValue(@LLONG_MIN, static_cast(LLONG_MIN), @"Negative long long NSNumber should convert to int64_t."); - MLNAssertConstantEqualsValue(@NSIntegerMin, static_cast(NSIntegerMin), @"Negative NSInteger NSNumber should convert to int64_t."); + // Negative integers should always come back as int64_t per mbgl::Value definition. + MLNAssertConstantEqualsValue(@SHRT_MIN, static_cast(SHRT_MIN), + @"Negative short NSNumber should convert to int64_t."); + MLNAssertConstantEqualsValue(@INT_MIN, static_cast(INT_MIN), + @"Negative int NSNumber should convert to int64_t."); + MLNAssertConstantEqualsValue(@LONG_MIN, static_cast(LONG_MIN), + @"Negative long NSNumber should convert to int64_t."); + MLNAssertConstantEqualsValue(@LLONG_MIN, static_cast(LLONG_MIN), + @"Negative long long NSNumber should convert to int64_t."); + MLNAssertConstantEqualsValue(@NSIntegerMin, static_cast(NSIntegerMin), + @"Negative NSInteger NSNumber should convert to int64_t."); - // Positive integers should always come back as uint64_t per mbgl::Value definition. - MLNAssertConstantEqualsValue(@SHRT_MAX, static_cast(SHRT_MAX), @"Positive short NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@INT_MAX, static_cast(INT_MAX), @"Positive int NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@LONG_MAX, static_cast(LONG_MAX), @"Positive long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@LLONG_MAX, static_cast(LLONG_MAX), @"Positive long long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@NSIntegerMax, static_cast(NSIntegerMax), @"Positive NSInteger NSNumber should convert to uint64_t."); + // Positive integers should always come back as uint64_t per mbgl::Value definition. + MLNAssertConstantEqualsValue(@SHRT_MAX, static_cast(SHRT_MAX), + @"Positive short NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@INT_MAX, static_cast(INT_MAX), + @"Positive int NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@LONG_MAX, static_cast(LONG_MAX), + @"Positive long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@LLONG_MAX, static_cast(LLONG_MAX), + @"Positive long long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@NSIntegerMax, static_cast(NSIntegerMax), + @"Positive NSInteger NSNumber should convert to uint64_t."); } - (void)testUnsignedIntegerValuation { - // Zero-value integers should always come back as uint64_t per mbgl::Value definition - // (using the interpretation that zero is not negative). We use the unsigned long long - // value just for parity with the positive integer test. - MLNAssertConstantEqualsValue(@(static_cast(0)), static_cast(0), @"Unsigned short NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@0u, static_cast(0), @"Unsigned int NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@0UL, static_cast(0), @"Unsigned long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@0ULL, static_cast(0), @"Unsigned long long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@(static_cast(0)), static_cast(0), @"Unsigned NSUInteger NSNumber should convert to uint64_t."); + // Zero-value integers should always come back as uint64_t per mbgl::Value definition + // (using the interpretation that zero is not negative). We use the unsigned long long + // value just for parity with the positive integer test. + MLNAssertConstantEqualsValue(@(static_cast(0)), static_cast(0), + @"Unsigned short NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@0u, static_cast(0), + @"Unsigned int NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@0UL, static_cast(0), + @"Unsigned long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@0ULL, static_cast(0), + @"Unsigned long long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@(static_cast(0)), static_cast(0), + @"Unsigned NSUInteger NSNumber should convert to uint64_t."); - // Positive integers should always come back as uint64_t per mbgl::Value definition. - // We use the unsigned long long value because it can store the highest number on - // both 32- and 64-bit and won't overflow. - MLNAssertConstantEqualsValue(@USHRT_MAX, static_cast(USHRT_MAX), @"Unsigned short NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@UINT_MAX, static_cast(UINT_MAX), @"Unsigned int NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@ULONG_MAX, static_cast(ULONG_MAX), @"Unsigned long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@ULLONG_MAX, static_cast(ULLONG_MAX), @"Unsigned long long NSNumber should convert to uint64_t."); - MLNAssertConstantEqualsValue(@NSUIntegerMax, static_cast(NSUIntegerMax), @"Unsigned NSUInteger NSNumber should convert to uint64_t."); + // Positive integers should always come back as uint64_t per mbgl::Value definition. + // We use the unsigned long long value because it can store the highest number on + // both 32- and 64-bit and won't overflow. + MLNAssertConstantEqualsValue(@USHRT_MAX, static_cast(USHRT_MAX), + @"Unsigned short NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@UINT_MAX, static_cast(UINT_MAX), + @"Unsigned int NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@ULONG_MAX, static_cast(ULONG_MAX), + @"Unsigned long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@ULLONG_MAX, static_cast(ULLONG_MAX), + @"Unsigned long long NSNumber should convert to uint64_t."); + MLNAssertConstantEqualsValue(@NSUIntegerMax, static_cast(NSUIntegerMax), + @"Unsigned NSUInteger NSNumber should convert to uint64_t."); } - (void)testNullValuation { - mbgl::NullValue nullValue; - MLNAssertConstantEqualsValue([NSNull null], nullValue, @"NSNull should convert to mbgl::NullValue."); + mbgl::NullValue nullValue; + MLNAssertConstantEqualsValue([NSNull null], nullValue, + @"NSNull should convert to mbgl::NullValue."); } // MARK: - Feature type tests - (void)testFeatureType { - XCTAssertEqual([NSExpression expressionForConstantValue:@"Point"].mgl_featureType, mbgl::FeatureType::Point); - XCTAssertEqual([NSExpression expressionForConstantValue:@"LineString"].mgl_featureType, mbgl::FeatureType::LineString); - XCTAssertEqual([NSExpression expressionForConstantValue:@"Polygon"].mgl_featureType, mbgl::FeatureType::Polygon); - XCTAssertEqual([NSExpression expressionForConstantValue:@"Unknown"].mgl_featureType, mbgl::FeatureType::Unknown); - XCTAssertEqual([NSExpression expressionForConstantValue:@""].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:@"Point"].mgl_featureType, + mbgl::FeatureType::Point); + XCTAssertEqual([NSExpression expressionForConstantValue:@"LineString"].mgl_featureType, + mbgl::FeatureType::LineString); + XCTAssertEqual([NSExpression expressionForConstantValue:@"Polygon"].mgl_featureType, + mbgl::FeatureType::Polygon); + XCTAssertEqual([NSExpression expressionForConstantValue:@"Unknown"].mgl_featureType, + mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:@""].mgl_featureType, + mbgl::FeatureType::Unknown); - XCTAssertEqual([NSExpression expressionForConstantValue:@1].mgl_featureType, mbgl::FeatureType::Point); - XCTAssertEqual([NSExpression expressionForConstantValue:@2].mgl_featureType, mbgl::FeatureType::LineString); - XCTAssertEqual([NSExpression expressionForConstantValue:@3].mgl_featureType, mbgl::FeatureType::Polygon); - XCTAssertEqual([NSExpression expressionForConstantValue:@0].mgl_featureType, mbgl::FeatureType::Unknown); - XCTAssertEqual([NSExpression expressionForConstantValue:@-1].mgl_featureType, mbgl::FeatureType::Unknown); - XCTAssertEqual([NSExpression expressionForConstantValue:@4].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:@1].mgl_featureType, + mbgl::FeatureType::Point); + XCTAssertEqual([NSExpression expressionForConstantValue:@2].mgl_featureType, + mbgl::FeatureType::LineString); + XCTAssertEqual([NSExpression expressionForConstantValue:@3].mgl_featureType, + mbgl::FeatureType::Polygon); + XCTAssertEqual([NSExpression expressionForConstantValue:@0].mgl_featureType, + mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:@-1].mgl_featureType, + mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:@4].mgl_featureType, + mbgl::FeatureType::Unknown); - XCTAssertEqual([NSExpression expressionForConstantValue:nil].mgl_featureType, mbgl::FeatureType::Unknown); + XCTAssertEqual([NSExpression expressionForConstantValue:nil].mgl_featureType, + mbgl::FeatureType::Unknown); } // MARK: - JSON expression object tests - (void)testVariableExpressionObject { - { - NSExpression *expression = [NSExpression expressionForVariable:@"zoomLevel"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"zoom"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$zoomLevel"].mgl_jsonExpressionObject, @[@"zoom"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"zoom"]], expression); - NSMutableDictionary *context = [@{@"zoomLevel": @16} mutableCopy]; - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @16); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"heatmapDensity"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"heatmap-density"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$heatmapDensity"].mgl_jsonExpressionObject, @[@"heatmap-density"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"heatmap-density"]], expression); - NSMutableDictionary *context = [@{@"heatmapDensity": @1} mutableCopy]; - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"lineProgress"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"line-progress"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$lineProgress"].mgl_jsonExpressionObject, @[@"line-progress"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"line-progress"]], expression); - NSMutableDictionary *context = [@{@"lineProgress": @1} mutableCopy]; - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"featureAccumulated"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"accumulated"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureAccumulated"].mgl_jsonExpressionObject, @[@"accumulated"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"accumulated"]], expression); - } + { + NSExpression *expression = [NSExpression expressionForVariable:@"zoomLevel"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"zoom" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$zoomLevel"].mgl_jsonExpressionObject, @[ @"zoom" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"zoom" ]], expression); + NSMutableDictionary *context = [@{@"zoomLevel" : @16} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @16); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"heatmapDensity"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"heatmap-density" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$heatmapDensity"].mgl_jsonExpressionObject, + @[ @"heatmap-density" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"heatmap-density" ]], + expression); + NSMutableDictionary *context = [@{@"heatmapDensity" : @1} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"lineProgress"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"line-progress" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$lineProgress"].mgl_jsonExpressionObject, + @[ @"line-progress" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"line-progress" ]], + expression); + NSMutableDictionary *context = [@{@"lineProgress" : @1} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @1); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"featureAccumulated"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"accumulated" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$featureAccumulated"].mgl_jsonExpressionObject, + @[ @"accumulated" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"accumulated" ]], + expression); + } - { - NSExpression *expression = [NSExpression expressionForVariable:@"geometryType"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"geometry-type"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$geometryType"].mgl_jsonExpressionObject, @[@"geometry-type"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"geometry-type"]], expression); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"featureIdentifier"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"id"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureIdentifier"].mgl_jsonExpressionObject, @[@"id"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"id"]], expression); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"featureAttributes"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[@"properties"]); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$featureAttributes"].mgl_jsonExpressionObject, @[@"properties"]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[@"properties"]], expression); - } - { - NSExpression *expression = [NSExpression expressionForVariable:@"loremIpsum"]; - NSArray *jsonExpression = @[@"var", @"loremIpsum"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"$loremIpsum"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - NSMutableDictionary *context = [@{@"loremIpsum": @"Lorem ipsum dolor sit amet"} mutableCopy]; - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], @"Lorem ipsum dolor sit amet"); - } - { - NSDictionary *context = @{@"loremIpsum": MLNConstantExpression(@"Lorem ipsum dolor sit amet")}; - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_LET('loremIpsum', 'Lorem ipsum dolor sit amet', uppercase($loremIpsum))", context]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(uppercase($loremIpsum), 'mgl_expressionWithContext:', %@)", context]; - NSArray *jsonExpression = @[@"let", @"loremIpsum", @"Lorem ipsum dolor sit amet", @[@"upcase", @[@"var", @"loremIpsum"]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + { + NSExpression *expression = [NSExpression expressionForVariable:@"geometryType"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"geometry-type" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$geometryType"].mgl_jsonExpressionObject, + @[ @"geometry-type" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"geometry-type" ]], + expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"featureIdentifier"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"id" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$featureIdentifier"].mgl_jsonExpressionObject, + @[ @"id" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"id" ]], expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"featureAttributes"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @[ @"properties" ]); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$featureAttributes"].mgl_jsonExpressionObject, + @[ @"properties" ]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@[ @"properties" ]], + expression); + } + { + NSExpression *expression = [NSExpression expressionForVariable:@"loremIpsum"]; + NSArray *jsonExpression = @[ @"var", @"loremIpsum" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"$loremIpsum"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + NSMutableDictionary *context = [@{@"loremIpsum" : @"Lorem ipsum dolor sit amet"} mutableCopy]; + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:context], + @"Lorem ipsum dolor sit amet"); + } + { + NSDictionary *context = @{@"loremIpsum" : MLNConstantExpression(@"Lorem ipsum dolor sit amet")}; +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = [NSExpression + expressionWithFormat: + @"MLN_LET('loremIpsum', 'Lorem ipsum dolor sit amet', uppercase($loremIpsum))", + context]; + NSExpression *compatibilityExpression = [NSExpression + expressionWithFormat:@"FUNCTION(uppercase($loremIpsum), 'mgl_expressionWithContext:', %@)", + context]; + NSArray *jsonExpression = @[ + @"let", @"loremIpsum", @"Lorem ipsum dolor sit amet", + @[ @"upcase", @[ @"var", @"loremIpsum" ] ] + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testConstantValueExpressionObject { - { - NSExpression *expression = [NSExpression expressionForConstantValue:nil]; - XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); - XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:[NSNull null]], expression); - XCTAssertNil([expression expressionValueWithObject:nil context:nil]); - } - { - NSExpression *expression = [NSExpression expressionForConstantValue:@1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @1); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1"].mgl_jsonExpressionObject, @1); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@1], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - } - { - NSExpression *expression = [NSExpression expressionForConstantValue:@YES]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @YES); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TRUE"].mgl_jsonExpressionObject, @YES); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@YES], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); - } - { - NSExpression *expression = [NSExpression expressionForConstantValue:nil]; - XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); - XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:[NSNull null]], expression); - XCTAssertNil([expression expressionValueWithObject:nil context:nil]); - } - { - CGVector vector = CGVectorMake(1, 2); - NSExpression *expression = [NSExpression expressionForConstantValue:@(vector)]; + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@1]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1"].mgl_jsonExpressionObject, @1); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@1], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@YES]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @YES); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TRUE"].mgl_jsonExpressionObject, + @YES); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:@YES], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:nil]; + XCTAssert(expression.mgl_jsonExpressionObject == [NSNull null]); + XCTAssert([NSExpression expressionWithFormat:@"nil"].mgl_jsonExpressionObject == [NSNull null]); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:[NSNull null]], expression); + XCTAssertNil([expression expressionValueWithObject:nil context:nil]); + } + { + CGVector vector = CGVectorMake(1, 2); + NSExpression *expression = [NSExpression expressionForConstantValue:@(vector)]; #if !TARGET_OS_IPHONE - NSArray *jsonExpression = @[@"literal", @[@1, @-2]]; + NSArray *jsonExpression = @[ @"literal", @[ @1, @-2 ] ]; #else - NSArray *jsonExpression = @[@"literal", @[@1, @2]]; + NSArray *jsonExpression = @[ @"literal", @[ @1, @2 ] ]; #endif - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // No way to distinguish offsets from ordinary arrays in expressions. - XCTAssertEqualObjects([[NSExpression expressionWithMLNJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(vector)); - } - { + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression expressionWithMLNJSONObject:jsonExpression].collection + valueForKeyPath:@"constantValue"], + jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(vector)); + } + { #if !TARGET_OS_IPHONE - NSEdgeInsets padding = {1, 2, 3, 4}; - NSValue *value = [NSValue valueWithEdgeInsets:padding]; + NSEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithEdgeInsets:padding]; #else - UIEdgeInsets padding = {1, 2, 3, 4}; - NSValue *value = [NSValue valueWithUIEdgeInsets:padding]; + UIEdgeInsets padding = {1, 2, 3, 4}; + NSValue *value = [NSValue valueWithUIEdgeInsets:padding]; #endif - NSExpression *expression = [NSExpression expressionForConstantValue:value]; - NSArray *jsonExpression = @[@"literal", @[@1, @4, @3, @2]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // No way to distinguish offsets from ordinary arrays in expressions. - XCTAssertEqualObjects([[NSExpression expressionWithMLNJSONObject:jsonExpression].collection valueForKeyPath:@"constantValue"], jsonExpression.lastObject); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], value); - } - { - MLNColor *color = [MLNColor mgl_colorWithColor:{ 255.0/255, 239.0/255, 213.0/255, 1 }]; // papayawhip - NSExpression *expression = [NSExpression expressionForConstantValue:color]; - NSArray *jsonExpression = @[@"rgb", @255, @239, @213]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); - } - { - // Transform color components to non-premultiplied values - float alpha = 0.5; - float red = (255.0 * alpha) / 255; - float green = (239.0 * alpha) / 255; - float blue = (213.0 * alpha) / 255; - MLNColor *color = [MLNColor mgl_colorWithColor:{ red, green, blue, alpha }]; // papayawhip - NSExpression *expression = [NSExpression expressionForConstantValue:color]; - NSArray *jsonExpression = @[@"rgba", @255.0, @239.0, @213.0, @0.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); - } - { - // Transform color components to non-premultiplied values - NSArray *jsonExpression = @[@"rgba", @255.0, @239.0, @213.0, @0.5]; - NSExpression *papayawhipExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - MLNColor *papayaWhipColor = [papayawhipExpression expressionValueWithObject:nil context:nil]; - CGFloat convertedRed = 0.0, convertedGreen = 0.0, convertedBlue = 0.0, convertedAlpha = 0.0; - [papayaWhipColor getRed:&convertedRed green:&convertedGreen blue:&convertedBlue alpha:&convertedAlpha]; + NSExpression *expression = [NSExpression expressionForConstantValue:value]; + NSArray *jsonExpression = @[ @"literal", @[ @1, @4, @3, @2 ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // No way to distinguish offsets from ordinary arrays in expressions. + XCTAssertEqualObjects([[NSExpression expressionWithMLNJSONObject:jsonExpression].collection + valueForKeyPath:@"constantValue"], + jsonExpression.lastObject); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], value); + } + { + MLNColor *color = + [MLNColor mgl_colorWithColor:{255.0 / 255, 239.0 / 255, 213.0 / 255, 1}]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[ @"rgb", @255, @239, @213 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + // Transform color components to non-premultiplied values + float alpha = 0.5; + float red = (255.0 * alpha) / 255; + float green = (239.0 * alpha) / 255; + float blue = (213.0 * alpha) / 255; + MLNColor *color = [MLNColor mgl_colorWithColor:{red, green, blue, alpha}]; // papayawhip + NSExpression *expression = [NSExpression expressionForConstantValue:color]; + NSArray *jsonExpression = @[ @"rgba", @255.0, @239.0, @213.0, @0.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], color); + } + { + // Transform color components to non-premultiplied values + NSArray *jsonExpression = @[ @"rgba", @255.0, @239.0, @213.0, @0.5 ]; + NSExpression *papayawhipExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + MLNColor *papayaWhipColor = [papayawhipExpression expressionValueWithObject:nil context:nil]; + CGFloat convertedRed = 0.0, convertedGreen = 0.0, convertedBlue = 0.0, convertedAlpha = 0.0; + [papayaWhipColor getRed:&convertedRed + green:&convertedGreen + blue:&convertedBlue + alpha:&convertedAlpha]; - XCTAssertEqualWithAccuracy(convertedRed, 255.0/255.0, 0.0000001); - XCTAssertEqualWithAccuracy(convertedGreen, 239.0/255.0, 0.0000001); - XCTAssertEqualWithAccuracy(convertedBlue, 213.0/255.0, 0.0000001); - XCTAssertEqual(convertedAlpha, 0.5); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"noindex(513)"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @513); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @513); - } + XCTAssertEqualWithAccuracy(convertedRed, 255.0 / 255.0, 0.0000001); + XCTAssertEqualWithAccuracy(convertedGreen, 239.0 / 255.0, 0.0000001); + XCTAssertEqualWithAccuracy(convertedBlue, 213.0 / 255.0, 0.0000001); + XCTAssertEqual(convertedAlpha, 0.5); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"noindex(513)"]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, @513); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @513); + } } - (void)testKeyPathExpressionObject { - { - NSExpression *expression = [NSExpression expressionForKeyPath:@"highway"]; - NSArray *jsonExpression = @[@"get", @"highway"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"highway"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"%@.population", @{@"population": MLNConstantExpression(@12000)}]; - NSArray *jsonExpression = @[@"get", @"population", @[@"literal", @{@"population": @12000}]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"%@.uppercase('population')", @{@"POPULATION": MLNConstantExpression(@12000)}]; - NSArray *jsonExpression = @[@"get", @[@"upcase", @"population"], @[@"literal", @{@"POPULATION": @12000}]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForKeyPath:@"lineStyle.color"]; - NSArray *jsonExpression = @[@"get", @"color", @[@"get", @"lineStyle"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForKeyPath:@"map.box.gl"]; - NSArray *jsonExpression = @[@"get", @"gl", @[@"get", @"box", @[@"get", @"map"]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + { + NSExpression *expression = [NSExpression expressionForKeyPath:@"highway"]; + NSArray *jsonExpression = @[ @"get", @"highway" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"highway"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"%@.population", + @{@"population" : MLNConstantExpression(@12000)}]; + NSArray *jsonExpression = + @[ @"get", @"population", @[ @"literal", + @{@"population" : @12000} ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"%@.uppercase('population')", + @{@"POPULATION" : MLNConstantExpression(@12000)}]; + NSArray *jsonExpression = + @[ @"get", @[ @"upcase", @"population" ], @[ @"literal", + @{@"POPULATION" : @12000} ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForKeyPath:@"lineStyle.color"]; + NSArray *jsonExpression = @[ @"get", @"color", @[ @"get", @"lineStyle" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForKeyPath:@"map.box.gl"]; + NSArray *jsonExpression = @[ @"get", @"gl", @[ @"get", @"box", @[ @"get", @"map" ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testStatisticalExpressionObject { - { - NSExpression *expression = [NSExpression expressionWithFormat:@"average({1, 2, 2, 3, 4, 7, 9})"]; - NSArray *jsonExpression = @[@"/", @[@"+", @1, @2, @2, @3, @4, @7, @9], @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @4); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"sum({1, 2, 2, 3, 4, 7, 9})"]; - NSArray *jsonExpression = @[@"+", @1, @2, @2, @3, @4, @7, @9]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @28); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"count({1, 2, 2, 3, 4, 7, 9})"]; - NSArray *jsonExpression = @[@"length", @[@"literal", @[@1, @2, @2, @3, @4, @7, @9]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @7); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"min({1, 2, 2, 3, 4, 7, 9})"]; - NSArray *jsonExpression = @[@"min", @1, @2, @2, @3, @4, @7, @9]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"max({1, 2, 2, 3, 4, 7, 9})"]; - NSArray *jsonExpression = @[@"max", @1, @2, @2, @3, @4, @7, @9]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @9); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"average({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[ + @"/", @[ @"+", @1, @2, @2, @3, @4, @7, @9 ], + @[ @"length", @[ @"literal", @[ @1, @2, @2, @3, @4, @7, @9 ] ] ] + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @4); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"sum({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[ @"+", @1, @2, @2, @3, @4, @7, @9 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @28); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"count({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[ @"length", @[ @"literal", @[ @1, @2, @2, @3, @4, @7, @9 ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @7); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"min({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[ @"min", @1, @2, @2, @3, @4, @7, @9 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"max({1, 2, 2, 3, 4, 7, 9})"]; + NSArray *jsonExpression = @[ @"max", @1, @2, @2, @3, @4, @7, @9 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @9); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testArithmeticExpressionObject { - NSArray *arguments = @[MLNConstantExpression(@1), MLNConstantExpression(@1)]; - { - NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:arguments]; - NSArray *jsonExpression = @[@"+", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *testExpression = [NSExpression expressionWithFormat:@"sum:({1, 1, 2})"]; - NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForAggregate:@[MLNConstantExpression(@1), MLNConstantExpression(@1), MLNConstantExpression(@2)]]]]; + NSArray *arguments = @[ MLNConstantExpression(@1), MLNConstantExpression(@1) ]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:arguments]; + NSArray *jsonExpression = @[ @"+", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *testExpression = [NSExpression expressionWithFormat:@"sum:({1, 1, 2})"]; + NSExpression *expression = + [NSExpression expressionForFunction:@"sum:" + arguments:@[ [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@1), MLNConstantExpression(@1), + MLNConstantExpression(@2) + ]] ]]; - NSArray *jsonExpression = @[@"+", @1, @1, @2]; + NSArray *jsonExpression = @[ @"+", @1, @1, @2 ]; - XCTAssertEqualObjects(testExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(expression, testExpression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[MLNConstantExpression(@1), MLNConstantExpression(@1), MLNConstantExpression(@2)]]; - NSArray *jsonExpression = @[@"+", @1, @1, @2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(testExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(expression, testExpression); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"sum:" + arguments:@[ + MLNConstantExpression(@1), MLNConstantExpression(@1), + MLNConstantExpression(@2) + ]]; + NSArray *jsonExpression = @[ @"+", @1, @1, @2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // - [NSExpression expressionWithMLNJSONObject:] creates an expression with an aggregate expression as an argument. This is not equal to an expression with an array of expressions as an argument. For testing purposes, we will compare their operands and arrays of expressions. - NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); - XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments); - } - { - NSArray *threeArguments = @[MLNConstantExpression(@1), MLNConstantExpression(@1), MLNConstantExpression(@1)]; - NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" arguments:threeArguments]; - NSArray *jsonExpression = @[@"+", @1, @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - jsonExpression = @[@"+", @[@"+", @1, @1], @1]; - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1 + 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], [NSExpression expressionWithFormat:@"1 + 1 + 1"]); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"from:subtract:" arguments:arguments]; - NSArray *jsonExpression = @[@"-", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 - 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"multiply:by:" arguments:arguments]; - NSArray *jsonExpression = @[@"*", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 * 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"divide:by:" arguments:arguments]; - NSArray *jsonExpression = @[@"/", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 / 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"modulus:by:" arguments:arguments]; - NSArray *jsonExpression = @[@"%", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // NSExpression lacks a shorthand operator for modulus. - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"max:" arguments:arguments]; - NSArray *jsonExpression = @[@"max", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // - [NSExpression expressionWithMLNJSONObject:] creates an expression with an aggregate + // expression as an argument. This is not equal to an expression with an array of expressions as + // an argument. For testing purposes, we will compare their operands and arrays of expressions. + NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); + XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, + expression.arguments); + } + { + NSArray *threeArguments = + @[ MLNConstantExpression(@1), MLNConstantExpression(@1), MLNConstantExpression(@1) ]; + NSExpression *expression = [NSExpression expressionForFunction:@"add:to:" + arguments:threeArguments]; + NSArray *jsonExpression = @[ @"+", @1, @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + jsonExpression = @[ @"+", @[ @"+", @1, @1 ], @1 ]; + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 + 1 + 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], + [NSExpression expressionWithFormat:@"1 + 1 + 1"]); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"from:subtract:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"-", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 - 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"multiply:by:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"*", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 * 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"divide:by:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"/", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 / 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"modulus:by:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"%", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression lacks a shorthand operator for modulus. + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"max:" arguments:arguments]; + NSArray *jsonExpression = @[ @"max", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); - XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"min:" arguments:arguments]; - NSArray *jsonExpression = @[@"min", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); + XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, + expression.arguments); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"min:" arguments:arguments]; + NSArray *jsonExpression = @[ @"min", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); - XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, expression.arguments); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MLNConstantExpression(@1.5)]]; - NSArray *jsonExpression = @[@"ceil", @1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MLNConstantExpression(@-1.5)]]; - NSArray *jsonExpression = @[@"ceil", @-1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MLNConstantExpression(@2)]]; - NSArray *jsonExpression = @[@"ceil", @2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" arguments:@[MLNConstantExpression(@-2)]]; - NSArray *jsonExpression = @[@"ceil", @-2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MLNConstantExpression(@1.5)]]; - NSArray *jsonExpression = @[@"-", @1.5, @[@"%", @1.5, @1]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"trunc:" arguments:@[MLNConstantExpression(@-1.5)]]; - NSArray *jsonExpression = @[@"-", @-1.5, @[@"%", @-1.5, @1]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MLNConstantExpression(@2)]]; - NSArray *jsonExpression = @[@"abs", @2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"abs:" arguments:@[MLNConstantExpression(@-2)]]; - NSArray *jsonExpression = @[@"abs", @-2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MLNConstantExpression(@1.5)]]; - NSArray *jsonExpression = @[@"floor", @1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MLNConstantExpression(@-1.5)]]; - NSArray *jsonExpression = @[@"floor", @-1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MLNConstantExpression(@2)]]; - NSArray *jsonExpression = @[@"floor", @2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"floor:" arguments:@[MLNConstantExpression(@-2)]]; - NSArray *jsonExpression = @[@"floor", @-2]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); - } - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MLNConstantExpression(@1.5)]]; - NSArray *jsonExpression = @[@"round", @1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MLNConstantExpression(@-1.5)]]; - NSArray *jsonExpression = @[@"round", @-1.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MLNConstantExpression(@2.5)]]; - NSArray *jsonExpression = @[@"round", @2.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @3); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_round:" arguments:@[MLNConstantExpression(@-2.5)]]; - NSArray *jsonExpression = @[@"round", @-2.5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-3); - } + NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); + XCTAssertEqualObjects(aggregateExpression.arguments.firstObject.collection, + expression.arguments); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"ceiling:" arguments:@[ MLNConstantExpression(@1.5) ]]; + NSArray *jsonExpression = @[ @"ceil", @1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"ceiling:" + arguments:@[ MLNConstantExpression(@-1.5) ]]; + NSArray *jsonExpression = @[ @"ceil", @-1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" + arguments:@[ MLNConstantExpression(@2) ]]; + NSArray *jsonExpression = @[ @"ceil", @2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ceiling:" + arguments:@[ MLNConstantExpression(@-2) ]]; + NSArray *jsonExpression = @[ @"ceil", @-2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"trunc:" arguments:@[ MLNConstantExpression(@1.5) ]]; + NSArray *jsonExpression = @[ @"-", @1.5, @[ @"%", @1.5, @1 ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"trunc:" arguments:@[ MLNConstantExpression(@-1.5) ]]; + NSArray *jsonExpression = @[ @"-", @-1.5, @[ @"%", @-1.5, @1 ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-1); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" + arguments:@[ MLNConstantExpression(@2) ]]; + NSArray *jsonExpression = @[ @"abs", @2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"abs:" + arguments:@[ MLNConstantExpression(@-2) ]]; + NSArray *jsonExpression = @[ @"abs", @-2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"floor:" arguments:@[ MLNConstantExpression(@1.5) ]]; + NSArray *jsonExpression = @[ @"floor", @1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"floor:" arguments:@[ MLNConstantExpression(@-1.5) ]]; + NSArray *jsonExpression = @[ @"floor", @-1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" + arguments:@[ MLNConstantExpression(@2) ]]; + NSArray *jsonExpression = @[ @"floor", @2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"floor:" + arguments:@[ MLNConstantExpression(@-2) ]]; + NSArray *jsonExpression = @[ @"floor", @-2 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_round:" + arguments:@[ MLNConstantExpression(@1.5) ]]; + NSArray *jsonExpression = @[ @"round", @1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @2); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_round:" + arguments:@[ MLNConstantExpression(@-1.5) ]]; + NSArray *jsonExpression = @[ @"round", @-1.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-2); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_round:" + arguments:@[ MLNConstantExpression(@2.5) ]]; + NSArray *jsonExpression = @[ @"round", @2.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @3); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_round:" + arguments:@[ MLNConstantExpression(@-2.5) ]]; + NSArray *jsonExpression = @[ @"round", @-2.5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @-3); + } } - (void)testTrigonometricExpressionObject { - NSArray *arguments = @[MLNConstantExpression(@1), MLNConstantExpression(@1)]; - { - NSExpression *expression = [NSExpression expressionForFunction:@"sqrt:" arguments:arguments]; - NSArray *jsonExpression = @[@"sqrt", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"ln:" arguments:arguments]; - NSArray *jsonExpression = @[@"ln", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_log2:" arguments:@[MLNConstantExpression(@1024)]]; - NSArray *jsonExpression = @[@"log2", @1024]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @10); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"raise:toPower:" arguments:arguments]; - NSArray *jsonExpression = @[@"^", @1, @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 ** 1"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"exp:" arguments:@[MLNConstantExpression(@0)]]; - NSArray *jsonExpression = @[@"^", @[@"e"], @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForConstantValue:@(M_E)]; - NSArray *jsonExpression = @[@"e"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_E)); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForConstantValue:@(M_PI)]; - NSArray *jsonExpression = @[@"pi"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_PI)); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_acos:" arguments:@[MLNConstantExpression(@1)]]; - NSArray *jsonExpression = @[@"acos", @1]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_cos:" arguments:@[MLNConstantExpression(@0)]]; - NSArray *jsonExpression = @[@"cos", @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_asin:" arguments:@[MLNConstantExpression(@0)]]; - NSArray *jsonExpression = @[@"asin", @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_sin:" arguments:@[MLNConstantExpression(@0)]]; - NSArray *jsonExpression = @[@"sin", @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_atan:" arguments:@[MLNConstantExpression(@20)]]; - NSArray *jsonExpression = @[@"atan", @20]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - NSNumber *value = [expression expressionValueWithObject:nil context:nil]; - XCTAssertEqualWithAccuracy(value.doubleValue, 1.52, 0.001); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_tan:" arguments:@[MLNConstantExpression(@0)]]; - NSArray *jsonExpression = @[@"tan", @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + NSArray *arguments = @[ MLNConstantExpression(@1), MLNConstantExpression(@1) ]; + { + NSExpression *expression = [NSExpression expressionForFunction:@"sqrt:" arguments:arguments]; + NSArray *jsonExpression = @[ @"sqrt", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"ln:" arguments:arguments]; + NSArray *jsonExpression = @[ @"ln", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_log2:" + arguments:@[ MLNConstantExpression(@1024) ]]; + NSArray *jsonExpression = @[ @"log2", @1024 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @10); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"raise:toPower:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"^", @1, @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"1 ** 1"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"exp:" + arguments:@[ MLNConstantExpression(@0) ]]; + NSArray *jsonExpression = @[ @"^", @[ @"e" ], @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_E)]; + NSArray *jsonExpression = @[ @"e" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_E)); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForConstantValue:@(M_PI)]; + NSArray *jsonExpression = @[ @"pi" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @(M_PI)); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_acos:" + arguments:@[ MLNConstantExpression(@1) ]]; + NSArray *jsonExpression = @[ @"acos", @1 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_cos:" + arguments:@[ MLNConstantExpression(@0) ]]; + NSArray *jsonExpression = @[ @"cos", @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @1); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_asin:" + arguments:@[ MLNConstantExpression(@0) ]]; + NSArray *jsonExpression = @[ @"asin", @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_sin:" + arguments:@[ MLNConstantExpression(@0) ]]; + NSArray *jsonExpression = @[ @"sin", @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_atan:" + arguments:@[ MLNConstantExpression(@20) ]]; + NSArray *jsonExpression = @[ @"atan", @20 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + NSNumber *value = [expression expressionValueWithObject:nil context:nil]; + XCTAssertEqualWithAccuracy(value.doubleValue, 1.52, 0.001); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_tan:" + arguments:@[ MLNConstantExpression(@0) ]]; + NSArray *jsonExpression = @[ @"tan", @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @0); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testShapeDistanceExpressionObject { - { - MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; - point.coordinate = CLLocationCoordinate2DMake(1, -1); - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_distanceFrom:" arguments:@[MLNConstantExpression(point)]]; - NSArray *jsonExpression = @[@"distance", @{@"type": @"Point", @"coordinates": @[@-1, @1]}]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertThrowsSpecificNamed([expression expressionValueWithObject:nil context:nil], NSException, NSInvalidArgumentException); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + { + MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; + point.coordinate = CLLocationCoordinate2DMake(1, -1); +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_distanceFrom:" + arguments:@[ MLNConstantExpression(point) ]]; + NSArray *jsonExpression = + @[ @"distance", + @{@"type" : @"Point", + @"coordinates" : @[ @-1, @1 ]} ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertThrowsSpecificNamed([expression expressionValueWithObject:nil context:nil], + NSException, NSInvalidArgumentException); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testStringFormattingExpressionObject { - NSArray *arguments = @[MLNConstantExpression(@"MacDonald")]; - { - NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION('Old', 'stringByAppendingString:', 'MacDonald')"]; - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *aftermarketExpression = [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"]; - NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); - XCTAssertEqualObjects([aftermarketExpression expressionValueWithObject:nil context:nil], @"OldMacDonald"); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], aftermarketExpression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_join:" arguments:@[@"Old", @"MacDonald"]]; - NSExpression *aftermarketExpression = [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"]; - NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + NSArray *arguments = @[ MLNConstantExpression(@"MacDonald") ]; + { + NSExpression *expression = [NSExpression + expressionWithFormat:@"FUNCTION('Old', 'stringByAppendingString:', 'MacDonald')"]; +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *aftermarketExpression = + [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"]; + NSArray *jsonExpression = @[ @"concat", @"Old", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([aftermarketExpression expressionValueWithObject:nil context:nil], + @"OldMacDonald"); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], + aftermarketExpression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_join:" + arguments:@[ @"Old", @"MacDonald" ]]; + NSExpression *aftermarketExpression = + [NSExpression expressionWithFormat:@"mgl_join({'Old', 'MacDonald'})"]; + NSArray *jsonExpression = @[ @"concat", @"Old", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, expression.mgl_jsonExpressionObject); - NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" arguments:arguments]; - NSArray *jsonExpression = @[@"upcase", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"lowercase:" arguments:arguments]; - NSArray *jsonExpression = @[@"downcase", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"length:" arguments:arguments]; - NSArray *jsonExpression = @[@"length", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + XCTAssertEqualObjects(aftermarketExpression.mgl_jsonExpressionObject, + expression.mgl_jsonExpressionObject); + NSExpression *aggregateExpression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + XCTAssertEqualObjects(aggregateExpression.operand, expression.operand); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"uppercase:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"upcase", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"lowercase:" + arguments:arguments]; + NSArray *jsonExpression = @[ @"downcase", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"length:" arguments:arguments]; + NSArray *jsonExpression = @[ @"length", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testTypeConversionExpressionObject { - { - NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'boolValue')"]; - NSArray *jsonExpression = @[@"to-boolean", @[@"get", @"number"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // NSExpression is unable to evaluate -[NSNumber boolValue] by itself - // because it returns a primitive instead of an object. - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_number')"]; - NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue')"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue')"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue')"].mgl_jsonExpressionObject, jsonExpression); - // NSExpression is unable to evaluate NSNumber’s -floatValue, - // -doubleValue, or -decimalValue by themselves because they each return - // a primitive instead of an object. - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], - [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:', zipCode)"]; - NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"], @[@"get", @"zipCode"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue', zipCode)"].mgl_jsonExpressionObject, jsonExpression); - // NSExpression is unable to evaluate NSNumber’s -floatValue, - // -doubleValue, or -decimalValue by themselves because they each return - // a primitive instead of an object. - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:')"]; - NSArray *jsonExpression = @[@"to-number", @[@"get", @"postalCode"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:@{@"postalCode": @"02134"} context:nil], @02134); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(number, 'NSString')"]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'stringValue')"]; - NSArray *jsonExpression = @[@"to-string", @[@"get", @"number"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:@{@"number": @1.5} context:nil], @"1.5"); - XCTAssertEqualObjects([compatibilityExpression expressionValueWithObject:@{@"number": @1.5} context:nil], @"1.5"); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { + { + NSExpression *expression = [NSExpression expressionWithFormat:@"FUNCTION(number, 'boolValue')"]; + NSArray *jsonExpression = @[ @"to-boolean", @[ @"get", @"number" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // NSExpression is unable to evaluate -[NSNumber boolValue] by itself + // because it returns a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'mgl_number')"]; + NSArray *jsonExpression = @[ @"to-number", @[ @"get", @"postalCode" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue')"] + .mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue')"] + .mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue')"] + .mgl_jsonExpressionObject, + jsonExpression); + // NSExpression is unable to evaluate NSNumber’s -floatValue, + // -doubleValue, or -decimalValue by themselves because they each return + // a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], + [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]); + } + { + NSExpression *expression = [NSExpression + expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:', zipCode)"]; + NSArray *jsonExpression = + @[ @"to-number", @[ @"get", @"postalCode" ], @[ @"get", @"zipCode" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'doubleValue', zipCode)"] + .mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'floatValue', zipCode)"] + .mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"FUNCTION(postalCode, 'decimalValue', zipCode)"] + .mgl_jsonExpressionObject, + jsonExpression); + // NSExpression is unable to evaluate NSNumber’s -floatValue, + // -doubleValue, or -decimalValue by themselves because they each return + // a primitive instead of an object. + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(postalCode, 'NSNumber')"]; + NSExpression *compatibilityExpression = [NSExpression + expressionWithFormat:@"FUNCTION(postalCode, 'mgl_numberWithFallbackValues:')"]; + NSArray *jsonExpression = @[ @"to-number", @[ @"get", @"postalCode" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects( + [expression expressionValueWithObject:@{@"postalCode" : @"02134"} context:nil], @02134); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(number, 'NSString')"]; + NSExpression *compatibilityExpression = + [NSExpression expressionWithFormat:@"FUNCTION(number, 'stringValue')"]; + NSArray *jsonExpression = @[ @"to-string", @[ @"get", @"number" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{ + @"number" : @1.5 + } + context:nil], + @"1.5"); + XCTAssertEqualObjects([compatibilityExpression expressionValueWithObject:@{ + @"number" : @1.5 + } + context:nil], + @"1.5"); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { #if TARGET_OS_IPHONE - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(x, 'UIColor')"]; + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(x, 'UIColor')"]; #else - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(x, 'NSColor')"]; + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(x, 'NSColor')"]; #endif - NSArray *jsonExpression = @[@"to-color", @[@"get", @"x"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('to-color', x, y, z)"]; - NSArray *jsonExpression = @[@"to-color", @[@"get", @"x"], @[@"get", @"y"], @[@"get", @"z"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(noindex(x), 'NSArray')"]; - NSArray *jsonExpression = @[@"to-rgba", @[@"get", @"x"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(noindex(%@), 'NSArray')", MLNConstantExpression(MLNColor.blueColor)]; - NSArray *jsonExpression = @[@"to-rgba", @[@"rgb", @0, @0, @255]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(noindex('x'), 'NSArray')"]; - XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } + NSArray *jsonExpression = @[ @"to-color", @[ @"get", @"x" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionWithFormat:@"MLN_FUNCTION('to-color', x, y, z)"]; + NSArray *jsonExpression = + @[ @"to-color", @[ @"get", @"x" ], @[ @"get", @"y" ], @[ @"get", @"z" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(noindex(x), 'NSArray')"]; + NSArray *jsonExpression = @[ @"to-rgba", @[ @"get", @"x" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"CAST(noindex(%@), 'NSArray')", + MLNConstantExpression(MLNColor.blueColor)]; + NSArray *jsonExpression = @[ @"to-rgba", @[ @"rgb", @0, @0, @255 ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionWithFormat:@"CAST(noindex('x'), 'NSArray')"]; + XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } } - (void)testInterpolationExpressionObject { - { - NSDictionary *stops = @{@0: MLNConstantExpression(@100), @10: MLNConstantExpression(@200)}; - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'linear', nil, %@)", stops]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", stops]; - NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{@1: MLNConstantExpression(@2), @3: MLNConstantExpression(@6)}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'exponential', 2, %@)", stops]; - NSArray *jsonExpression = @[@"interpolate", @[@"exponential", @2], @[@"get", @"x"], @1, @2, @3, @6]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{@0: MLNConstantExpression(@0), @100: MLNConstantExpression(@100)}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops]; - NSArray *jsonExpression = @[@"interpolate", @[@"cubic-bezier", @0.42, @0, @0.58, @1], @[@"get", @"x"], @0, @0, @100, @100]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{@0: MLNConstantExpression(@111), @1: MLNConstantExpression(@1111)}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(x, 11, %@)", stops]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops]; - NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{@0: MLNConstantExpression(@111), @1: MLNConstantExpression(@1111)}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, 11, %@)", stops]; - NSArray *jsonExpression = @[@"step", @[@"zoom"], @11, @0, @111, @1, @1111]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, 'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", stops]; - XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSDictionary *stops = @{}; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, 11, %@)", stops]; - XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@100), @10 : MLNConstantExpression(@200)}; + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); + NSExpression *expression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:(x, 'linear', nil, %@)", stops]; + NSExpression *compatibilityExpression = [NSExpression + expressionWithFormat: + @"FUNCTION(x, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + stops]; + NSArray *jsonExpression = + @[ @"interpolate", @[ @"linear" ], @[ @"get", @"x" ], @0, @100, @10, @200 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@1 : MLNConstantExpression(@2), @3 : MLNConstantExpression(@6)}; + NSExpression *expression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:(x, 'exponential', 2, %@)", stops]; + NSArray *jsonExpression = + @[ @"interpolate", @[ @"exponential", @2 ], @[ @"get", @"x" ], @1, @2, @3, @6 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@0), @100 : MLNConstantExpression(@100)}; + NSExpression *expression = + [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, " + @"'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", + stops]; + NSArray *jsonExpression = @[ + @"interpolate", @[ @"cubic-bezier", @0.42, @0, @0.58, @1 ], @[ @"get", @"x" ], @0, @0, @100, + @100 + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@111), @1 : MLNConstantExpression(@1111)}; + NSExpression *expression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:(x, 11, %@)", stops]; + NSExpression *compatibilityExpression = [NSExpression + expressionWithFormat:@"FUNCTION(x, 'mgl_stepWithMinimum:stops:', 11, %@)", stops]; + NSArray *jsonExpression = @[ @"step", @[ @"get", @"x" ], @11, @0, @111, @1, @1111 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@111), @1 : MLNConstantExpression(@1111)}; + NSExpression *expression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, 11, %@)", stops]; + NSArray *jsonExpression = @[ @"step", @[ @"zoom" ], @11, @0, @111, @1, @1111 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{}; + NSExpression *expression = + [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(x, " + @"'cubic-bezier', { 0.42, 0, 0.58, 1 }, %@)", + stops]; + XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSDictionary *stops = @{}; + NSExpression *expression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, 11, %@)", stops]; + XCTAssertThrowsSpecificNamed(expression.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } } - (void)testMatchExpressionObject { - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_MATCH(2 - 1, %@, %@, %@, %@, 'default')", MLNConstantExpression(@1), - MLNConstantExpression(@"one"), - MLNConstantExpression(@0), - MLNConstantExpression(@"zero")]; - NSExpression *predicate = [NSExpression expressionWithFormat:@"2 - 1"]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_match:', %@)", predicate, @[MLNConstantExpression(@1), - MLNConstantExpression(@"one"), - MLNConstantExpression(@0), - MLNConstantExpression(@"zero"), - MLNConstantExpression(@"default")]]; - NSArray *jsonExpression = @[@"match", @[@"-", @2, @1], @1, @"one", @0, @"zero", @"default"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_MATCH(2 * 1, %@, %@, 'default')", MLNConstantExpression(@1), MLNConstantExpression(@"one")]; - NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_MATCH(x, {'a', 'A'}, 'Apple', {'b', 'B'}, 'Banana', 'Kumquat')"]; - NSArray *jsonExpression = @[@"match", @[@"get", @"x"], @[@"a", @"A"], @"Apple", @[@"b", @"B"], @"Banana", @"Kumquat"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_MATCH(x, %@, 'Apple', %@, 'Banana', 'Kumquat')", - @[@"a", @"A"], @"Bb"]; - NSArray *jsonExpression = @[@"match", @[@"get", @"x"], @[@"a", @"A"], @"Apple", @"Bb", @"Banana", @"Kumquat"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression].description, expression.description); - } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = [NSExpression + expressionWithFormat:@"MLN_MATCH(2 - 1, %@, %@, %@, %@, 'default')", + MLNConstantExpression(@1), MLNConstantExpression(@"one"), + MLNConstantExpression(@0), MLNConstantExpression(@"zero")]; + NSExpression *predicate = [NSExpression expressionWithFormat:@"2 - 1"]; + NSExpression *compatibilityExpression = + [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_match:', %@)", predicate, @[ + MLNConstantExpression(@1), MLNConstantExpression(@"one"), MLNConstantExpression(@0), + MLNConstantExpression(@"zero"), MLNConstantExpression(@"default") + ]]; + NSArray *jsonExpression = @[ @"match", @[ @"-", @2, @1 ], @1, @"one", @0, @"zero", @"default" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression + expressionWithFormat:@"MLN_MATCH(2 * 1, %@, %@, 'default')", MLNConstantExpression(@1), + MLNConstantExpression(@"one")]; + NSArray *jsonExpression = @[ @"match", @[ @"*", @2, @1 ], @1, @"one", @"default" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression + expressionWithFormat:@"MLN_MATCH(x, {'a', 'A'}, 'Apple', {'b', 'B'}, 'Banana', 'Kumquat')"]; + NSArray *jsonExpression = @[ + @"match", @[ @"get", @"x" ], @[ @"a", @"A" ], @"Apple", @[ @"b", @"B" ], @"Banana", @"Kumquat" + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"MLN_MATCH(x, %@, 'Apple', %@, 'Banana', 'Kumquat')", + @[ @"a", @"A" ], @"Bb"]; + NSArray *jsonExpression = + @[ @"match", @[ @"get", @"x" ], @[ @"a", @"A" ], @"Apple", @"Bb", @"Banana", @"Kumquat" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression].description, + expression.description); + } } - (void)testCoalesceExpressionObject { - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_coalesce(%@)", - @[[NSExpression expressionForKeyPath:@"x"], - [NSExpression expressionForKeyPath:@"y"], - [NSExpression expressionForKeyPath:@"z"], - [NSExpression expressionForConstantValue:@0]]]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", @[[NSExpression expressionForKeyPath:@"x"], - [NSExpression expressionForKeyPath:@"y"], - [NSExpression expressionForKeyPath:@"z"], - [NSExpression expressionForConstantValue:@0]]]; - NSArray *jsonExpression = @[@"coalesce", @[@"get", @"x"], @[@"get", @"y"], @[@"get", @"z"], @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_coalesce(%@)", @[ + [NSExpression expressionForKeyPath:@"x"], [NSExpression expressionForKeyPath:@"y"], + [NSExpression expressionForKeyPath:@"z"], [NSExpression expressionForConstantValue:@0] + ]]; + NSExpression *compatibilityExpression = + [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_coalesce')", @[ + [NSExpression expressionForKeyPath:@"x"], [NSExpression expressionForKeyPath:@"y"], + [NSExpression expressionForKeyPath:@"z"], [NSExpression expressionForConstantValue:@0] + ]]; + NSArray *jsonExpression = + @[ @"coalesce", @[ @"get", @"x" ], @[ @"get", @"y" ], @[ @"get", @"z" ], @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testConditionalExpressionObject { - { - NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"]; - NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES]; - NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO]; - NSExpression *expression = [NSExpression expressionForConditional:conditional trueExpression:trueExpression falseExpression:falseExpression]; - NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"]; - NSArray *jsonExpression = @[@"case", @[@"==", @0, @1], @YES, @[@"case", @[@"==", @1, @2], @YES, @NO]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_IF(%@, %@, %@)", - [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], - MLNConstantExpression(@YES), - MLNConstantExpression(@NO)]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(%@, 'mgl_if:', %@)", [NSPredicate predicateWithFormat:@"1 = 2"], @[MLNConstantExpression(@YES), MLNConstantExpression(@NO)]]; - NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - expression = [NSExpression expressionWithFormat:@"TERNARY(1 = 2, YES, NO)"]; - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_IF(%@, %@, %@, %@, %@)", - [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 2"]], - MLNConstantExpression(@YES), - [NSExpression expressionWithFormat:@"%@", [NSPredicate predicateWithFormat:@"1 = 1"]], - MLNConstantExpression(@YES), - MLNConstantExpression(@NO)]; - NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @[@"==", @1, @1], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); - } - { - NSArray *jsonExpression = @[ - @"case", - @[ - @"<", - @[@"get", @"area"], - @80000 - ], - @[@"get", @"abbr"], - @[@"get", @"name_en"] - ]; - NSExpression *expression = [NSExpression expressionWithFormat:@"TERNARY(area < 80000, abbr, name_en)"]; - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - } + { + NSPredicate *conditional = [NSPredicate predicateWithFormat:@"1 = 2"]; + NSExpression *trueExpression = [NSExpression expressionForConstantValue:@YES]; + NSExpression *falseExpression = [NSExpression expressionForConstantValue:@NO]; + NSExpression *expression = [NSExpression expressionForConditional:conditional + trueExpression:trueExpression + falseExpression:falseExpression]; + NSArray *jsonExpression = @[ @"case", @[ @"==", @1, @2 ], @YES, @NO ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects( + [NSExpression expressionWithFormat:@"TERNARY(1 = 2, TRUE, FALSE)"].mgl_jsonExpressionObject, + jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"TERNARY(0 = 1, TRUE, TERNARY(1 = 2, TRUE, FALSE))"]; + NSArray *jsonExpression = + @[ @"case", @[ @"==", @0, @1 ], @YES, @[ @"case", @[ @"==", @1, @2 ], @YES, @NO ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = [NSExpression + expressionWithFormat:@"MLN_IF(%@, %@, %@)", + [NSExpression + expressionWithFormat:@"%@", + [NSPredicate predicateWithFormat:@"1 = 2"]], + MLNConstantExpression(@YES), MLNConstantExpression(@NO)]; + NSExpression *compatibilityExpression = [NSExpression + expressionWithFormat:@"FUNCTION(%@, 'mgl_if:', %@)", + [NSPredicate predicateWithFormat:@"1 = 2"], + @[ MLNConstantExpression(@YES), MLNConstantExpression(@NO) ]]; + NSArray *jsonExpression = @[ @"case", @[ @"==", @1, @2 ], @YES, @NO ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + expression = [NSExpression expressionWithFormat:@"TERNARY(1 = 2, YES, NO)"]; + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + } + { + NSExpression *expression = [NSExpression + expressionWithFormat:@"MLN_IF(%@, %@, %@, %@, %@)", + [NSExpression + expressionWithFormat:@"%@", + [NSPredicate predicateWithFormat:@"1 = 2"]], + MLNConstantExpression(@YES), + [NSExpression + expressionWithFormat:@"%@", + [NSPredicate predicateWithFormat:@"1 = 1"]], + MLNConstantExpression(@YES), MLNConstantExpression(@NO)]; + NSArray *jsonExpression = @[ @"case", @[ @"==", @1, @2 ], @YES, @[ @"==", @1, @1 ], @YES, @NO ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } + { + NSArray *jsonExpression = @[ + @"case", @[ @"<", @[ @"get", @"area" ], @80000 ], @[ @"get", @"abbr" ], + @[ @"get", @"name_en" ] + ]; + NSExpression *expression = + [NSExpression expressionWithFormat:@"TERNARY(area < 80000, abbr, name_en)"]; + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } } - (void)testLookupExpressionObject { - { - NSExpression *array = [NSExpression expressionForAggregate:@[MLNConstantExpression(@9), - MLNConstantExpression(@8), - MLNConstantExpression(@7)]]; - NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" - arguments:@[array, MLNConstantExpression(@"FIRST")]]; - NSArray *jsonExpression = @[@"at", @0, @[ @"literal", @[@9, @8, @7]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - } - { - NSExpression *array = [NSExpression expressionForAggregate:@[MLNConstantExpression(@9), - MLNConstantExpression(@8), - MLNConstantExpression(@7)]]; - NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" - arguments:@[array, MLNConstantExpression(@"LAST")]]; - NSArray *jsonExpression = @[@"at", @[@"-", @[@"length", @[ @"literal", @[@9, @8, @7]]], @1], @[ @"literal", @[@9, @8, @7]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - } - { - NSExpression *array = [NSExpression expressionForAggregate:@[MLNConstantExpression(@9), - MLNConstantExpression(@8), - MLNConstantExpression(@7)]]; - NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" - arguments:@[array, MLNConstantExpression(@"SIZE")]]; - NSArray *jsonExpression = @[@"length", @[ @"literal", @[@9, @8, @7]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - } - { - NSExpression *array = [NSExpression expressionForAggregate:@[MLNConstantExpression(@9), - MLNConstantExpression(@8), - MLNConstantExpression(@7)]]; - NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" - arguments:@[array, MLNConstantExpression(@1)]]; - NSArray *jsonExpression = @[@"at", @1, @[ @"literal", @[@9, @8, @7]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *array = [NSExpression expressionForAggregate:@[MLNConstantExpression(@9), - MLNConstantExpression(@8), - MLNConstantExpression(@7)]]; - NSExpression *expression = [NSExpression expressionForFunction:@"objectFrom:withIndex:" - arguments:@[array, [NSExpression expressionForKeyPath:@"x"]]]; - NSArray *jsonExpression = @[@"at", @[@"get", @"x"], @[ @"literal", @[@9, @8, @7]]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" - arguments:@[[NSExpression expressionForEvaluatedObject], - [NSExpression expressionForConstantValue:@"x"]]]; - NSExpression *compatibilityExpression = [NSExpression expressionWithFormat:@"FUNCTION(self, 'mgl_has:', 'x')"]; - NSArray *jsonExpression = @[@"has", @"x"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" - arguments:@[MLNConstantExpression(@{@"x": MLNConstantExpression(@0)}), - MLNConstantExpression(@"x")]]; - NSArray *jsonExpression = @[@"has", @"x", @[@"literal", @{@"x": @0}]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" - arguments:@[[NSExpression expressionForVariable:@"featureAttributes"], - [NSExpression expressionForConstantValue:@"x"]]]; - NSArray *jsonExpression = @[@"has", @"x", @[@"properties"]]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression; - expression = [NSExpression expressionWithFormat:@"TERNARY(key != nil, 1, 0)"]; - NSArray *jsonExpression = @[@"case", @[@"!=", @[@"get", @"key"], [NSNull null]], @1, @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:@{} context:nil], @NO); - XCTAssertEqualObjects([expression expressionValueWithObject:@{@"key": @"🗝"} context:nil], @YES); - } - { - NSDictionary *dictionary = @{@"key": @"🔑"}; - NSExpression *expression; - expression = [NSExpression expressionWithFormat:@"TERNARY(%@.key != nil, 1, 0)", dictionary]; - NSArray *jsonExpression = @[@"case", @[@"!=", @[@"get", @"key", @[@"literal", dictionary]], [NSNull null]], @1, @0]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - // The dictionary isn’t equal enough. - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression].description, expression.description); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); - } + { + NSExpression *array = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@9), MLNConstantExpression(@8), MLNConstantExpression(@7) + ]]; + NSExpression *expression = + [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ array, MLNConstantExpression(@"FIRST") ]]; + NSArray *jsonExpression = @[ @"at", @0, @[ @"literal", @[ @9, @8, @7 ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } + { + NSExpression *array = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@9), MLNConstantExpression(@8), MLNConstantExpression(@7) + ]]; + NSExpression *expression = + [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ array, MLNConstantExpression(@"LAST") ]]; + NSArray *jsonExpression = @[ + @"at", @[ @"-", @[ @"length", @[ @"literal", @[ @9, @8, @7 ] ] ], @1 ], + @[ @"literal", @[ @9, @8, @7 ] ] + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } + { + NSExpression *array = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@9), MLNConstantExpression(@8), MLNConstantExpression(@7) + ]]; + NSExpression *expression = + [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ array, MLNConstantExpression(@"SIZE") ]]; + NSArray *jsonExpression = @[ @"length", @[ @"literal", @[ @9, @8, @7 ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + } + { + NSExpression *array = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@9), MLNConstantExpression(@8), MLNConstantExpression(@7) + ]]; + NSExpression *expression = + [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ array, MLNConstantExpression(@1) ]]; + NSArray *jsonExpression = @[ @"at", @1, @[ @"literal", @[ @9, @8, @7 ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *array = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@9), MLNConstantExpression(@8), MLNConstantExpression(@7) + ]]; + NSExpression *expression = + [NSExpression expressionForFunction:@"objectFrom:withIndex:" + arguments:@[ array, [NSExpression expressionForKeyPath:@"x"] ]]; + NSArray *jsonExpression = @[ @"at", @[ @"get", @"x" ], @[ @"literal", @[ @9, @8, @7 ] ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[ + [NSExpression expressionForEvaluatedObject], + [NSExpression expressionForConstantValue:@"x"] + ]]; + NSExpression *compatibilityExpression = + [NSExpression expressionWithFormat:@"FUNCTION(self, 'mgl_has:', 'x')"]; + NSArray *jsonExpression = @[ @"has", @"x" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[ + MLNConstantExpression( + @{@"x" : MLNConstantExpression(@0)}), + MLNConstantExpression(@"x") + ]]; + NSArray *jsonExpression = @[ @"has", @"x", @[ @"literal", @{@"x" : @0} ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionForFunction:@"mgl_does:have:" + arguments:@[ + [NSExpression expressionForVariable:@"featureAttributes"], + [NSExpression expressionForConstantValue:@"x"] + ]]; + NSArray *jsonExpression = @[ @"has", @"x", @[ @"properties" ] ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression; + expression = [NSExpression expressionWithFormat:@"TERNARY(key != nil, 1, 0)"]; + NSArray *jsonExpression = @[ @"case", @[ @"!=", @[ @"get", @"key" ], [NSNull null] ], @1, @0 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:@{} context:nil], @NO); + XCTAssertEqualObjects([expression expressionValueWithObject:@{@"key" : @"🗝"} context:nil], + @YES); + } + { + NSDictionary *dictionary = @{@"key" : @"🔑"}; + NSExpression *expression; + expression = [NSExpression expressionWithFormat:@"TERNARY(%@.key != nil, 1, 0)", dictionary]; + NSArray *jsonExpression = @[ + @"case", @[ @"!=", @[ @"get", @"key", @[ @"literal", dictionary ] ], [NSNull null] ], @1, @0 + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + // The dictionary isn’t equal enough. + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression].description, + expression.description); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @YES); + } } - (void)testFormatExpressionObject { - { - MLNAttributedExpression *attribute1 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"foo"] - fontNames:nil - fontScale:@(1.2)]; - MLNAttributedExpression *attribute2 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"biz"] - fontNames:nil - fontScale:@(1.0)]; - MLNAttributedExpression *attribute3 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"bar"] - fontNames:nil - fontScale:@(0.8)]; - MLNAttributedExpression *attribute4 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"\r"] - fontNames:@[] - fontScale:nil]; - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@, %@, %@, %@)", - MLNConstantExpression(attribute1), - MLNConstantExpression(attribute4), - MLNConstantExpression(attribute2), - MLNConstantExpression(attribute3)]; - NSArray *jsonExpression = @[@"format", @"foo", @{@"font-scale": @1.2}, @"\r", @{}, @"biz", @{@"font-scale": @1.0}, @"bar", @{@"font-scale": @0.8}]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - MLNAttributedExpression *attribute1 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"foo"] - fontNames:nil - fontScale:@(1.2)]; - MLNAttributedExpression *attribute2 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"biz"] - fontNames:nil - fontScale:@(1.0)]; - MLNAttributedExpression *attribute3 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"bar"] - fontNames:nil - fontScale:@(0.8)]; - MLNAttributedExpression *attribute4 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"\n"] - fontNames:@[] - fontScale:nil]; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@, %@, %@, %@)", - MLNConstantExpression(attribute1), - MLNConstantExpression(attribute4), - MLNConstantExpression(attribute2), - MLNConstantExpression(attribute3)]; - NSArray *jsonExpression = @[@"format", @"foo", @{@"font-scale": @1.2}, @"\n", @{}, @"biz", @{@"font-scale": @1.0}, @"bar", @{@"font-scale": @0.8}]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - MLNAttributedExpression *attribute1 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"foo"] - fontNames:nil - fontScale:@(1.2)]; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + { + MLNAttributedExpression *attribute1 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"foo"] + fontNames:nil + fontScale:@(1.2)]; + MLNAttributedExpression *attribute2 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"biz"] + fontNames:nil + fontScale:@(1.0)]; + MLNAttributedExpression *attribute3 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"bar"] + fontNames:nil + fontScale:@(0.8)]; + MLNAttributedExpression *attribute4 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"\r"] + fontNames:@[] + fontScale:nil]; +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@, %@, %@, %@)", MLNConstantExpression(attribute1), + MLNConstantExpression(attribute4), MLNConstantExpression(attribute2), + MLNConstantExpression(attribute3)]; + NSArray *jsonExpression = @[ + @"format", @"foo", + @{@"font-scale" : @1.2}, @"\r", @{}, @"biz", + @{@"font-scale" : @1.0}, @"bar", + @{@"font-scale" : @0.8} + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + MLNAttributedExpression *attribute1 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"foo"] + fontNames:nil + fontScale:@(1.2)]; + MLNAttributedExpression *attribute2 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"biz"] + fontNames:nil + fontScale:@(1.0)]; + MLNAttributedExpression *attribute3 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"bar"] + fontNames:nil + fontScale:@(0.8)]; + MLNAttributedExpression *attribute4 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"\n"] + fontNames:@[] + fontScale:nil]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@, %@, %@, %@)", MLNConstantExpression(attribute1), + MLNConstantExpression(attribute4), MLNConstantExpression(attribute2), + MLNConstantExpression(attribute3)]; + NSArray *jsonExpression = @[ + @"format", @"foo", + @{@"font-scale" : @1.2}, @"\n", @{}, @"biz", + @{@"font-scale" : @1.0}, @"bar", + @{@"font-scale" : @0.8} + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + MLNAttributedExpression *attribute1 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"foo"] + fontNames:nil + fontScale:@(1.2)]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSExpression *compatibilityExpression = [NSExpression expressionForFunction:@"mgl_attributed:" arguments:@[MLNConstantExpression(attribute1)]]; - NSArray *jsonExpression = @[@"format", @"foo", @{@"font-scale": @1.2}]; - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, expression.mgl_jsonExpressionObject); - XCTAssertEqualObjects(compatibilityExpression, expression); - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] initWithExpression:[NSExpression expressionForConstantValue:@"foo"] - attributes:@{ MLNFontScaleAttribute: MLNConstantExpression(@(1.2)), - MLNFontColorAttribute: MLNConstantExpression(@"yellow") }] ; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + NSExpression *compatibilityExpression = + [NSExpression expressionForFunction:@"mgl_attributed:" + arguments:@[ MLNConstantExpression(attribute1) ]]; + NSArray *jsonExpression = @[ @"format", @"foo", @{@"font-scale" : @1.2} ]; + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, + expression.mgl_jsonExpressionObject); + XCTAssertEqualObjects(compatibilityExpression, expression); + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] + initWithExpression:[NSExpression expressionForConstantValue:@"foo"] + attributes:@{ + MLNFontScaleAttribute : MLNConstantExpression(@(1.2)), + MLNFontColorAttribute : MLNConstantExpression(@"yellow") + }]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSExpression *compatibilityExpression = [NSExpression expressionForFunction:@"mgl_attributed:" arguments:@[MLNConstantExpression(attribute1)]]; - NSArray *jsonExpression = @[ @"format", @"foo", @{ @"font-scale": @1.2, @"text-color": @"yellow" } ]; - XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, expression.mgl_jsonExpressionObject); - XCTAssertEqualObjects(compatibilityExpression, expression); - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] initWithExpression:[NSExpression expressionForConstantValue:@"foo"]] ; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + NSExpression *compatibilityExpression = + [NSExpression expressionForFunction:@"mgl_attributed:" + arguments:@[ MLNConstantExpression(attribute1) ]]; + NSArray *jsonExpression = + @[ @"format", @"foo", + @{@"font-scale" : @1.2, + @"text-color" : @"yellow"} ]; + XCTAssertEqualObjects(compatibilityExpression.mgl_jsonExpressionObject, + expression.mgl_jsonExpressionObject); + XCTAssertEqualObjects(compatibilityExpression, expression); + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] + initWithExpression:[NSExpression expressionForConstantValue:@"foo"]]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSArray *jsonExpression = @[ @"format", @"foo", @{ } ]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *fontNames = [NSExpression expressionForAggregate:@[ MLNConstantExpression(@"DIN Offc Pro Bold"), MLNConstantExpression(@"Arial Unicode MS Bold") ]]; - MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] initWithExpression:[NSExpression expressionForConstantValue:@"foo"] - attributes:@{ MLNFontScaleAttribute: MLNConstantExpression(@(1.2)), - MLNFontColorAttribute: MLNConstantExpression(@"yellow"), - MLNFontNamesAttribute: fontNames - }] ; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + NSArray *jsonExpression = @[ @"format", @"foo", @{} ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *fontNames = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@"DIN Offc Pro Bold"), + MLNConstantExpression(@"Arial Unicode MS Bold") + ]]; + MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] + initWithExpression:[NSExpression expressionForConstantValue:@"foo"] + attributes:@{ + MLNFontScaleAttribute : MLNConstantExpression(@(1.2)), + MLNFontColorAttribute : MLNConstantExpression(@"yellow"), + MLNFontNamesAttribute : fontNames + }]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSArray *jsonExpression = @[ @"format", @"foo", @{ @"font-scale": @1.2, @"text-color": @"yellow" , @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ]]} ]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *fontNames = [NSExpression expressionForAggregate:@[ MLNConstantExpression(@"DIN Offc Pro Bold"), MLNConstantExpression(@"Arial Unicode MS Bold") ]]; - MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] initWithExpression:[NSExpression expressionForConstantValue:@"foo"] - attributes:@{ MLNFontScaleAttribute: MLNConstantExpression(@(1.2)), - MLNFontColorAttribute: MLNConstantExpression([MLNColor redColor]), - MLNFontNamesAttribute: fontNames - }] ; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + NSArray *jsonExpression = @[ + @"format", @"foo", @{ + @"font-scale" : @1.2, + @"text-color" : @"yellow", + @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ] ] + } + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *fontNames = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@"DIN Offc Pro Bold"), + MLNConstantExpression(@"Arial Unicode MS Bold") + ]]; + MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] + initWithExpression:[NSExpression expressionForConstantValue:@"foo"] + attributes:@{ + MLNFontScaleAttribute : MLNConstantExpression(@(1.2)), + MLNFontColorAttribute : MLNConstantExpression([MLNColor redColor]), + MLNFontNamesAttribute : fontNames + }]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSArray *jsonExpression = @[ @"format", @"foo", @{ @"font-scale": @1.2, @"text-color": @[@"rgb", @255, @0, @0] , @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ]]} ]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *fontNames = [NSExpression expressionForAggregate:@[ MLNConstantExpression(@"DIN Offc Pro Bold"), MLNConstantExpression(@"Arial Unicode MS Bold") ]]; - MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] initWithExpression:[NSExpression expressionWithFormat:@"CAST(x, 'NSString')"] - attributes:@{ MLNFontScaleAttribute: MLNConstantExpression(@(1.2)), - MLNFontColorAttribute: MLNConstantExpression([MLNColor redColor]), - MLNFontNamesAttribute: fontNames - }] ; - NSExpression *expression = [NSExpression expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; + NSArray *jsonExpression = @[ + @"format", @"foo", @{ + @"font-scale" : @1.2, + @"text-color" : @[ @"rgb", @255, @0, @0 ], + @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ] ] + } + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *fontNames = [NSExpression expressionForAggregate:@[ + MLNConstantExpression(@"DIN Offc Pro Bold"), + MLNConstantExpression(@"Arial Unicode MS Bold") + ]]; + MLNAttributedExpression *attribute1 = [[MLNAttributedExpression alloc] + initWithExpression:[NSExpression expressionWithFormat:@"CAST(x, 'NSString')"] + attributes:@{ + MLNFontScaleAttribute : MLNConstantExpression(@(1.2)), + MLNFontColorAttribute : MLNConstantExpression([MLNColor redColor]), + MLNFontNamesAttribute : fontNames + }]; + NSExpression *expression = [NSExpression + expressionWithFormat:@"mgl_attributed:(%@)", MLNConstantExpression(attribute1)]; - NSArray *jsonExpression = @[ @"format", @[@"to-string", @[@"get", @"x"]], @{ @"font-scale": @1.2, @"text-color": @[@"rgb", @255, @0, @0] , @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ]]} ]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - MLNAttributedExpression *attribute1 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"foo"] - fontNames:nil - fontScale:@(1.2)]; - MLNAttributedExpression *attribute2 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"biz"] - fontNames:nil - fontScale:@(1.0)]; - MLNAttributedExpression *attribute3 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"bar"] - fontNames:nil - fontScale:@(0.8)]; - MLNAttributedExpression *attribute4 = [MLNAttributedExpression attributedExpression:[NSExpression expressionForConstantValue:@"\n"] - fontNames:@[] - fontScale:nil]; - NSExpression *expression = [NSExpression mgl_expressionForAttributedExpressions:@[MLNConstantExpression(attribute1), - MLNConstantExpression(attribute4), - MLNConstantExpression(attribute2), - MLNConstantExpression(attribute3)]]; - NSArray *jsonExpression = @[@"format", @"foo", @{@"font-scale": @1.2}, @"\n", @{}, @"biz", @{@"font-scale": @1.0}, @"bar", @{@"font-scale": @0.8}]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + NSArray *jsonExpression = @[ + @"format", @[ @"to-string", @[ @"get", @"x" ] ], @{ + @"font-scale" : @1.2, + @"text-color" : @[ @"rgb", @255, @0, @0 ], + @"text-font" : @[ @"literal", @[ @"DIN Offc Pro Bold", @"Arial Unicode MS Bold" ] ] + } + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + MLNAttributedExpression *attribute1 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"foo"] + fontNames:nil + fontScale:@(1.2)]; + MLNAttributedExpression *attribute2 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"biz"] + fontNames:nil + fontScale:@(1.0)]; + MLNAttributedExpression *attribute3 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"bar"] + fontNames:nil + fontScale:@(0.8)]; + MLNAttributedExpression *attribute4 = [MLNAttributedExpression + attributedExpression:[NSExpression expressionForConstantValue:@"\n"] + fontNames:@[] + fontScale:nil]; + NSExpression *expression = [NSExpression mgl_expressionForAttributedExpressions:@[ + MLNConstantExpression(attribute1), MLNConstantExpression(attribute4), + MLNConstantExpression(attribute2), MLNConstantExpression(attribute3) + ]]; + NSArray *jsonExpression = @[ + @"format", @"foo", + @{@"font-scale" : @1.2}, @"\n", @{}, @"biz", + @{@"font-scale" : @1.0}, @"bar", + @{@"font-scale" : @0.8} + ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } - (void)testGenericExpressionObject { - { - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('random', 1, 2, 3, 4, 5)"]; - NSArray *jsonExpression = @[@"random", @1, @2, @3, @4, @5]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('random', 1, 2, 3, 4)"]; - XCTAssertThrowsSpecificNamed([expression expressionValueWithObject:nil context:nil], NSException, NSInvalidArgumentException); - } - { - NSArray *arguments = @[ - MLNConstantExpression(@"one"), MLNConstantExpression(@1), - [NSExpression expressionForVariable:@"one"], - ]; - NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_LET" arguments:arguments]; - NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_LET:" arguments:arguments]; - XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, unaryExpression.mgl_jsonExpressionObject); - } - { - NSArray *arguments = @[ - [NSExpression expressionForVariable:@"x"], - MLNConstantExpression(@YES), MLNConstantExpression(@"yes"), - MLNConstantExpression(@NO), MLNConstantExpression(@"no"), - ]; - NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_MATCH" arguments:arguments]; - NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_MATCH:" arguments:arguments]; - XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, unaryExpression.mgl_jsonExpressionObject); - } - { - NSArray *arguments = @[ - [NSPredicate predicateWithValue:YES], - MLNConstantExpression(@"yes"), MLNConstantExpression(@"no"), - ]; - NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_IF" arguments:arguments]; - NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_IF:" arguments:arguments]; - XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, unaryExpression.mgl_jsonExpressionObject); - } - { - NSArray *arguments = @[MLNConstantExpression(@"zoom")]; - NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_FUNCTION" arguments:arguments]; - NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_FUNCTION:" arguments:arguments]; - XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, unaryExpression.mgl_jsonExpressionObject); - } + { +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *expression = + [NSExpression expressionWithFormat:@"MLN_FUNCTION('random', 1, 2, 3, 4, 5)"]; + NSArray *jsonExpression = @[ @"random", @1, @2, @3, @4, @5 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = + [NSExpression expressionWithFormat:@"MLN_FUNCTION('random', 1, 2, 3, 4)"]; + XCTAssertThrowsSpecificNamed([expression expressionValueWithObject:nil context:nil], + NSException, NSInvalidArgumentException); + } + { + NSArray *arguments = @[ + MLNConstantExpression(@"one"), + MLNConstantExpression(@1), + [NSExpression expressionForVariable:@"one"], + ]; + NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_LET" + arguments:arguments]; + NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_LET:" + arguments:arguments]; + XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, + unaryExpression.mgl_jsonExpressionObject); + } + { + NSArray *arguments = @[ + [NSExpression expressionForVariable:@"x"], + MLNConstantExpression(@YES), + MLNConstantExpression(@"yes"), + MLNConstantExpression(@NO), + MLNConstantExpression(@"no"), + ]; + NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_MATCH" + arguments:arguments]; + NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_MATCH:" + arguments:arguments]; + XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, + unaryExpression.mgl_jsonExpressionObject); + } + { + NSArray *arguments = @[ + [NSPredicate predicateWithValue:YES], + MLNConstantExpression(@"yes"), + MLNConstantExpression(@"no"), + ]; + NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_IF" + arguments:arguments]; + NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_IF:" + arguments:arguments]; + XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, + unaryExpression.mgl_jsonExpressionObject); + } + { + NSArray *arguments = @[ MLNConstantExpression(@"zoom") ]; + NSExpression *nullaryExpression = [NSExpression expressionForFunction:@"MLN_FUNCTION" + arguments:arguments]; + NSExpression *unaryExpression = [NSExpression expressionForFunction:@"MLN_FUNCTION:" + arguments:arguments]; + XCTAssertEqualObjects(nullaryExpression.mgl_jsonExpressionObject, + unaryExpression.mgl_jsonExpressionObject); + } } // MARK: - Localization tests - (void)testLocalization { - { - NSExpression *original = MLNConstantExpression(@""); - NSExpression *expected = original; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *original = MLNConstantExpression(@"Old MacDonald"); - NSExpression *expected = original; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *original = MLNConstantExpression(@"{name_en}"); - NSExpression *expected = original; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *keyExpression = [NSExpression expressionForKeyPath:@"name_en"]; - MLNAttributedExpression *attributedExpression = [MLNAttributedExpression attributedExpression:keyExpression attributes:@{}]; - NSExpression *original = [NSExpression expressionForConstantValue:attributedExpression]; + { + NSExpression *original = MLNConstantExpression(@""); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = MLNConstantExpression(@"Old MacDonald"); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = MLNConstantExpression(@"{name_en}"); + NSExpression *expected = original; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *keyExpression = [NSExpression expressionForKeyPath:@"name_en"]; + MLNAttributedExpression *attributedExpression = + [MLNAttributedExpression attributedExpression:keyExpression attributes:@{}]; + NSExpression *original = [NSExpression expressionForConstantValue:attributedExpression]; - #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - #endif - NSExpression *coalesceExpression = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; - MLNAttributedExpression *expectedAttributedExpression = [MLNAttributedExpression attributedExpression:coalesceExpression attributes:@{}]; - NSExpression *expected = [NSExpression expressionForConstantValue:expectedAttributedExpression]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; - NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]], expected); - } - { - NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_fr", @"name"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"fr-CA"]], expected); - } - { - NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K, %K, %K})", @"name_zh-Hans", @"name_zh-CN", @"name_zh", @"name"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"zh-Hans"]], expected); - } - { - NSExpression *original = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_coalesce:({mgl_coalesce:({name_en, name}), mgl_coalesce:({name_en, name})})"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); - } - { - NSExpression *original = [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_coalesce:({mgl_coalesce:({name_ja, name}), mgl_coalesce:({name_ja, name})})"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"ja-JP"]], expected); - } - { - NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; - NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"tlh"]], expected); - } - { - NSExpression *original = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ - @1: [NSExpression expressionForKeyPath:@"abbr"], - @2: @"…", - @3: [NSExpression expressionForKeyPath:@"name_fr"], +#if TARGET_OS_IPHONE + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); +#endif + NSExpression *coalesceExpression = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; + MLNAttributedExpression *expectedAttributedExpression = + [MLNAttributedExpression attributedExpression:coalesceExpression attributes:@{}]; + NSExpression *expected = [NSExpression expressionForConstantValue:expectedAttributedExpression]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; + XCTAssertEqualObjects( + [original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]], + expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_fr", @"name"]; + XCTAssertEqualObjects( + [original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"fr-CA"]], + expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K, %K, %K})", @"name_zh-Hans", + @"name_zh-CN", @"name_zh", @"name"]; + XCTAssertEqualObjects( + [original + mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"zh-Hans"]], + expected); + } + { + NSExpression *original = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; + NSExpression *expected = [NSExpression + expressionWithFormat: + @"mgl_coalesce:({mgl_coalesce:({name_en, name}), mgl_coalesce:({name_en, name})})"]; + XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:nil], expected); + } + { + NSExpression *original = + [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_en", @"name"]; + NSExpression *expected = [NSExpression + expressionWithFormat: + @"mgl_coalesce:({mgl_coalesce:({name_ja, name}), mgl_coalesce:({name_ja, name})})"]; + XCTAssertEqualObjects( + [original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"ja-JP"]], + expected); + } + { + NSExpression *original = [NSExpression expressionForKeyPath:@"name_en"]; + NSExpression *expected = [NSExpression expressionForKeyPath:@"name"]; + XCTAssertEqualObjects( + [original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"tlh"]], + expected); + } + { + NSExpression *original = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ + @1 : [NSExpression expressionForKeyPath:@"abbr"], + @2 : @"…", + @3 : [NSExpression expressionForKeyPath:@"name_fr"], }]; - NSExpression *expected = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ - @1: [NSExpression expressionForKeyPath:@"abbr"], - @2: @"…", - @3: [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_es", @"name"] + NSExpression *expected = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, short, %@)", @{ + @1 : [NSExpression expressionForKeyPath:@"abbr"], + @2 : @"…", + @3 : [NSExpression expressionWithFormat:@"mgl_coalesce({%K, %K})", @"name_es", @"name"] }]; - XCTAssertEqualObjects([original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"es-PR"]], expected); - } - { - NSArray *jsonExpression = @[ - @"step", - @[@"zoom"], - @[ - @"case", - @[ - @"<", - @[ - @"to-number", - @[@"get", @"area"] - ], - @80000 - ], - @[@"get", @"abbr"], - @[@"get", @"name_en"] - ], - @5, @[@"get", @"name_en"] - ]; - NSArray *localizedJSONExpression = @[ - @"step", - @[@"zoom"], - @[ - @"case", - @[ - @"<", - @[ - @"to-number", - @[@"get", @"area"] - ], - @80000 - ], - @[@"get", @"abbr"], - @[@"get", @"name"] - ], - @5, @[@"get", @"name"] - ]; - NSExpression *expression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; - NSExpression *localizedExpression = [expression mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]]; - XCTAssertEqualObjects(localizedExpression.mgl_jsonExpressionObject, localizedJSONExpression); - } + XCTAssertEqualObjects( + [original mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"es-PR"]], + expected); + } + { + NSArray *jsonExpression = @[ + @"step", @[ @"zoom" ], + @[ + @"case", @[ @"<", @[ @"to-number", @[ @"get", @"area" ] ], @80000 ], @[ @"get", @"abbr" ], + @[ @"get", @"name_en" ] + ], + @5, @[ @"get", @"name_en" ] + ]; + NSArray *localizedJSONExpression = @[ + @"step", @[ @"zoom" ], + @[ + @"case", @[ @"<", @[ @"to-number", @[ @"get", @"area" ] ], @80000 ], @[ @"get", @"abbr" ], + @[ @"get", @"name" ] + ], + @5, @[ @"get", @"name" ] + ]; + NSExpression *expression = [NSExpression expressionWithMLNJSONObject:jsonExpression]; + NSExpression *localizedExpression = + [expression mgl_expressionLocalizedIntoLocale:[NSLocale localeWithLocaleIdentifier:@"mul"]]; + XCTAssertEqualObjects(localizedExpression.mgl_jsonExpressionObject, localizedJSONExpression); + } } - (void)testConvenienceInitializers { - { - NSExpression *expression = [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"1 = 2"] - trueExpression:MLNConstantExpression(@YES) - falseExpresssion:MLNConstantExpression(@NO)]; + { + NSExpression *expression = + [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"1 = 2"] + trueExpression:MLNConstantExpression(@YES) + falseExpresssion:MLNConstantExpression(@NO)]; - NSArray *jsonExpression = @[@"case", @[@"==", @1, @2], @YES, @NO]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); - } - { - NSDictionary *stops = @{@0: MLNConstantExpression(@111), @1: MLNConstantExpression(@1111)}; - NSExpression *expression = [NSExpression mgl_expressionForSteppingExpression:[NSExpression expressionForKeyPath:@"x"] - fromExpression:[NSExpression expressionForConstantValue:@11] - stops:[NSExpression expressionForConstantValue:stops]]; - NSArray *jsonExpression = @[@"step", @[@"get", @"x"], @11, @0, @111, @1, @1111]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *stops = @{@0: MLNConstantExpression(@100), @10: MLNConstantExpression(@200)}; - NSExpression *expression = [NSExpression mgl_expressionForInterpolatingExpression:[NSExpression expressionForKeyPath:@"x"] - withCurveType:MLNExpressionInterpolationModeLinear - parameters:nil - stops:[NSExpression expressionForConstantValue:stops]]; - NSArray *jsonExpression = @[@"interpolate", @[@"linear"], @[@"get", @"x"], @0, @100, @10, @200]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSExpression *expression = [[NSExpression expressionForConstantValue:@"Old"] mgl_expressionByAppendingExpression:[NSExpression expressionForConstantValue:@"MacDonald"]]; - - NSArray *jsonExpression = @[@"concat", @"Old", @"MacDonald"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } - { - NSDictionary *values = @{ MLNConstantExpression(@1): MLNConstantExpression(@"one") }; - NSExpression *expression = [NSExpression mgl_expressionForMatchingExpression:[NSExpression expressionWithFormat:@"2 * 1"] - inDictionary:values - defaultExpression:[NSExpression expressionForConstantValue:@"default"]]; - NSArray *jsonExpression = @[@"match", @[@"*", @2, @1], @1, @"one", @"default"]; - XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); - } + NSArray *jsonExpression = @[ @"case", @[ @"==", @1, @2 ], @YES, @NO ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @NO); + } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@111), @1 : MLNConstantExpression(@1111)}; + NSExpression *expression = [NSExpression + mgl_expressionForSteppingExpression:[NSExpression expressionForKeyPath:@"x"] + fromExpression:[NSExpression expressionForConstantValue:@11] + stops:[NSExpression expressionForConstantValue:stops]]; + NSArray *jsonExpression = @[ @"step", @[ @"get", @"x" ], @11, @0, @111, @1, @1111 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *stops = @{@0 : MLNConstantExpression(@100), @10 : MLNConstantExpression(@200)}; + NSExpression *expression = [NSExpression + mgl_expressionForInterpolatingExpression:[NSExpression expressionForKeyPath:@"x"] + withCurveType:MLNExpressionInterpolationModeLinear + parameters:nil + stops:[NSExpression expressionForConstantValue:stops]]; + NSArray *jsonExpression = + @[ @"interpolate", @[ @"linear" ], @[ @"get", @"x" ], @0, @100, @10, @200 ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSExpression *expression = [[NSExpression expressionForConstantValue:@"Old"] + mgl_expressionByAppendingExpression:[NSExpression expressionForConstantValue:@"MacDonald"]]; + NSArray *jsonExpression = @[ @"concat", @"Old", @"MacDonald" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([expression expressionValueWithObject:nil context:nil], @"OldMacDonald"); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } + { + NSDictionary *values = @{MLNConstantExpression(@1) : MLNConstantExpression(@"one")}; + NSExpression *expression = [NSExpression + mgl_expressionForMatchingExpression:[NSExpression expressionWithFormat:@"2 * 1"] + inDictionary:values + defaultExpression:[NSExpression expressionForConstantValue:@"default"]]; + NSArray *jsonExpression = @[ @"match", @[ @"*", @2, @1 ], @1, @"one", @"default" ]; + XCTAssertEqualObjects(expression.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSExpression expressionWithMLNJSONObject:jsonExpression], expression); + } } // MARK: - Objective-C Example Code for Expected Failures - (void)testExpectPassObjC { - XCTAssertNotNil(@1); + XCTAssertNotNil(@1); } /// https://developer.apple.com/documentation/xctest/expected_failures?language=objc - (void)testExpectFailureObjC { - XCTExpectFailure(@"Objective-C example - Anticipate known test failures to prevent failing tests from affecting your workflows."); - XCTAssertNotNil(nil); + XCTExpectFailure(@"Objective-C example - Anticipate known test failures to prevent failing tests " + @"from affecting your workflows."); + XCTAssertNotNil(nil); } - @end diff --git a/platform/darwin/test/MLNFeatureTests.mm b/platform/darwin/test/MLNFeatureTests.mm index 0fbdbef1c..0bb986acc 100644 --- a/platform/darwin/test/MLNFeatureTests.mm +++ b/platform/darwin/test/MLNFeatureTests.mm @@ -2,8 +2,8 @@ #import #import -#import "MLNFoundation_Private.h" #import "../../darwin/src/MLNFeature_Private.h" +#import "MLNFoundation_Private.h" @interface MLNFeatureTests : XCTestCase @@ -12,349 +12,390 @@ @implementation MLNFeatureTests - (void)testGeometryConversion { - std::vector features; + std::vector features; - mbgl::Point point = { -90.066667, 29.95 }; - features.push_back(mbgl::Feature { point }); + mbgl::Point point = {-90.066667, 29.95}; + features.push_back(mbgl::Feature{point}); - mbgl::LineString lineString = { - { -84.516667, 39.1 }, - { -90.066667, 29.95 }, - }; - features.push_back(mbgl::Feature { lineString }); + mbgl::LineString lineString = { + {-84.516667, 39.1}, + {-90.066667, 29.95}, + }; + features.push_back(mbgl::Feature{lineString}); - mbgl::Polygon polygon = { - { - { 1, 1 }, - { 4, 1 }, - { 4, 4 }, - { 1, 4 }, - }, - { - { 2, 2 }, - { 3, 2 }, - { 3, 3 }, - { 2, 3 }, - }, - }; - features.push_back(mbgl::Feature { polygon }); + mbgl::Polygon polygon = { + { + {1, 1}, + {4, 1}, + {4, 4}, + {1, 4}, + }, + { + {2, 2}, + {3, 2}, + {3, 3}, + {2, 3}, + }, + }; + features.push_back(mbgl::Feature{polygon}); - NSArray *> *shapes = MLNFeaturesFromMBGLFeatures(features); - XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes"); + NSArray *> *shapes = MLNFeaturesFromMBGLFeatures(features); + XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes"); - MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0]; - XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:pointShape.coordinate], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); + MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0]; + XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]); + XCTAssertEqualObjects( + [NSValue valueWithMLNCoordinate:pointShape.coordinate], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); - MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1]; - XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]); - XCTAssertEqual(polylineShape.pointCount, 2UL); - CLLocationCoordinate2D polylineCoordinates[2]; - [polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)]; - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[0]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[1]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); + MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1]; + XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]); + XCTAssertEqual(polylineShape.pointCount, 2UL); + CLLocationCoordinate2D polylineCoordinates[2]; + [polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)]; + XCTAssertEqualObjects( + [NSValue valueWithMLNCoordinate:polylineCoordinates[0]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]); + XCTAssertEqualObjects( + [NSValue valueWithMLNCoordinate:polylineCoordinates[1]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); - MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2]; - XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]); - XCTAssertEqual(polygonShape.pointCount, 4UL); - CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates; - XCTAssertNotEqual(polygonCoordinates, nil); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]); - NSArray *interiorPolygons = polygonShape.interiorPolygons; - XCTAssertEqual(interiorPolygons.count, 1UL); - MLNPolygon *interiorPolygon = interiorPolygons.firstObject; - XCTAssertEqual(interiorPolygon.pointCount, 4UL); - CLLocationCoordinate2D interiorPolygonCoordinates[4]; - [interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)]; - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]], - [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]); + MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2]; + XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]); + XCTAssertEqual(polygonShape.pointCount, 4UL); + CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates; + XCTAssertNotEqual(polygonCoordinates, nil); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]); + NSArray *interiorPolygons = polygonShape.interiorPolygons; + XCTAssertEqual(interiorPolygons.count, 1UL); + MLNPolygon *interiorPolygon = interiorPolygons.firstObject; + XCTAssertEqual(interiorPolygon.pointCount, 4UL); + CLLocationCoordinate2D interiorPolygonCoordinates[4]; + [interiorPolygon getCoordinates:interiorPolygonCoordinates + range:NSMakeRange(0, interiorPolygon.pointCount)]; + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]], + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]], + [NSValue valueWithMLNCoordinate: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; + 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 = MLNFeatureFromMBGLFeature(pointFeature); + id feature = MLNFeatureFromMBGLFeature(pointFeature); - XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]); + XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]); - id cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster); - XCTAssert(cluster); - XCTAssert(cluster.clusterIdentifier == 1); - XCTAssert(cluster.clusterPointCount == 5); + id cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster); + XCTAssert(cluster); + XCTAssert(cluster.clusterIdentifier == 1); + XCTAssert(cluster.clusterPointCount == 5); - XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]); + XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]); } - (void)testPropertyConversion { - std::vector features; + std::vector features; - mbgl::Point point = { -90.066667, 29.95 }; - mbgl::Feature pointFeature { point }; - pointFeature.id = { UINT64_MAX }; - pointFeature.properties["null"] = mapbox::feature::null_value; - 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 vector; - vector.push_back(true); - vector.push_back(false); - vector.push_back(true); - features.push_back(pointFeature); + mbgl::Point point = {-90.066667, 29.95}; + mbgl::Feature pointFeature{point}; + pointFeature.id = {UINT64_MAX}; + pointFeature.properties["null"] = mapbox::feature::null_value; + 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 vector; + vector.push_back(true); + vector.push_back(false); + vector.push_back(true); + features.push_back(pointFeature); - NSArray *> *shapes = MLNFeaturesFromMBGLFeatures(features); - XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes"); + NSArray *> *shapes = MLNFeaturesFromMBGLFeatures(features); + XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes"); - MLNShape *shape = shapes.firstObject; - XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]); - XCTAssertTrue([shape isKindOfClass:[MLNShape class]]); + MLNShape *shape = shapes.firstObject; + XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]); + XCTAssertTrue([shape isKindOfClass:[MLNShape 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"); + 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"]); + 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]]); - XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber"); - XCTAssertTrue(boolean.boolValue); - XCTAssertEqual(boolean, shape.attributes[@"bool"]); + NSNumber *boolean = [shape attributeForKey:@"bool"]; + XCTAssertNotNil(boolean); + XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]); + XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, + @"Boolean property should be converted to bool NSNumber"); + 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 *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 *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"]); + 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"]); + NSString *string = [shape attributeForKey:@"string"]; + XCTAssertNotNil(string); + XCTAssertTrue([string isKindOfClass:[NSString class]]); + XCTAssertEqualObjects(string, @"🚏"); + XCTAssertEqual(string, shape.attributes[@"string"]); } - (void)testPointFeatureGeoJSONDictionary { - MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; - CLLocationCoordinate2D coordinate = { 10, 10 }; - pointFeature.coordinate = coordinate; + MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; + CLLocationCoordinate2D coordinate = {10, 10}; + pointFeature.coordinate = coordinate; - // A GeoJSON feature - // when there are no identifier or properties - NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary]; + // 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"], @{}); + // 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"], @{}); - // when there is a string identifier - pointFeature.identifier = @"string-id"; + // 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); + // it has the identifier in the result + geoJSONFeature = [pointFeature geoJSONDictionary]; + XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier); - // when there are properties - pointFeature.attributes = @{@"name": @"name-value"}; + // 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); + // 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 }; - MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coords[] = {coord1, coord2}; + MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; - // A GeoJSON feature - NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary]; + // 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); + // 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 }; - MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3]; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coord3 = {0, 0}; + CLLocationCoordinate2D coords[] = {coord1, coord2, coord3}; + MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3]; - // A GeoJSON feature - NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary]; + // 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); + // 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 }; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coord3 = {0, 0}; + CLLocationCoordinate2D coords[] = {coord1, coord2, coord3}; - MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3]; - MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3]; + MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3]; + MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3]; - MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polyLine1, polyLine2]]; + MLNMultiPolylineFeature *multiPolylineFeature = + [MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polyLine1, polyLine2 ]]; - // A GeoJSON feature - NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary]; + // 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); + // 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 }; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coord3 = {0, 0}; + CLLocationCoordinate2D coords[] = {coord1, coord2, coord3}; - MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3]; - MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3]; + MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3]; + MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3]; - MLNMultiPolygonFeature *multiPolylineFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygon1, polygon2]]; + MLNMultiPolygonFeature *multiPolylineFeature = + [MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygon1, polygon2 ]]; - // A GeoJSON feature - NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary]; + // 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); + // 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 }; - MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3]; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coord3 = {0, 0}; + CLLocationCoordinate2D coords[] = {coord1, coord2, coord3}; + MLNPointCollectionFeature *pointCollectionFeature = + [MLNPointCollectionFeature 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); + // 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 { - MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; - CLLocationCoordinate2D pointCoordinate = { 10, 10 }; - pointFeature.coordinate = pointCoordinate; + MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; + CLLocationCoordinate2D pointCoordinate = {10, 10}; + pointFeature.coordinate = pointCoordinate; - CLLocationCoordinate2D coord1 = { 0, 0 }; - CLLocationCoordinate2D coord2 = { 10, 10 }; - CLLocationCoordinate2D coords[] = { coord1, coord2 }; - MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; + CLLocationCoordinate2D coord1 = {0, 0}; + CLLocationCoordinate2D coord2 = {10, 10}; + CLLocationCoordinate2D coords[] = {coord1, coord2}; + MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; - MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]]; + MLNShapeCollectionFeature *shapeCollectionFeature = + [MLNShapeCollectionFeature shapeCollectionWithShapes:@[ pointFeature, polylineFeature ]]; - // A GeoJSON feature - NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; + // A GeoJSON feature + NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; - // it has the correct geometry - NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection", - @"geometries": @[ - @{ @"geometry": @{@"type": @"Point", - @"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]}, - @"properties": @{}, - @"type": @"Feature", - }, - @{ @"geometry": @{@"type": @"LineString", - @"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], - @[@(coord2.longitude), @(coord2.latitude)]]}, - @"properties": @{}, - @"type": @"Feature", - } - ] - }; - XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); + // it has the correct geometry + NSDictionary *expectedGeometry = @{ + @"type" : @"GeometryCollection", + @"geometries" : @[ + @{ + @"geometry" : @{ + @"type" : @"Point", + @"coordinates" : @[ @(pointCoordinate.longitude), @(pointCoordinate.latitude) ] + }, + @"properties" : @{}, + @"type" : @"Feature", + }, + @{ + @"geometry" : @{ + @"type" : @"LineString", + @"coordinates" : @[ + @[ @(coord1.longitude), @(coord1.latitude) ], + @[ @(coord2.longitude), @(coord2.latitude) ] + ] + }, + @"properties" : @{}, + @"type" : @"Feature", + } + ] + }; + XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); - // When the shape collection is created with an empty array of shapes - shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; + // When the shape collection is created with an empty array of shapes + shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; - // it has the correct (empty) geometry - geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; - expectedGeometry = @{@"type": @"GeometryCollection", - @"geometries": @[]}; - XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); + // it has the correct (empty) geometry + geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; + expectedGeometry = @{@"type" : @"GeometryCollection", @"geometries" : @[]}; + XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); } @end diff --git a/platform/darwin/test/MLNGeometryTests.mm b/platform/darwin/test/MLNGeometryTests.mm index 63c560da2..b27b36000 100644 --- a/platform/darwin/test/MLNGeometryTests.mm +++ b/platform/darwin/test/MLNGeometryTests.mm @@ -9,203 +9,237 @@ @implementation MLNGeometryTests - (void)testCoordinateBoundsIsEmpty { - MLNCoordinateBounds emptyBounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0)); - XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds)); - XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds))); + MLNCoordinateBounds emptyBounds = + MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0)); + XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds)); + XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan( + MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds))); } - (void)testAngleConversions { - XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5); - XCTAssertEqual(0, MLNDegreesFromRadians(0)); - XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5); - XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5); - XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5); - XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5); - XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5); + XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5); + XCTAssertEqual(0, MLNDegreesFromRadians(0)); + XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5); + XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5); + XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5); + XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5); + XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5); - XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4); - XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5); - XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5); - XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5); - XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5); - XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5); - XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5); - XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5); - XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4); + XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4); + XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5); + XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5); + XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5); + XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5); + XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5); + XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5); + XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5); + XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4); } - (void)testAltitudeConversions { - CGSize tallSize = CGSizeMake(600, 1200); - CGSize midSize = CGSizeMake(600, 800); - CGSize shortSize = CGSizeMake(600, 400); + CGSize tallSize = CGSizeMake(600, 1200); + CGSize midSize = CGSizeMake(600, 800); + CGSize shortSize = CGSizeMake(600, 400); - XCTAssertEqualWithAccuracy(1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize), 1e-8); - XCTAssertLessThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, tallSize)); - XCTAssertGreaterThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, shortSize)); + XCTAssertEqualWithAccuracy( + 1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize), + 1e-8); + XCTAssertLessThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), + MLNZoomLevelForAltitude(1800, 0, 0, tallSize)); + XCTAssertGreaterThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), + MLNZoomLevelForAltitude(1800, 0, 0, shortSize)); - XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 0, midSize), 0, 0, midSize), 1e-8); - XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 0, midSize), 0, 0, midSize), 1e-8); + XCTAssertEqualWithAccuracy( + 0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 0, midSize), 0, 0, midSize), 1e-8); + XCTAssertEqualWithAccuracy( + 18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 0, midSize), 0, 0, midSize), 1e-8); - XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 40, midSize), 0, 40, midSize), 1e-8); - XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 40, midSize), 0, 40, midSize), 1e-8); + XCTAssertEqualWithAccuracy( + 0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 40, midSize), 0, 40, midSize), 1e-8); + XCTAssertEqualWithAccuracy( + 18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 40, midSize), 0, 40, midSize), + 1e-8); - XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 60, 40, midSize), 60, 40, midSize), 1e-8); - XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 60, 40, midSize), 60, 40, midSize), 1e-8); + XCTAssertEqualWithAccuracy( + 0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 60, 40, midSize), 60, 40, midSize), + 1e-8); + XCTAssertEqualWithAccuracy( + 18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 60, 40, midSize), 60, 40, midSize), + 1e-8); } - (void)testGeometryBoxing { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239); - CLLocationCoordinate2D roundTrippedCoordinate = [NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue; + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239); + CLLocationCoordinate2D roundTrippedCoordinate = + [NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue; - XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude, @"Latitude should round-trip."); - XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude, @"Longitude should round-trip."); + XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude, + @"Latitude should round-trip."); + XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude, + @"Longitude should round-trip."); - MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997); - MLNCoordinateSpan roundTrippedSpan = [NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue; + MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997); + MLNCoordinateSpan roundTrippedSpan = + [NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue; - XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta, @"Latitude delta should round-trip."); - XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta, @"Longitude delta should round-trip."); + XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta, + @"Latitude delta should round-trip."); + XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta, + @"Longitude delta should round-trip."); - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239), - CLLocationCoordinate2DMake(37.7757368, -122.4135302)); - MLNCoordinateBounds roundTrippedBounds = [NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue; + MLNCoordinateBounds bounds = + MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239), + CLLocationCoordinate2DMake(37.7757368, -122.4135302)); + MLNCoordinateBounds roundTrippedBounds = + [NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue; - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], - [NSValue valueWithMLNCoordinate:roundTrippedBounds.sw], - @"Southwest should round-trip."); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], - [NSValue valueWithMLNCoordinate:roundTrippedBounds.ne], - @"Northeast should round-trip."); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], + [NSValue valueWithMLNCoordinate:roundTrippedBounds.sw], + @"Southwest should round-trip."); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], + [NSValue valueWithMLNCoordinate:roundTrippedBounds.ne], + @"Northeast should round-trip."); } - (void)testCoordinateInCoordinateBounds { - CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104); - CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111); - MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne); + CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104); + CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111); + MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne); - CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5); + CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5); - XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming)); - XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming)); - XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming)); + XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming)); + XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming)); + XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming)); - CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135); - CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50); - CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100); + CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135); + CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50); + CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100); - XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming)); - XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming)); - XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming)); - XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming)); + XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming)); + XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming)); + XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming)); + XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming)); } - (void)testGeoJSONDeserialization { - NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error; - MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; - XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization."); - XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization."); - XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]], @"Valid GeoJSON point feature data should produce an MLNPointFeature."); - XCTAssertEqual(feature.attributes.count, 0UL); - XCTAssertEqual(feature.coordinate.latitude, 0); - XCTAssertEqual(feature.coordinate.longitude, 0); + NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": " + @"[0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data + encoding:NSUTF8StringEncoding + error:&error]; + XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization."); + XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization."); + XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]], + @"Valid GeoJSON point feature data should produce an MLNPointFeature."); + XCTAssertEqual(feature.attributes.count, 0UL); + XCTAssertEqual(feature.coordinate.latitude, 0); + XCTAssertEqual(feature.coordinate.longitude, 0); - data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}" dataUsingEncoding:NSUTF8StringEncoding]; - error = nil; - MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; - XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization."); - XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization."); + data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}" + dataUsingEncoding:NSUTF8StringEncoding]; + error = nil; + MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; + XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization."); + XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization."); } - (void)testGeoJSONSerialization { - MLNPointFeature *feature = [[MLNPointFeature alloc] init]; - feature.identifier = @504; - feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667); + MLNPointFeature *feature = [[MLNPointFeature alloc] init]; + feature.identifier = @504; + feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667); - NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding]; - XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object."); - NSError *error; - NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; - XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON."); - XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON."); - XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]], @"Serialized GeoJSON data should be a JSON object."); - NSDictionary *geoJSON = @{ - @"type": @"Feature", - @"id": @504, - @"geometry": @{ - @"type": @"Point", - @"coordinates": @[ - @(-90.066667), - @29.95, - ], - }, - @"properties": @{}, - }; - XCTAssertEqualObjects(serializedGeoJSON, geoJSON, @"MLNPointFeature should serialize as a GeoJSON point feature."); + NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding]; + XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object."); + NSError *error; + NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data + options:0 + error:&error]; + XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON."); + XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON."); + XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]], + @"Serialized GeoJSON data should be a JSON object."); + NSDictionary *geoJSON = @{ + @"type" : @"Feature", + @"id" : @504, + @"geometry" : @{ + @"type" : @"Point", + @"coordinates" : @[ + @(-90.066667), + @29.95, + ], + }, + @"properties" : @{}, + }; + XCTAssertEqualObjects(serializedGeoJSON, geoJSON, + @"MLNPointFeature should serialize as a GeoJSON point feature."); } - (void)testMLNCoordinateBoundsToMLNCoordinateQuad { - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425), - CLLocationCoordinate2DMake(46.437, -71.516)); + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425), + CLLocationCoordinate2DMake(46.437, -71.516)); - MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], - [NSValue valueWithMLNCoordinate:quad.bottomLeft], - @"Bounds southwest should be bottom left of quad."); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], - [NSValue valueWithMLNCoordinate:quad.topRight], - @"Bounds northeast should be top right of quad."); + MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], + [NSValue valueWithMLNCoordinate:quad.bottomLeft], + @"Bounds southwest should be bottom left of quad."); + XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], + [NSValue valueWithMLNCoordinate:quad.topRight], + @"Bounds northeast should be top right of quad."); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)], - [NSValue valueWithMLNCoordinate:quad.topLeft], - @"Quad top left should be computed correctly."); - XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)], - [NSValue valueWithMLNCoordinate:quad.bottomRight], - @"Quad bottom right should be computed correctly."); + XCTAssertEqualObjects( + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)], + [NSValue valueWithMLNCoordinate:quad.topLeft], + @"Quad top left should be computed correctly."); + XCTAssertEqualObjects( + [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)], + [NSValue valueWithMLNCoordinate:quad.bottomRight], + @"Quad bottom right should be computed correctly."); } - (void)testMLNMapPoint { - MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0); + MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0); - MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue; - XCTAssertEqual(point.x, roundTrippedPoint.x); - XCTAssertEqual(point.y, roundTrippedPoint.y); - XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel); + MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue; + XCTAssertEqual(point.x, roundTrippedPoint.x); + XCTAssertEqual(point.y, roundTrippedPoint.y); + XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel); } - (void)testMLNLocationCoordinate2DIsValid { - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323); - XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361); - XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); - } - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361); - XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); - } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323); + XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361); + XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); + } + { + CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361); + XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); + } } @end diff --git a/platform/darwin/test/MLNHeatmapColorTests.mm b/platform/darwin/test/MLNHeatmapColorTests.mm index e589091f6..89b133878 100644 --- a/platform/darwin/test/MLNHeatmapColorTests.mm +++ b/platform/darwin/test/MLNHeatmapColorTests.mm @@ -11,56 +11,83 @@ @implementation MLNHeatmapColorTests - (void)testProperties { - MLNPointFeature *feature = [[MLNPointFeature alloc] init]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; - MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + MLNPointFeature *feature = [[MLNPointFeature alloc] init]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" + shape:feature + options:nil]; + MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" + source:source]; - auto rawLayer = static_cast(layer.rawLayer); + auto rawLayer = static_cast(layer.rawLayer); - XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), - @"heatmap-color should be unset initially."); - NSExpression *defaultExpression = layer.heatmapColor; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"heatmap-color should be unset initially."); + NSExpression *defaultExpression = layer.heatmapColor; - NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; - layer.heatmapColor = constantExpression; + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; + layer.heatmapColor = constantExpression; + mbgl::style::PropertyValue propertyValue = {0xff}; + XCTAssertEqual( + rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), + @"Setting heatmapColor to a constant value expression should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, constantExpression, + @"heatmapColor should round-trip constant value expressions."); - mbgl::style::PropertyValue propertyValue = { 0xff }; - XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), - @"Setting heatmapColor to a constant value expression should update heatmap-color."); - XCTAssertEqualObjects(layer.heatmapColor, constantExpression, - @"heatmapColor should round-trip constant value expressions."); - - constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; - NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]]; + constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; + NSExpression *constantExpression2 = + [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]]; #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); + XCTExpectFailure( + @"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); #endif - NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", constantExpression, @{@12: constantExpression2}]; - layer.heatmapColor = functionExpression; + NSExpression *functionExpression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", + constantExpression, @{@12 : constantExpression2}]; + layer.heatmapColor = functionExpression; - XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), - @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); - XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), - @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); - XCTAssertEqualObjects(layer.heatmapColor, functionExpression, - @"heatmapColor should round-trip expressions depending on $heatmapDensity."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should " + @"update heatmap-color."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should " + @"update heatmap-color."); + XCTExpectFailure( + @"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); + XCTAssertEqualObjects( + layer.heatmapColor, functionExpression, + @"heatmapColor should round-trip expressions depending on $heatmapDensity."); - layer.heatmapColor = nil; - XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), - @"Unsetting heatmapColor should return heatmap-color to the default value."); - // The contained colors aren’t object equal, even though their descriptions are. - XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description, - @"heatmapColor should return the default value after being unset."); + layer.heatmapColor = nil; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"Unsetting heatmapColor should return heatmap-color to the default value."); + // The contained colors aren’t object equal, even though their descriptions are. + XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description, + @"heatmapColor should return the default value after being unset."); - functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; - XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); - functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; - XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); - functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; - functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; - XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", + constantExpression, @{@18 : constantExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, + NSInvalidArgumentException, + @"MLNHeatmapLayer should raise an exception if a camera expression " + @"is applied to heatmapColor."); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, + NSInvalidArgumentException, + @"MLNHeatmapLayer should raise an exception if a data expression is " + @"applied to heatmapColor."); + functionExpression = + [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, + @{@18 : constantExpression}]; + functionExpression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + @{@10 : functionExpression}]; + XCTAssertThrowsSpecificNamed( + layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, + @"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a " + @"property that does not support key paths to feature attributes."); } @end diff --git a/platform/darwin/test/MLNImageTests.mm b/platform/darwin/test/MLNImageTests.mm index 57a692277..7d4f2204c 100644 --- a/platform/darwin/test/MLNImageTests.mm +++ b/platform/darwin/test/MLNImageTests.mm @@ -1,14 +1,14 @@ -#import #import +#import #if TARGET_OS_IPHONE - #import "UIImage+MLNAdditions.h" - #define MLNImageResizingModeTile UIImageResizingModeTile - #define MLNImageResizingModeStretch UIImageResizingModeStretch +#import "UIImage+MLNAdditions.h" +#define MLNImageResizingModeTile UIImageResizingModeTile +#define MLNImageResizingModeStretch UIImageResizingModeStretch #else - #import "NSImage+MLNAdditions.h" - #define MLNImageResizingModeTile NSImageResizingModeTile - #define MLNImageResizingModeStretch NSImageResizingModeStretch +#import "NSImage+MLNAdditions.h" +#define MLNImageResizingModeTile NSImageResizingModeTile +#define MLNImageResizingModeStretch NSImageResizingModeStretch #endif @interface MLNImageTests : XCTestCase @@ -19,115 +19,122 @@ - (void)testStretching { #if TARGET_OS_IPHONE - CGRect rect = CGRectMake(0, 0, 24, 24); - UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale); - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor); - CGContextStrokeRectWithWidth(context, rect, 2); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + CGRect rect = CGRectMake(0, 0, 24, 24); + UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor); + CGContextStrokeRectWithWidth(context, rect, 2); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); #else - NSImage *image = [NSImage imageWithSize:NSMakeSize(24, 24) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { - // A little more fanciful than the iOS version, but we aren’t testing the actual contents of the image anyways. - NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge, NSMaxXEdge}; + NSImage *image = [NSImage + imageWithSize:NSMakeSize(24, 24) + flipped:NO + drawingHandler:^BOOL(NSRect dstRect) { + // A little more fanciful than the iOS version, but we aren’t testing the actual contents of + // the image anyways. + NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, + NSMinXEdge, NSMinYEdge, NSMaxXEdge}; CGFloat grays[] = {NSBlack, NSBlack, NSWhite, NSWhite, NSDarkGray, NSDarkGray}; - dstRect = NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0])); + dstRect = + NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0])); [NSColor.grayColor set]; NSRectFill(dstRect); return YES; - }]; - image.resizingMode = NSImageResizingModeTile; + }]; + image.resizingMode = NSImageResizingModeTile; #endif - { - auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; - XCTAssert(styleImage); - if (styleImage) { - XCTAssert(!styleImage->getContent()); - XCTAssertFalse(styleImage->isSdf()); - XCTAssertTrue(styleImage->getStretchX().empty()); - XCTAssertTrue(styleImage->getStretchY().empty()); + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + XCTAssert(!styleImage->getContent()); + XCTAssertFalse(styleImage->isSdf()); + XCTAssertTrue(styleImage->getStretchX().empty()); + XCTAssertTrue(styleImage->getStretchY().empty()); - MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; - XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile); - XCTAssertEqual(imageAfter.capInsets.top, 0); - XCTAssertEqual(imageAfter.capInsets.left, 0); - XCTAssertEqual(imageAfter.capInsets.bottom, 0); - XCTAssertEqual(imageAfter.capInsets.right, 0); - } + MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile); + XCTAssertEqual(imageAfter.capInsets.top, 0); + XCTAssertEqual(imageAfter.capInsets.left, 0); + XCTAssertEqual(imageAfter.capInsets.bottom, 0); + XCTAssertEqual(imageAfter.capInsets.right, 0); } + } #if TARGET_OS_IPHONE - image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsZero + resizingMode:UIImageResizingModeStretch]; #else - image.resizingMode = NSImageResizingModeStretch; - image.capInsets = NSEdgeInsetsZero; + image.resizingMode = NSImageResizingModeStretch; + image.capInsets = NSEdgeInsetsZero; #endif - { - auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; - XCTAssert(styleImage); - if (styleImage) { - auto scale = styleImage->getPixelRatio(); - XCTAssert(!styleImage->getContent()); + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + auto scale = styleImage->getPixelRatio(); + XCTAssert(!styleImage->getContent()); - auto stretchX = styleImage->getStretchX(); - XCTAssertEqual(stretchX.size(), 1UL); - if (!stretchX.empty()) { - XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale)); - } - auto stretchY = styleImage->getStretchY(); - XCTAssertEqual(stretchY.size(), 1UL); - if (!stretchY.empty()) { - XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale)); - } + auto stretchX = styleImage->getStretchX(); + XCTAssertEqual(stretchX.size(), 1UL); + if (!stretchX.empty()) { + XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale)); + } + auto stretchY = styleImage->getStretchY(); + XCTAssertEqual(stretchY.size(), 1UL); + if (!stretchY.empty()) { + XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale)); + } - MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; - XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); - XCTAssertEqual(imageAfter.capInsets.top, 0); - XCTAssertEqual(imageAfter.capInsets.left, 0); - XCTAssertEqual(imageAfter.capInsets.bottom, 0); - XCTAssertEqual(imageAfter.capInsets.right, 0); - } + MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); + XCTAssertEqual(imageAfter.capInsets.top, 0); + XCTAssertEqual(imageAfter.capInsets.left, 0); + XCTAssertEqual(imageAfter.capInsets.bottom, 0); + XCTAssertEqual(imageAfter.capInsets.right, 0); } + } #if TARGET_OS_IPHONE - image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)]; #else - image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4); + image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4); #endif - { - auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; - XCTAssert(styleImage); - if (styleImage) { - auto scale = styleImage->getPixelRatio(); - auto content = styleImage->getContent(); - XCTAssert(content); - if (content) { - XCTAssertEqual(content->top, 1 * scale); - XCTAssertEqual(content->left, 2 * scale); - XCTAssertEqual(content->bottom, 21 * scale); - XCTAssertEqual(content->right, 20 * scale); - } + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + auto scale = styleImage->getPixelRatio(); + auto content = styleImage->getContent(); + XCTAssert(content); + if (content) { + XCTAssertEqual(content->top, 1 * scale); + XCTAssertEqual(content->left, 2 * scale); + XCTAssertEqual(content->bottom, 21 * scale); + XCTAssertEqual(content->right, 20 * scale); + } - auto stretchX = styleImage->getStretchX(); - XCTAssertEqual(stretchX.size(), 1UL); - if (!stretchX.empty()) { - XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale)); - } - auto stretchY = styleImage->getStretchY(); - XCTAssertEqual(stretchY.size(), 1UL); - if (!stretchY.empty()) { - XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale)); - } + auto stretchX = styleImage->getStretchX(); + XCTAssertEqual(stretchX.size(), 1UL); + if (!stretchX.empty()) { + XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale)); + } + auto stretchY = styleImage->getStretchY(); + XCTAssertEqual(stretchY.size(), 1UL); + if (!stretchY.empty()) { + XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale)); + } - MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; - XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); - XCTAssertEqual(imageAfter.capInsets.top, 1); - XCTAssertEqual(imageAfter.capInsets.left, 2); - XCTAssertEqual(imageAfter.capInsets.bottom, 3); - XCTAssertEqual(imageAfter.capInsets.right, 4); - } + MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); + XCTAssertEqual(imageAfter.capInsets.top, 1); + XCTAssertEqual(imageAfter.capInsets.left, 2); + XCTAssertEqual(imageAfter.capInsets.bottom, 3); + XCTAssertEqual(imageAfter.capInsets.right, 4); } + } } @end diff --git a/platform/darwin/test/MLNNSDateAdditionsTests.mm b/platform/darwin/test/MLNNSDateAdditionsTests.mm index b5b1f5db4..3d6eca725 100644 --- a/platform/darwin/test/MLNNSDateAdditionsTests.mm +++ b/platform/darwin/test/MLNNSDateAdditionsTests.mm @@ -11,30 +11,28 @@ using namespace std::chrono_literals; @implementation MLNNSDateAdditionsTests - (void)testDurationToNSTimeInterval { + NSTimeInterval timeInterval = 5; + mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval); + NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration); - NSTimeInterval timeInterval = 5; - mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval); - NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration); + mbgl::Duration expectedDuration = 5s; + mbgl::Duration expectedDurationMiliSeconds = 5000ms; + mbgl::Duration expectedDurationMicroSeconds = 5000000us; + mbgl::Duration expectedDurationNanoSeconds = 5000000000ns; - mbgl::Duration expectedDuration = 5s; - mbgl::Duration expectedDurationMiliSeconds = 5000ms; - mbgl::Duration expectedDurationMicroSeconds = 5000000us; - mbgl::Duration expectedDurationNanoSeconds = 5000000000ns; + XCTAssertEqual(timeInterval, durationTimeInterval); + XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration)); + XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds)); + XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds)); + XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds)); - XCTAssertEqual(timeInterval, durationTimeInterval); - XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration)); - XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds)); - XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds)); - XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds)); - - mbgl::Duration durationMiliSeconds = 2500ms; - mbgl::Duration durationMicroSeconds = 2500000us; - mbgl::Duration durationNanoSeconds = 2500000000ns; - - XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds)); - XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds)); - XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds)); + mbgl::Duration durationMiliSeconds = 2500ms; + mbgl::Duration durationMicroSeconds = 2500000us; + mbgl::Duration durationNanoSeconds = 2500000000ns; + XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds)); + XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds)); + XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds)); } @end diff --git a/platform/darwin/test/MLNOfflinePackTests.mm b/platform/darwin/test/MLNOfflinePackTests.mm index e36dfdec2..aa0752317 100644 --- a/platform/darwin/test/MLNOfflinePackTests.mm +++ b/platform/darwin/test/MLNOfflinePackTests.mm @@ -10,49 +10,68 @@ @implementation MLNOfflinePackTests - (void)testInvalidation { - MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init]; + MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init]; - XCTAssertEqual(invalidPack.state, MLNOfflinePackStateInvalid, @"Offline pack should be invalid when initialized independently of MLNOfflineStorage."); + XCTAssertEqual( + invalidPack.state, MLNOfflinePackStateInvalid, + @"Offline pack should be invalid when initialized independently of MLNOfflineStorage."); - XCTAssertThrowsSpecificNamed(invalidPack.region, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its region."); - XCTAssertThrowsSpecificNamed(invalidPack.context, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its context."); - XCTAssertThrowsSpecificNamed([invalidPack resume], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being resumed."); - XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being suspended."); + XCTAssertThrowsSpecificNamed( + invalidPack.region, NSException, MLNInvalidOfflinePackException, + @"Invalid offline pack should raise an exception when accessing its region."); + XCTAssertThrowsSpecificNamed( + invalidPack.context, NSException, MLNInvalidOfflinePackException, + @"Invalid offline pack should raise an exception when accessing its context."); + XCTAssertThrowsSpecificNamed( + [invalidPack resume], NSException, MLNInvalidOfflinePackException, + @"Invalid offline pack should raise an exception when being resumed."); + XCTAssertThrowsSpecificNamed( + [invalidPack suspend], NSException, MLNInvalidOfflinePackException, + @"Invalid offline pack should raise an exception when being suspended."); } - (void)testInvalidatingAnInvalidPack { - MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init]; + MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init]; - XCTAssertThrowsSpecificNamed([invalidPack invalidate], NSException, NSInternalInconsistencyException, @"Invalid offline pack should raise an exception when being invalidated."); + XCTAssertThrowsSpecificNamed( + [invalidPack invalidate], NSException, NSInternalInconsistencyException, + @"Invalid offline pack should raise an exception when being invalidated."); - // Now try again, without asserts - NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; - MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; - [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + // Now try again, without asserts + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - // Make sure this doesn't crash without asserts - [invalidPack invalidate]; + // Make sure this doesn't crash without asserts + [invalidPack invalidate]; - [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; } - (void)testProgressBoxing { - MLNOfflinePackProgress progress = { - .countOfResourcesCompleted = 3, - .countOfResourcesExpected = 2, - .countOfBytesCompleted = 7, - .countOfTilesCompleted = 1, - .countOfTileBytesCompleted = 6, - .maximumResourcesExpected = UINT64_MAX, - }; - MLNOfflinePackProgress roundTrippedProgress = [NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue; + MLNOfflinePackProgress progress = { + .countOfResourcesCompleted = 3, + .countOfResourcesExpected = 2, + .countOfBytesCompleted = 7, + .countOfTilesCompleted = 1, + .countOfTileBytesCompleted = 6, + .maximumResourcesExpected = UINT64_MAX, + }; + MLNOfflinePackProgress roundTrippedProgress = + [NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue; - XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, @"Completed resources should round-trip."); - XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, @"Expected resources should round-trip."); - XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, @"Completed bytes should round-trip."); - XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted, @"Completed tiles should round-trip."); - XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted, @"Completed tile bytes should round-trip."); - XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, @"Maximum expected resources should round-trip."); + XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, + @"Completed resources should round-trip."); + XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, + @"Expected resources should round-trip."); + XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, + @"Completed bytes should round-trip."); + XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted, + @"Completed tiles should round-trip."); + XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted, + @"Completed tile bytes should round-trip."); + XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, + @"Maximum expected resources should round-trip."); } @end diff --git a/platform/darwin/test/MLNOfflineStorageTests.mm b/platform/darwin/test/MLNOfflineStorageTests.mm index 9c317eaf6..741686d9d 100644 --- a/platform/darwin/test/MLNOfflineStorageTests.mm +++ b/platform/darwin/test/MLNOfflineStorageTests.mm @@ -2,695 +2,937 @@ #import #import "MLNOfflineStorage_Private.h" +#import "MLNTestAssertionHandler.h" #import "NSBundle+MLNAdditions.h" #import "NSDate+MLNAdditions.h" -#import "MLNTestAssertionHandler.h" #include #include #pragma clang diagnostic ignored "-Wshadow" - @interface MLNOfflineStorageTests : XCTestCase @end @implementation MLNOfflineStorageTests + (void)tearDown { - NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:nil]; - NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Directory containing database should exist."); + NSURL *cacheDirectoryURL = + [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], + @"Directory containing database should exist."); - NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; - XCTAssertEqualObjects(cacheURL, MLNOfflineStorage.sharedOfflineStorage.databaseURL); + NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; + XCTAssertEqualObjects(cacheURL, MLNOfflineStorage.sharedOfflineStorage.databaseURL); - [[NSFileManager defaultManager] removeItemAtURL:cacheURL error:nil]; - XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], @"Database should not exist."); + [[NSFileManager defaultManager] removeItemAtURL:cacheURL error:nil]; + XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], + @"Database should not exist."); } - (void)setUp { - [super setUp]; - [MLNSettings useWellKnownTileServer:MLNMapTiler]; + [super setUp]; + [MLNSettings useWellKnownTileServer:MLNMapTiler]; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - XCTestExpectation *expectation = [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - return changeKind == NSKeyValueChangeSetting; - }]; - if ([MLNOfflineStorage sharedOfflineStorage].packs) { - [expectation fulfill]; - [self waitForExpectationsWithTimeout:0 handler:nil]; - } else { - [self waitForExpectationsWithTimeout:10 handler:nil]; - } + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + XCTestExpectation *expectation = [self + keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + return changeKind == NSKeyValueChangeSetting; + }]; + if ([MLNOfflineStorage sharedOfflineStorage].packs) { + [expectation fulfill]; + [self waitForExpectationsWithTimeout:0 handler:nil]; + } else { + [self waitForExpectationsWithTimeout:10 handler:nil]; + } - XCTAssertNotNil([MLNOfflineStorage sharedOfflineStorage].packs, @"Shared offline storage object should have a non-nil collection of packs by this point."); - }); + XCTAssertNotNil( + [MLNOfflineStorage sharedOfflineStorage].packs, + @"Shared offline storage object should have a non-nil collection of packs by this point."); + }); } - (NSURL *)offlineStorage:(MLNOfflineStorage *)storage URLForResourceOfKind:(MLNResourceKind)kind withURL:(NSURL *)url { - if ([url.scheme isEqual: @"test"] && [url.host isEqual: @"api"]) { - return [NSURL URLWithString:@"https://api.mapbox.com"]; - } else { - return url; - } + if ([url.scheme isEqual:@"test"] && [url.host isEqual:@"api"]) { + return [NSURL URLWithString:@"https://api.mapbox.com"]; + } else { + return url; + } } - (void)testSharedObject { - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage], [MLNOfflineStorage sharedOfflineStorage], @"There should only be one shared offline storage object."); + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage], [MLNOfflineStorage sharedOfflineStorage], + @"There should only be one shared offline storage object."); } - (void)testAddPackForBounds { - NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; - /// Somewhere near Grape Grove, Ohio, United States. - MLNCoordinateBounds bounds = { - { .latitude = 39.70358155855172, .longitude = -83.69506472545841 }, - { .latitude = 39.703818870225376, .longitude = -83.69420641857361 }, - }; - double zoomLevel = 20; - MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:zoomLevel toZoomLevel:zoomLevel]; + NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; + /// Somewhere near Grape Grove, Ohio, United States. + MLNCoordinateBounds bounds = { + {.latitude = 39.70358155855172, .longitude = -83.69506472545841}, + {.latitude = 39.703818870225376, .longitude = -83.69420641857361}, + }; + double zoomLevel = 20; + MLNTilePyramidOfflineRegion *region = + [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL + bounds:bounds + fromZoomLevel:zoomLevel + toZoomLevel:zoomLevel]; - NSString *nameKey = @"Name"; - NSString *name = @"🍇 Grape Grove"; + NSString *nameKey = @"Name"; + NSString *name = @"🍇 Grape Grove"; - NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ - nameKey: name, - }]; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ + nameKey : name, + }]; - __block MLNOfflinePack *pack; - [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; - return changeKind == NSKeyValueChangeInsertion && indices.count == 1; - }]; - XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; - [[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { + __block MLNOfflinePack *pack; + [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; + return changeKind == NSKeyValueChangeInsertion && + indices.count == 1; + }]; + XCTestExpectation *additionCompletionHandlerExpectation = + [self expectationWithDescription:@"add pack completion handler"]; + [[MLNOfflineStorage sharedOfflineStorage] + addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack, + NSError *_Nullable error) { XCTAssertNotNil(completionHandlerPack, @"Added pack should exist."); - XCTAssertEqual(completionHandlerPack.state, MLNOfflinePackStateInactive, @"New pack should initially have inactive state."); + XCTAssertEqual(completionHandlerPack.state, MLNOfflinePackStateInactive, + @"New pack should initially have inactive state."); pack = completionHandlerPack; [additionCompletionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, @"Added pack should have been added to the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks."); + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, + @"Added pack should have been added to the canonical collection of packs owned by " + @"the shared offline storage object. This assertion can fail if this test is run " + @"before -testAAALoadPacks."); - XCTAssertEqual(pack, [MLNOfflineStorage sharedOfflineStorage].packs.lastObject, @"Pack should be appended to end of packs array."); + XCTAssertEqual(pack, [MLNOfflineStorage sharedOfflineStorage].packs.lastObject, + @"Pack should be appended to end of packs array."); - XCTAssertEqualObjects(pack.region, region, @"Added pack’s region has changed."); + XCTAssertEqualObjects(pack.region, region, @"Added pack’s region has changed."); - NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; - XCTAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary."); - XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string."); - XCTAssertEqualObjects(userInfo[nameKey], name, @"Name of offline pack has changed."); + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + XCTAssert([userInfo isKindOfClass:[NSDictionary class]], + @"Context of offline pack isn’t a dictionary."); + XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], + @"Name of offline pack isn’t a string."); + XCTAssertEqualObjects(userInfo[nameKey], name, @"Name of offline pack has changed."); - XCTAssertEqual(pack.state, MLNOfflinePackStateInactive, @"New pack should initially have inactive state."); + XCTAssertEqual(pack.state, MLNOfflinePackStateInactive, + @"New pack should initially have inactive state."); - [self keyValueObservingExpectationForObject:pack keyPath:@"state" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - const auto state = static_cast([change[NSKeyValueChangeNewKey] longValue]); - return changeKind == NSKeyValueChangeSetting && state == MLNOfflinePackStateInactive; - }]; - [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { - MLNOfflinePack *notificationPack = notification.object; - XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], @"Object of notification should be an MLNOfflinePack."); + [self keyValueObservingExpectationForObject:pack + keyPath:@"state" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + const auto state = static_cast( + [change[NSKeyValueChangeNewKey] longValue]); + return changeKind == NSKeyValueChangeSetting && + state == MLNOfflinePackStateInactive; + }]; + [self + expectationForNotification:MLNOfflinePackProgressChangedNotification + object:pack + handler:^BOOL(NSNotification *_Nonnull notification) { + MLNOfflinePack *notificationPack = notification.object; + XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], + @"Object of notification should be an MLNOfflinePack."); - NSDictionary *userInfo = notification.userInfo; - XCTAssertNotNil(userInfo, @"Progress change notification should have a userInfo dictionary."); + NSDictionary *userInfo = notification.userInfo; + XCTAssertNotNil( + userInfo, + @"Progress change notification should have a userInfo dictionary."); - NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; - XCTAssert([stateNumber isKindOfClass:[NSNumber class]], @"Progress change notification’s state should be an NSNumber."); - XCTAssertEqual(stateNumber.integerValue, pack.state, @"State in a progress change notification should match the pack’s state."); + NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; + XCTAssert( + [stateNumber isKindOfClass:[NSNumber class]], + @"Progress change notification’s state should be an NSNumber."); + XCTAssertEqual(stateNumber.integerValue, pack.state, + @"State in a progress change notification should match " + @"the pack’s state."); - NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress]; - XCTAssert([progressValue isKindOfClass:[NSValue class]], @"Progress change notification’s progress should be an NSValue."); - XCTAssertEqualObjects(progressValue, [NSValue valueWithMLNOfflinePackProgress:pack.progress], @"Progress change notification’s progress should match pack’s progress."); + NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress]; + XCTAssert( + [progressValue isKindOfClass:[NSValue class]], + @"Progress change notification’s progress should be an NSValue."); + XCTAssertEqualObjects( + progressValue, + [NSValue valueWithMLNOfflinePackProgress:pack.progress], + @"Progress change notification’s progress should match pack’s " + @"progress."); - return notificationPack == pack && pack.state == MLNOfflinePackStateInactive; - }]; - [pack requestProgress]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + return notificationPack == pack && + pack.state == MLNOfflinePackStateInactive; + }]; + [pack requestProgress]; + [self waitForExpectationsWithTimeout:5 handler:nil]; - XCTAssertEqualObjects(pack.context, context, @"Offline pack context should match the context specified by the application."); - NSString *newName = @"🍑 Peach Grove"; - NSData *newContext = [NSKeyedArchiver archivedDataWithRootObject:@{ - nameKey: newName, - }]; + XCTAssertEqualObjects( + pack.context, context, + @"Offline pack context should match the context specified by the application."); + NSString *newName = @"🍑 Peach Grove"; + NSData *newContext = [NSKeyedArchiver archivedDataWithRootObject:@{ + nameKey : newName, + }]; - XCTestExpectation *contextCompletionHandlerExpectation = [self expectationWithDescription:@"set pack completion context handler"]; - __weak MLNOfflinePack *weakPack = pack; - [pack setContext:newContext completionHandler:^(NSError * _Nullable error) { + XCTestExpectation *contextCompletionHandlerExpectation = + [self expectationWithDescription:@"set pack completion context handler"]; + __weak MLNOfflinePack *weakPack = pack; + [pack setContext:newContext + completionHandler:^(NSError *_Nullable error) { XCTAssertNil(error); XCTAssertNotNil(weakPack); - XCTAssertEqualObjects(weakPack.context, newContext, @"Offline pack context should match the updated context specified by the application."); + XCTAssertEqualObjects( + weakPack.context, newContext, + @"Offline pack context should match the updated context specified by the application."); [contextCompletionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; } - (void)testAddPackForGeometry { - NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; - double zoomLevel = 20; - NSString *geojson = @"{ \"type\": \"Polygon\", \"coordinates\": [ [ [ 5.1299285888671875, 52.10365839097971 ], [ 5.103063583374023, 52.110037078604236 ], [ 5.080232620239258, 52.09548601177304 ], [ 5.106925964355469, 52.07987524347506 ], [ 5.1299285888671875, 52.10365839097971 ] ] ]}"; - NSError *error; - MLNShape *shape = [MLNShape shapeWithData: [geojson dataUsingEncoding:NSUTF8StringEncoding] encoding: NSUTF8StringEncoding error:&error]; - XCTAssertNil(error); - MLNShapeOfflineRegion *region = [[MLNShapeOfflineRegion alloc] initWithStyleURL:styleURL shape:shape fromZoomLevel:zoomLevel toZoomLevel:zoomLevel]; - region.includesIdeographicGlyphs = NO; + NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; + double zoomLevel = 20; + NSString *geojson = + @"{ \"type\": \"Polygon\", \"coordinates\": [ [ [ 5.1299285888671875, 52.10365839097971 ], [ " + @"5.103063583374023, 52.110037078604236 ], [ 5.080232620239258, 52.09548601177304 ], [ " + @"5.106925964355469, 52.07987524347506 ], [ 5.1299285888671875, 52.10365839097971 ] ] ]}"; + NSError *error; + MLNShape *shape = [MLNShape shapeWithData:[geojson dataUsingEncoding:NSUTF8StringEncoding] + encoding:NSUTF8StringEncoding + error:&error]; + XCTAssertNil(error); + MLNShapeOfflineRegion *region = [[MLNShapeOfflineRegion alloc] initWithStyleURL:styleURL + shape:shape + fromZoomLevel:zoomLevel + toZoomLevel:zoomLevel]; + region.includesIdeographicGlyphs = NO; - NSString *nameKey = @"Name"; - NSString *name = @"Utrecht centrum"; + NSString *nameKey = @"Name"; + NSString *name = @"Utrecht centrum"; - NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey: name}]; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey : name}]; - __block MLNOfflinePack *pack; - [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; - return changeKind == NSKeyValueChangeInsertion && indices.count == 1; - }]; - XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; - [[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { + __block MLNOfflinePack *pack; + [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; + return changeKind == NSKeyValueChangeInsertion && + indices.count == 1; + }]; + XCTestExpectation *additionCompletionHandlerExpectation = + [self expectationWithDescription:@"add pack completion handler"]; + [[MLNOfflineStorage sharedOfflineStorage] + addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack, + NSError *_Nullable error) { XCTAssertNotNil(completionHandlerPack, @"Added pack should exist."); - XCTAssertEqual(completionHandlerPack.state, MLNOfflinePackStateInactive, @"New pack should initially have inactive state."); + XCTAssertEqual(completionHandlerPack.state, MLNOfflinePackStateInactive, + @"New pack should initially have inactive state."); pack = completionHandlerPack; [additionCompletionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, @"Added pack should have been added to the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks."); + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, + @"Added pack should have been added to the canonical collection of packs owned by " + @"the shared offline storage object. This assertion can fail if this test is run " + @"before -testAAALoadPacks."); - XCTAssertEqual(pack, [MLNOfflineStorage sharedOfflineStorage].packs.lastObject, @"Pack should be appended to end of packs array."); + XCTAssertEqual(pack, [MLNOfflineStorage sharedOfflineStorage].packs.lastObject, + @"Pack should be appended to end of packs array."); - XCTAssertEqualObjects(pack.region, region, @"Added pack’s region has changed."); + XCTAssertEqualObjects(pack.region, region, @"Added pack’s region has changed."); - NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; - XCTAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary."); - XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string."); - XCTAssertEqualObjects(userInfo[nameKey], name, @"Name of offline pack has changed."); + NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context]; + XCTAssert([userInfo isKindOfClass:[NSDictionary class]], + @"Context of offline pack isn’t a dictionary."); + XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], + @"Name of offline pack isn’t a string."); + XCTAssertEqualObjects(userInfo[nameKey], name, @"Name of offline pack has changed."); - XCTAssertEqual(pack.state, MLNOfflinePackStateInactive, @"New pack should initially have inactive state."); + XCTAssertEqual(pack.state, MLNOfflinePackStateInactive, + @"New pack should initially have inactive state."); - [self keyValueObservingExpectationForObject:pack keyPath:@"state" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - const auto state = static_cast([change[NSKeyValueChangeNewKey] longValue]); - return changeKind == NSKeyValueChangeSetting && state == MLNOfflinePackStateInactive; - }]; - [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { - MLNOfflinePack *notificationPack = notification.object; - XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], @"Object of notification should be an MLNOfflinePack."); + [self keyValueObservingExpectationForObject:pack + keyPath:@"state" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + const auto state = static_cast( + [change[NSKeyValueChangeNewKey] longValue]); + return changeKind == NSKeyValueChangeSetting && + state == MLNOfflinePackStateInactive; + }]; + [self + expectationForNotification:MLNOfflinePackProgressChangedNotification + object:pack + handler:^BOOL(NSNotification *_Nonnull notification) { + MLNOfflinePack *notificationPack = notification.object; + XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], + @"Object of notification should be an MLNOfflinePack."); - NSDictionary *userInfo = notification.userInfo; - XCTAssertNotNil(userInfo, @"Progress change notification should have a userInfo dictionary."); + NSDictionary *userInfo = notification.userInfo; + XCTAssertNotNil( + userInfo, + @"Progress change notification should have a userInfo dictionary."); - NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; - XCTAssert([stateNumber isKindOfClass:[NSNumber class]], @"Progress change notification’s state should be an NSNumber."); - XCTAssertEqual(stateNumber.integerValue, pack.state, @"State in a progress change notification should match the pack’s state."); + NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; + XCTAssert( + [stateNumber isKindOfClass:[NSNumber class]], + @"Progress change notification’s state should be an NSNumber."); + XCTAssertEqual(stateNumber.integerValue, pack.state, + @"State in a progress change notification should match " + @"the pack’s state."); - NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress]; - XCTAssert([progressValue isKindOfClass:[NSValue class]], @"Progress change notification’s progress should be an NSValue."); - XCTAssertEqualObjects(progressValue, [NSValue valueWithMLNOfflinePackProgress:pack.progress], @"Progress change notification’s progress should match pack’s progress."); + NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress]; + XCTAssert( + [progressValue isKindOfClass:[NSValue class]], + @"Progress change notification’s progress should be an NSValue."); + XCTAssertEqualObjects( + progressValue, + [NSValue valueWithMLNOfflinePackProgress:pack.progress], + @"Progress change notification’s progress should match pack’s " + @"progress."); - return notificationPack == pack && pack.state == MLNOfflinePackStateInactive; - }]; - [pack requestProgress]; - [self waitForExpectationsWithTimeout:5 handler:nil]; - pack = nil; + return notificationPack == pack && + pack.state == MLNOfflinePackStateInactive; + }]; + [pack requestProgress]; + [self waitForExpectationsWithTimeout:5 handler:nil]; + pack = nil; } - (void)testInvalidatePack { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect offline pack to be invalidated without an error."]; - MLNCoordinateBounds bounds = { - { .latitude = 48.8660, .longitude = 2.3306 }, - { .latitude = 48.8603, .longitude = 2.3213 }, - }; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect offline pack to be invalidated without an error."]; + MLNCoordinateBounds bounds = { + {.latitude = 48.8660, .longitude = 2.3306}, + {.latitude = 48.8603, .longitude = 2.3213}, + }; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:10 toZoomLevel:11]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + MLNTilePyramidOfflineRegion *region = + [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL + bounds:bounds + fromZoomLevel:10 + toZoomLevel:11]; - NSString *nameKey = @"Name"; - NSString *name = @"Paris square"; + NSString *nameKey = @"Name"; + NSString *name = @"Paris square"; - NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey: name}]; - [[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey : name}]; + [[MLNOfflineStorage sharedOfflineStorage] + addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) { XCTAssertNotNil(pack); - [[MLNOfflineStorage sharedOfflineStorage] invalidatePack:pack withCompletionHandler:^(NSError * _Nullable) { - XCTAssertNotNil(pack); - XCTAssertNil(error); - [expectation fulfill]; - }]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + [[MLNOfflineStorage sharedOfflineStorage] invalidatePack:pack + withCompletionHandler:^(NSError *_Nullable) { + XCTAssertNotNil(pack); + XCTAssertNil(error); + [expectation fulfill]; + }]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; } - (void)testRemovePack { - NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; - XCTAssertNotNil(pack, @"Added pack should still exist."); + MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; + XCTAssertNotNil(pack, @"Added pack should still exist."); - [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; - return changeKind == NSKeyValueChangeRemoval && indices.count == 1; - }]; - XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"]; - [[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { - XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler."); + [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; + return changeKind == NSKeyValueChangeRemoval && + indices.count == 1; + }]; + XCTestExpectation *completionHandlerExpectation = + [self expectationWithDescription:@"remove pack completion handler"]; + [[MLNOfflineStorage sharedOfflineStorage] + removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { + XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, + @"Removed pack should be invalid in the completion handler."); [completionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; - XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, @"Removed pack should have been invalidated synchronously."); + XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, + @"Removed pack should have been invalidated synchronously."); - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack."); + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, + @"Removed pack should have been removed from the canonical collection of packs " + @"owned by the shared offline storage object. This assertion can fail if this " + @"test is run before -testAAALoadPacks or -testAddPack."); } - (void)testBackupExclusion { - NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:nil]; - // As of iOS SDK 12.2 unit tests now have a bundle id: com.apple.dt.xctest.tool - NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; - cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Cache subdirectory should exist."); + NSURL *cacheDirectoryURL = + [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + // As of iOS SDK 12.2 unit tests now have a bundle id: com.apple.dt.xctest.tool + NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], + @"Cache subdirectory should exist."); - NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; - XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], @"Cache database should exist."); + NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], + @"Cache database should exist."); - NSError *error = nil; - NSNumber *exclusionFlag = nil; - [cacheDirectoryURL getResourceValue:&exclusionFlag - forKey:NSURLIsExcludedFromBackupKey - error:&error]; - XCTAssertTrue(exclusionFlag && [exclusionFlag boolValue], @"Backup exclusion flag should be set for the directory containing the cache database."); - XCTAssertNil(error, @"No errors should be returned when checking backup exclusion flag."); + NSError *error = nil; + NSNumber *exclusionFlag = nil; + [cacheDirectoryURL getResourceValue:&exclusionFlag + forKey:NSURLIsExcludedFromBackupKey + error:&error]; + XCTAssertTrue( + exclusionFlag && [exclusionFlag boolValue], + @"Backup exclusion flag should be set for the directory containing the cache database."); + XCTAssertNil(error, @"No errors should be returned when checking backup exclusion flag."); } - (void)addPacks:(NSInteger)count { + XCTestExpectation *expectation = [self expectationWithDescription:@"added packs"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"added packs"]; + NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; - NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; + MLNCoordinateBounds bounds[] = { + {{51.5, -0.2}, {51.6, -0.1}}, // London + {{60.1, 24.8}, {60.3, 25.1}}, // Helsinki + {{38.9, -77.1}, {38.9, -77.0}}, // DC + {{37.7, -122.5}, {37.9, -122.4}} // SF + }; - MLNCoordinateBounds bounds[] = { - {{51.5, -0.2}, {51.6, -0.1}}, // London - {{60.1, 24.8}, {60.3, 25.1}}, // Helsinki - {{38.9, -77.1}, {38.9, -77.0}}, // DC - {{37.7, -122.5}, {37.9, -122.4}} // SF - }; + int arraySize = sizeof(bounds) / sizeof(bounds[0]); - int arraySize = sizeof(bounds)/sizeof(bounds[0]); + count = MIN(count, arraySize); - count = MIN(count, arraySize); + dispatch_group_t group = dispatch_group_create(); - dispatch_group_t group = dispatch_group_create(); + for (int i = 0; i < count; i++) { + dispatch_group_enter(group); + MLNTilePyramidOfflineRegion *region = + [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL + bounds:bounds[i] + fromZoomLevel:20 + toZoomLevel:20]; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"index" : @(i)}]; - for (int i = 0; i < count; i++) { + [[MLNOfflineStorage sharedOfflineStorage] + addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) { + XCTAssertNotNil(pack); + XCTAssertNil(error); - dispatch_group_enter(group); - MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds[i] fromZoomLevel:20 toZoomLevel:20]; - NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ - @"index": @(i) + dispatch_group_leave(group); }]; + } - [[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region - withContext:context - completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { - XCTAssertNotNil(pack); - XCTAssertNil(error); + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + [expectation fulfill]; + }); - dispatch_group_leave(group); - }]; - } - - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - [expectation fulfill]; - }); - - [self waitForExpectations:@[expectation] timeout:1.0]; + [self waitForExpectations:@[ expectation ] timeout:1.0]; } - (void)testRemovePackTwiceInSuccession { + [self addPacks:1]; - [self addPacks:1]; + NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; + XCTAssertNotNil(pack, @"Added pack should still exist."); - MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; - XCTAssertNotNil(pack, @"Added pack should still exist."); + [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = static_cast( + [change[NSKeyValueChangeKindKey] unsignedLongValue]); + NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; + return changeKind == NSKeyValueChangeRemoval && + indices.count == 1; + }]; - [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; - return changeKind == NSKeyValueChangeRemoval && indices.count == 1; - }]; + XCTestExpectation *completionHandlerExpectation = + [self expectationWithDescription:@"remove pack completion handler"]; - XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"]; + [[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:nil]; - [[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:nil]; + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; - NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; - MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - - [[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { - XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler."); + [[MLNOfflineStorage sharedOfflineStorage] + removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { + XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, + @"Removed pack should be invalid in the completion handler."); [completionHandlerExpectation fulfill]; - }]; + }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; + [self waitForExpectationsWithTimeout:5 handler:nil]; - [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; - XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, @"Removed pack should have been invalidated synchronously."); + XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, + @"Removed pack should have been invalidated synchronously."); - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, @"Removed pack should have been removed from the canonical collection of packs owned by the shared offline storage object. This assertion can fail if this test is run before -testAAALoadPacks or -testAddPack."); + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks - 1, + @"Removed pack should have been removed from the canonical collection of packs " + @"owned by the shared offline storage object. This assertion can fail if this " + @"test is run before -testAAALoadPacks or -testAddPack."); - NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); } - (void)test15536RemovePacksWhileReloading { + // This test triggers + // + // throw std::runtime_error("Malformed offline region definition"); + // + // in offline.cpp + // + // Reloading packs, while trying to remove them is currently problematic. - // This test triggers - // - // throw std::runtime_error("Malformed offline region definition"); - // - // in offline.cpp - // - // Reloading packs, while trying to remove them is currently problematic. + [self addPacks:4]; - [self addPacks:4]; + NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + XCTAssert(countOfPacks > 0); - NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - XCTAssert(countOfPacks > 0); + // Now delete packs one by one + XCTestExpectation *expectation = [self expectationWithDescription:@"All packs removed"]; + expectation.expectedFulfillmentCount = countOfPacks; - // Now delete packs one by one - XCTestExpectation *expectation = [self expectationWithDescription:@"All packs removed"]; - expectation.expectedFulfillmentCount = countOfPacks; + MLNOfflineStorage *storage = [MLNOfflineStorage sharedOfflineStorage]; + NSArray *packs = [storage.packs copy]; - MLNOfflineStorage *storage = [MLNOfflineStorage sharedOfflineStorage]; - NSArray *packs = [storage.packs copy]; + // Simulate what happens the first time sharedOfflineStorage is accessed + [storage reloadPacks]; - // Simulate what happens the first time sharedOfflineStorage is accessed - [storage reloadPacks]; + NSArray *validPacks = [packs + filteredArrayUsingPredicate:[NSPredicate + predicateWithBlock:^BOOL( + id _Nullable evaluatedObject, + NSDictionary *_Nullable bindings) { + MLNOfflinePack *pack = (MLNOfflinePack *)evaluatedObject; + return pack.state != MLNOfflinePackStateInvalid; + }]]; - NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - MLNOfflinePack *pack = (MLNOfflinePack*)evaluatedObject; - return pack.state != MLNOfflinePackStateInvalid; - }]]; + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; - NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; - MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - - for (MLNOfflinePack *pack in validPacks) { - [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { - [expectation fulfill]; + for (MLNOfflinePack *pack in validPacks) { + [storage removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { + [expectation fulfill]; }]; - } + } - [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; - [self waitForExpectations:@[expectation] timeout:10.0]; + [self waitForExpectations:@[ expectation ] timeout:10.0]; - // TODO: What should we expect here? All packs removed? + // TODO: What should we expect here? All packs removed? - NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); } // Test to explore https://github.com/mapbox/mapbox-gl-native/issues/15536 - (void)test15536RemovePacksOnBackgroundQueueWhileReloading { + [self addPacks:4]; - [self addPacks:4]; + NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + XCTAssert(countOfPacks > 0); - NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - XCTAssert(countOfPacks > 0); + // Now delete packs one by one + dispatch_queue_t queue = + dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL); - // Now delete packs one by one - dispatch_queue_t queue = dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL); + XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"]; + expectation.expectedFulfillmentCount = countOfPacks; - XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"]; - expectation.expectedFulfillmentCount = countOfPacks; + MLNOfflineStorage *storage = [MLNOfflineStorage sharedOfflineStorage]; - MLNOfflineStorage *storage = [MLNOfflineStorage sharedOfflineStorage]; + // Simulate what happens the first time sharedOfflineStorage is accessed + [storage reloadPacks]; - // Simulate what happens the first time sharedOfflineStorage is accessed - [storage reloadPacks]; + // NSArray *packs = [storage.packs copy]; -// NSArray *packs = [storage.packs copy]; + dispatch_async(queue, ^{ + NSArray *packs = storage.packs; + NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; + MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; - dispatch_async(queue, ^{ - NSArray *packs = storage.packs; - NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; - MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; + [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; - [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; + NSArray *validPacks = [packs + filteredArrayUsingPredicate:[NSPredicate + predicateWithBlock:^BOOL( + id _Nullable evaluatedObject, + NSDictionary *_Nullable bindings) { + MLNOfflinePack *pack = (MLNOfflinePack *)evaluatedObject; + return pack.state != MLNOfflinePackStateInvalid; + }]]; - NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - MLNOfflinePack *pack = (MLNOfflinePack*)evaluatedObject; - return pack.state != MLNOfflinePackStateInvalid; - }]]; + for (MLNOfflinePack *pack in validPacks) { + // NOTE: pack can be invalid, as we have two threads potentially + // modifying the same MLNOfflinePack. - for (MLNOfflinePack *pack in validPacks) { - // NOTE: pack can be invalid, as we have two threads potentially - // modifying the same MLNOfflinePack. + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + [storage removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { + dispatch_group_leave(group); + }]; + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - dispatch_group_t group = dispatch_group_create(); - dispatch_group_enter(group); - [storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { - dispatch_group_leave(group); - }]; - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + [expectation fulfill]; + } - [expectation fulfill]; - } + [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; + }); - [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; - }); + [self waitForExpectations:@[ expectation ] timeout:60.0]; - [self waitForExpectations:@[expectation] timeout:60.0]; + // TODO: What should we expect here? All packs removed? - // TODO: What should we expect here? All packs removed? - - NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); + NSLog(@"Test `%@` complete", NSStringFromSelector(_cmd)); } - (void)testCountOfBytesCompleted { - XCTAssertGreaterThan([MLNOfflineStorage sharedOfflineStorage].countOfBytesCompleted, 0UL); + XCTAssertGreaterThan([MLNOfflineStorage sharedOfflineStorage].countOfBytesCompleted, 0UL); } - (void)testResourceTransform { - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - [os setDelegate:self]; + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + [os setDelegate:self]; - auto fs = os.mbglOnlineFileSource; + auto fs = os.mbglOnlineFileSource; - // Delegate returns "https://api.mapbox.com" as a replacement URL. - const mbgl::Resource resource { mbgl::Resource::Unknown, "test://api" }; - std::unique_ptr req; - req = fs->request(resource, [&](mbgl::Response res) { - req.reset(); - XCTAssertFalse(res.error.get(), @"Request should not return an error"); - XCTAssertTrue(res.data.get(), @"Request should return data"); - XCTAssertEqual("{\"api\":\"mapbox\"}", *res.data, @"Request did not return expected data"); - CFRunLoopStop(CFRunLoopGetCurrent()); - }); + // Delegate returns "https://api.mapbox.com" as a replacement URL. + const mbgl::Resource resource{mbgl::Resource::Unknown, "test://api"}; + std::unique_ptr req; + req = fs->request(resource, [&](mbgl::Response res) { + req.reset(); + XCTAssertFalse(res.error.get(), @"Request should not return an error"); + XCTAssertTrue(res.data.get(), @"Request should return data"); + XCTAssertEqual("{\"api\":\"mapbox\"}", *res.data, @"Request did not return expected data"); + CFRunLoopStop(CFRunLoopGetCurrent()); + }); - CFRunLoopRun(); + CFRunLoopRun(); - [os setDelegate:nil]; + [os setDelegate:nil]; } - (void)testAddFileContent { - NSFileManager *fileManager = [NSFileManager defaultManager]; + NSFileManager *fileManager = [NSFileManager defaultManager]; - // Valid database - [XCTContext runActivityNamed:@"Valid database" block:^(id activity) { - NSURL *resourceURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"sideload_sat" ofType:@"db"]]; + // Valid database + [XCTContext + runActivityNamed:@"Valid database" + block:^(id activity) { + NSURL *resourceURL = + [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] + pathForResource:@"sideload_sat" + ofType:@"db"]]; - NSError *error; - NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:resourceURL.path error:&error]; - XCTAssertNil(error, @"Getting the file's attributes should not return an error. (%@)", resourceURL.path); + NSError *error; + NSDictionary *fileAttributes = + [fileManager attributesOfItemAtPath:resourceURL.path error:&error]; + XCTAssertNil(error, + @"Getting the file's attributes should not return an error. (%@)", + resourceURL.path); - NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize]; - long long fileSize = [fileSizeNumber longLongValue]; - long long databaseFileSize = 19218432; - // Merging databases creates an empty file if the file does not exist at the given path. - XCTAssertEqual(fileSize, databaseFileSize, @"The database file size must be:%lld actual size:%lld", databaseFileSize, fileSize); + NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize]; + long long fileSize = [fileSizeNumber longLongValue]; + long long databaseFileSize = 19218432; + // Merging databases creates an empty file if the file does not exist at the + // given path. + XCTAssertEqual(fileSize, databaseFileSize, + @"The database file size must be:%lld actual size:%lld", + databaseFileSize, fileSize); - NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; + NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; - [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { - const auto changeKind = static_cast([change[NSKeyValueChangeKindKey] unsignedLongValue]); - NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; - return changeKind == NSKeyValueChangeInsertion && indices.count == 1; - }]; + [self + keyValueObservingExpectationForObject:[MLNOfflineStorage + sharedOfflineStorage] + keyPath:@"packs" + handler:^BOOL(id _Nonnull observedObject, + NSDictionary *_Nonnull change) { + const auto changeKind = + static_cast( + [change[NSKeyValueChangeKindKey] + unsignedLongValue]); + NSIndexSet *indices = + change[NSKeyValueChangeIndexesKey]; + return changeKind == + NSKeyValueChangeInsertion && + indices.count == 1; + }]; - XCTestExpectation *fileAdditionCompletionHandlerExpectation = [self expectationWithDescription:@"add database content completion handler"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - [os addContentsOfURL:resourceURL withCompletionHandler:^(NSURL *fileURL, NSArray * _Nullable packs, NSError * _Nullable error) { - XCTAssertNotNil(fileURL, @"The fileURL should not be nil."); - XCTAssertNotNil(packs, @"Adding the contents of the sideload_sat.db should update one pack."); - XCTAssertNil(error, @"Adding contents to a file should not return an error."); - for (MLNOfflinePack *pack in [MLNOfflineStorage sharedOfflineStorage].packs) { - NSLog(@"PACK:%@", pack); - } - [fileAdditionCompletionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; - // Depending on the database it may update or add a pack. For this case specifically the offline database adds one pack. - XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, @"Adding contents of sideload_sat.db should add one pack."); - }]; + XCTestExpectation *fileAdditionCompletionHandlerExpectation = + [self expectationWithDescription:@"add database content completion handler"]; + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + [os addContentsOfURL:resourceURL + withCompletionHandler:^(NSURL *fileURL, + NSArray *_Nullable packs, + NSError *_Nullable error) { + XCTAssertNotNil(fileURL, @"The fileURL should not be nil."); + XCTAssertNotNil( + packs, + @"Adding the contents of the sideload_sat.db should update one pack."); + XCTAssertNil(error, + @"Adding contents to a file should not return an error."); + for (MLNOfflinePack *pack in [MLNOfflineStorage sharedOfflineStorage] + .packs) { + NSLog(@"PACK:%@", pack); + } + [fileAdditionCompletionHandlerExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + // Depending on the database it may update or add a pack. For this case + // specifically the offline database adds one pack. + XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, + countOfPacks + 1, + @"Adding contents of sideload_sat.db should add one pack."); + }]; - // Invalid database type - [XCTContext runActivityNamed:@"Invalid database type" block:^(id activity) { - NSURL *resourceURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"one-liner" ofType:@"json"]]; + // Invalid database type + [XCTContext + runActivityNamed:@"Invalid database type" + block:^(id activity) { + NSURL *resourceURL = + [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] + pathForResource:@"one-liner" + ofType:@"json"]]; - XCTestExpectation *invalidFileCompletionHandlerExpectation = [self expectationWithDescription:@"invalid content database completion handler"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - [os addContentsOfFile:resourceURL.path withCompletionHandler:^(NSURL *fileURL, NSArray * _Nullable packs, NSError * _Nullable error) { - XCTAssertNotNil(error, @"Passing an invalid offline database file should return an error."); - XCTAssertNil(packs, @"Passing an invalid offline database file should not add packs to the offline database."); - [invalidFileCompletionHandlerExpectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; - }]; + XCTestExpectation *invalidFileCompletionHandlerExpectation = [self + expectationWithDescription:@"invalid content database completion handler"]; + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + [os addContentsOfFile:resourceURL.path + withCompletionHandler:^(NSURL *fileURL, + NSArray *_Nullable packs, + NSError *_Nullable error) { + XCTAssertNotNil( + error, + @"Passing an invalid offline database file should return an error."); + XCTAssertNil(packs, @"Passing an invalid offline database file should not " + @"add packs to the offline database."); + [invalidFileCompletionHandlerExpectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + }]; - // File does not exist - [XCTContext runActivityNamed:@"File does not exist" block:^(id activity) { - NSURL *resourceURL = [NSURL URLWithString:@"nonexistent.db"]; + // File does not exist + [XCTContext runActivityNamed:@"File does not exist" + block:^(id activity) { + NSURL *resourceURL = [NSURL URLWithString:@"nonexistent.db"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - XCTAssertThrowsSpecificNamed([os addContentsOfURL:resourceURL withCompletionHandler:nil], NSException, NSInvalidArgumentException, "MLNOfflineStorage should rise an exception if an invalid database file is passed."); - }]; + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + XCTAssertThrowsSpecificNamed( + [os addContentsOfURL:resourceURL withCompletionHandler:nil], + NSException, NSInvalidArgumentException, + "MLNOfflineStorage should rise an exception if an invalid database " + "file is passed."); + }]; - // URL to a non-file - [XCTContext runActivityNamed:@"URL to a non-file" block:^(id activity) { - NSURL *resourceURL = [NSURL URLWithString:@"https://www.mapbox.com"]; + // URL to a non-file + [XCTContext runActivityNamed:@"URL to a non-file" + block:^(id activity) { + NSURL *resourceURL = [NSURL URLWithString:@"https://www.mapbox.com"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - XCTAssertThrowsSpecificNamed([os addContentsOfURL:resourceURL withCompletionHandler:nil], NSException, NSInvalidArgumentException, "MLNOfflineStorage should rise an exception if an invalid URL file is passed."); - }]; + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + XCTAssertThrowsSpecificNamed( + [os addContentsOfURL:resourceURL withCompletionHandler:nil], + NSException, NSInvalidArgumentException, + "MLNOfflineStorage should rise an exception if an invalid URL file " + "is passed."); + }]; } - (void)testPutResourceForURL { - NSURL *styleURL = [NSURL URLWithString:@"https://api.mapbox.com/some/thing"]; + NSURL *styleURL = [NSURL URLWithString:@"https://api.mapbox.com/some/thing"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - std::string testData("test data"); - NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()]; - __block std::unique_ptr req; - [os preloadData:data forURL:styleURL modificationDate:nil expirationDate:nil eTag:nil mustRevalidate:NO completionHandler:^(NSURL * url, NSError * _Nullable error) { + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + std::string testData("test data"); + NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()]; + __block std::unique_ptr req; + [os preloadData:data + forURL:styleURL + modificationDate:nil + expirationDate:nil + eTag:nil + mustRevalidate:NO + completionHandler:^(NSURL *url, NSError *_Nullable error) { XCTAssertFalse(error, @"preloadData should not return an error"); XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid"); auto fs = os.mbglDatabaseFileSource; - const mbgl::Resource resource { mbgl::Resource::Unknown, "https://api.mapbox.com/some/thing" }; + const mbgl::Resource resource{mbgl::Resource::Unknown, "https://api.mapbox.com/some/thing"}; req = fs->request(resource, [&](mbgl::Response res) { - XCTAssertFalse(res.error.get(), @"Request should not return an error"); - XCTAssertTrue(res.data.get(), @"Request should return data"); - XCTAssertFalse(res.modified, @"Request should not have a modification timestamp"); - XCTAssertFalse(res.expires, @"Request should not have an expiration timestamp"); - XCTAssertFalse(res.etag, @"Request should not have an entity tag"); - XCTAssertFalse(res.mustRevalidate, @"Request should not require revalidation"); - XCTAssertEqual("test data", *res.data, @"Request did not return expected data"); - CFRunLoopStop(CFRunLoopGetCurrent()); + XCTAssertFalse(res.error.get(), @"Request should not return an error"); + XCTAssertTrue(res.data.get(), @"Request should return data"); + XCTAssertFalse(res.modified, @"Request should not have a modification timestamp"); + XCTAssertFalse(res.expires, @"Request should not have an expiration timestamp"); + XCTAssertFalse(res.etag, @"Request should not have an entity tag"); + XCTAssertFalse(res.mustRevalidate, @"Request should not require revalidation"); + XCTAssertEqual("test data", *res.data, @"Request did not return expected data"); + CFRunLoopStop(CFRunLoopGetCurrent()); }); - }]; + }]; - CFRunLoopRun(); + CFRunLoopRun(); } - (void)testPutResourceForURLWithTimestamps { - NSURL *styleURL = [NSURL URLWithString:@"https://api.mapbox.com/some/thing1"]; + NSURL *styleURL = [NSURL URLWithString:@"https://api.mapbox.com/some/thing1"]; - MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; - std::string testData("test data"); - NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()]; - __block NSDate *now = [NSDate date]; - __block NSDate *future = [now dateByAddingTimeInterval:600]; - __block std::unique_ptr req; - [os preloadData:data forURL:styleURL modificationDate:now expirationDate:future eTag:@"some etag" mustRevalidate:YES completionHandler:^(NSURL * url, NSError * _Nullable error){ + MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; + std::string testData("test data"); + NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()]; + __block NSDate *now = [NSDate date]; + __block NSDate *future = [now dateByAddingTimeInterval:600]; + __block std::unique_ptr req; + [os preloadData:data + forURL:styleURL + modificationDate:now + expirationDate:future + eTag:@"some etag" + mustRevalidate:YES + completionHandler:^(NSURL *url, NSError *_Nullable error) { XCTAssertFalse(error, @"preloadData should not return an error"); XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid"); auto fs = os.mbglDatabaseFileSource; - const mbgl::Resource resource { mbgl::Resource::Unknown, "https://api.mapbox.com/some/thing1" }; - req = fs->request(resource, [&, tNow = now.timeIntervalSince1970, tFuture = future.timeIntervalSince1970](mbgl::Response res) { - XCTAssertFalse(res.error.get(), @"Request should not return an error"); - XCTAssertTrue(res.data.get(), @"Request should return data"); - XCTAssertTrue(res.modified, @"Request should have a modification timestamp"); - XCTAssertEqual(MLNTimeIntervalFromDuration(res.modified->time_since_epoch()), floor(tNow), @"Modification timestamp should roundtrip"); - XCTAssertTrue(res.expires, @"Request should have an expiration timestamp"); - XCTAssertEqual(MLNTimeIntervalFromDuration(res.expires->time_since_epoch()), floor(tFuture), @"Expiration timestamp should roundtrip"); - XCTAssertTrue(res.etag, @"Request should have an entity tag"); - XCTAssertEqual(*res.etag, "some etag", @"Entity tag should roundtrip"); - XCTAssertTrue(res.mustRevalidate, @"Request should require revalidation"); - XCTAssertEqual("test data", *res.data, @"Request did not return expected data"); - CFRunLoopStop(CFRunLoopGetCurrent()); + const mbgl::Resource resource{mbgl::Resource::Unknown, + "https://api.mapbox.com/some/thing1"}; + req = fs->request(resource, [&, tNow = now.timeIntervalSince1970, + tFuture = future.timeIntervalSince1970](mbgl::Response res) { + XCTAssertFalse(res.error.get(), @"Request should not return an error"); + XCTAssertTrue(res.data.get(), @"Request should return data"); + XCTAssertTrue(res.modified, @"Request should have a modification timestamp"); + XCTAssertEqual(MLNTimeIntervalFromDuration(res.modified->time_since_epoch()), floor(tNow), + @"Modification timestamp should roundtrip"); + XCTAssertTrue(res.expires, @"Request should have an expiration timestamp"); + XCTAssertEqual(MLNTimeIntervalFromDuration(res.expires->time_since_epoch()), + floor(tFuture), @"Expiration timestamp should roundtrip"); + XCTAssertTrue(res.etag, @"Request should have an entity tag"); + XCTAssertEqual(*res.etag, "some etag", @"Entity tag should roundtrip"); + XCTAssertTrue(res.mustRevalidate, @"Request should require revalidation"); + XCTAssertEqual("test data", *res.data, @"Request did not return expected data"); + CFRunLoopStop(CFRunLoopGetCurrent()); }); - }]; + }]; - CFRunLoopRun(); + CFRunLoopRun(); } - (void)testSetMaximumAmbientCache { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect maximum cache size to be raised without an error."]; - [[MLNOfflineStorage sharedOfflineStorage] setMaximumAmbientCacheSize:0 withCompletionHandler:^(NSError * _Nullable error) { - XCTAssertNil(error); - [[MLNOfflineStorage sharedOfflineStorage] setMaximumAmbientCacheSize:50*1024*1024 withCompletionHandler:^(NSError * _Nullable error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - }]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect maximum cache size to be raised without an error."]; + [[MLNOfflineStorage sharedOfflineStorage] + setMaximumAmbientCacheSize:0 + withCompletionHandler:^(NSError *_Nullable error) { + XCTAssertNil(error); + [[MLNOfflineStorage sharedOfflineStorage] + setMaximumAmbientCacheSize:50 * 1024 * 1024 + withCompletionHandler:^(NSError *_Nullable error) { + XCTAssertNil(error); + [expectation fulfill]; + }]; + }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + [self waitForExpectationsWithTimeout:10 handler:nil]; } - (void)testInvalidateAmbientCache { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be invalidated without an error."]; - [[MLNOfflineStorage sharedOfflineStorage] invalidateAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect cache to be invalidated without an error."]; + [[MLNOfflineStorage sharedOfflineStorage] + invalidateAmbientCacheWithCompletionHandler:^(NSError *_Nullable error) { XCTAssertNil(error); [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; } - (void)testClearCache { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be cleared without an error."]; - [[MLNOfflineStorage sharedOfflineStorage] clearAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect cache to be cleared without an error."]; + [[MLNOfflineStorage sharedOfflineStorage] + clearAmbientCacheWithCompletionHandler:^(NSError *_Nullable error) { XCTAssertNil(error); [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; } - (void)testResetDatabase { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."]; - [[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect database to be reset without an error."]; + [[MLNOfflineStorage sharedOfflineStorage] + resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) { XCTAssertNil(error); [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; } @end diff --git a/platform/darwin/test/MLNPredicateTests.mm b/platform/darwin/test/MLNPredicateTests.mm index 977ad42dd..e85efc588 100644 --- a/platform/darwin/test/MLNPredicateTests.mm +++ b/platform/darwin/test/MLNPredicateTests.mm @@ -1,16 +1,16 @@ -#import #import +#import -#import "NSPredicate+MLNPrivateAdditions.h" #import "MLNValueEvaluator.h" +#import "NSPredicate+MLNPrivateAdditions.h" @implementation NSString (MLNAdditions) - (NSString *)stringByRemovingPointerAddresses { - return [self stringByReplacingOccurrencesOfString:@"\\b0x[0-9a-f]+\\b" - withString:@"0xdeadbeef" - options:NSRegularExpressionSearch - range:NSMakeRange(0, self.length)]; + return [self stringByReplacingOccurrencesOfString:@"\\b0x[0-9a-f]+\\b" + withString:@"0xdeadbeef" + options:NSRegularExpressionSearch + range:NSMakeRange(0, self.length)]; } @end @@ -21,575 +21,694 @@ @implementation MLNPredicateTests - (void)testUnsupportedFilterPredicates { - XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, NSException, NSInvalidArgumentException); - XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, NSException, NSInvalidArgumentException); - XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, NSInvalidArgumentException); - XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a MATCHES 'i\\w{18}n'"].mgl_filter, NSException, NSInvalidArgumentException); - NSPredicate *selectorPredicate = [NSPredicate predicateWithFormat:@"(SELF isKindOfClass: %@)", [MLNPolyline class]]; - XCTAssertThrowsSpecificNamed(selectorPredicate.mgl_filter, NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, + NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, + NSException, NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed( + [NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, + NSInvalidArgumentException); + XCTAssertThrowsSpecificNamed( + [NSPredicate predicateWithFormat:@"a MATCHES 'i\\w{18}n'"].mgl_filter, NSException, + NSInvalidArgumentException); + NSPredicate *selectorPredicate = + [NSPredicate predicateWithFormat:@"(SELF isKindOfClass: %@)", [MLNPolyline class]]; + XCTAssertThrowsSpecificNamed(selectorPredicate.mgl_filter, NSException, + NSInvalidArgumentException); - XCTAssertThrowsSpecificNamed([NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + XCTAssertThrowsSpecificNamed( + [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, + NSDictionary *_Nullable bindings) { XCTAssertTrue(NO, @"Predicate block should not be evaluated."); return NO; - }].mgl_filter, NSException, NSInvalidArgumentException); + }].mgl_filter, + NSException, NSInvalidArgumentException); } - (void)testComparisonPredicates { - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"x"], @YES]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"]; - NSArray *jsonExpression = @[@"<", @[@"to-number", @[@"get", @"x"]], @5]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"]; - NSArray *jsonExpression = @[@">", @[@"to-number", @[@"get", @"x"]], @5]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"]; - NSArray *jsonExpression = @[@"<=", @[@"to-number", @[@"get", @"x"]], @5]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"]; - NSArray *jsonExpression = @[@">=", @[@"to-number", @[@"get", @"x"]], @5]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"]; - NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"x"]], @"value"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; - NSArray *jsonExpression = @[@"==", @[@"geometry-type"], @"Point"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"]; - NSArray *jsonExpression = @[@"==", @[@"id"], @67086180]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"]; - NSArray *jsonExpression = @[@"==", @[@"id"], [NSNull null]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], [NSNull null]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"]; - NSArray *jsonExpression = @[@"!=", @[@"geometry-type"], @"Point"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"]; - NSArray *jsonExpression = @[@"!=", @[@"id"], @67086180]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"]; - NSArray *jsonExpression = @[@"!=", @[@"id"], [NSNull null]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"]; - NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"]; - NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], [NSNull null]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"]; - NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"]; - NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"]; - NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"]; - NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') BETWEEN {'b', 'z'}"]; - NSArray *jsonExpression =@[@"all", @[@"<=", @"b", @[@"to-string", @[@"get", @"a"]]], @[@"<=", @[@"to-string", @[@"get", @"a"]], @"z"]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; - NSArray *jsonExpression = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; - NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:NO]; - } - { - NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; - NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:NO]; - } - { - NSArray *expected = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; - NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:NO]; - } - { - NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"to-number", @[@"id"]], @[@"literal", @[@3002970001, @3004140052, @3002950027, @3002970033]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST($featureIdentifier, 'NSNumber') IN { 3002970001, 3004140052, 3002950027, 3002970033 }"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"!", @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"get", @"a"], @[@"literal", @[@"b", @"c"]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], @[@"LineString", @"Polygon"]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3 } CONTAINS x"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"x IN { 6, 5, 4, 3 }"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"LineString", @"Polygon"], [NSExpression expressionForVariable:@"geometryType"]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN name"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '📍'"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN name"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"]; + NSArray *jsonExpression = @[ @"==", @[ @"get", @"x" ], @YES ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"]; + NSArray *jsonExpression = @[ @"<", @[ @"to-number", @[ @"get", @"x" ] ], @5 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"]; + NSArray *jsonExpression = @[ @">", @[ @"to-number", @[ @"get", @"x" ] ], @5 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"]; + NSArray *jsonExpression = @[ @"<=", @[ @"to-number", @[ @"get", @"x" ] ], @5 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"]; + NSArray *jsonExpression = @[ @">=", @[ @"to-number", @[ @"get", @"x" ] ], @5 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"]; + NSArray *jsonExpression = @[ @">", @[ @"to-string", @[ @"get", @"x" ] ], @"value" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"]; + NSArray *jsonExpression = @[ @"==", @[ @"get", @"a" ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; + NSArray *jsonExpression = @[ @"==", @[ @"geometry-type" ], @"Point" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"]; + NSArray *jsonExpression = @[ @"==", @[ @"id" ], @67086180 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"]; + NSArray *jsonExpression = @[ @"==", @[ @"id" ], [NSNull null] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"]; + NSArray *jsonExpression = @[ @"==", @[ @"get", @"a" ], [NSNull null] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"]; + NSArray *jsonExpression = @[ @"!=", @[ @"geometry-type" ], @"Point" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"]; + NSArray *jsonExpression = @[ @"!=", @[ @"id" ], @67086180 ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"]; + NSArray *jsonExpression = @[ @"!=", @[ @"id" ], [NSNull null] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"]; + NSArray *jsonExpression = @[ @"!=", @[ @"get", @"a" ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"]; + NSArray *jsonExpression = @[ @"!=", @[ @"get", @"a" ], [NSNull null] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"]; + NSArray *jsonExpression = @[ @"<", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"]; + NSArray *jsonExpression = @[ @"<=", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"]; + NSArray *jsonExpression = @[ @">", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"]; + NSArray *jsonExpression = @[ @">=", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') BETWEEN {'b', 'z'}"]; + NSArray *jsonExpression = @[ + @"all", @[ @"<=", @"b", @[ @"to-string", @[ @"get", @"a" ] ] ], + @[ @"<=", @[ @"to-string", @[ @"get", @"a" ] ], @"z" ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSExpression *limits = [NSExpression expressionForAggregate:@[ + [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100] + ]]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + NSArray *jsonExpression = @[ + @"all", @[ @">=", @[ @"to-number", @[ @"get", @"x" ] ], @10 ], + @[ @"<=", @[ @"to-number", @[ @"get", @"x" ] ], @100 ] + ]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[ + @"all", @[ @"<=", @10, @[ @"to-number", @[ @"get", @"x" ] ] ], + @[ @"<=", @[ @"to-number", @[ @"get", @"x" ] ], @100 ] + ]; + NSExpression *limits = [NSExpression expressionForAggregate:@[ + [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100] + ]]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[ + @"all", @[ @"<=", @10, @[ @"to-number", @[ @"get", @"x" ] ] ], + @[ @">=", @100, @[ @"to-number", @[ @"get", @"x" ] ] ] + ]; + NSExpression *limits = [NSExpression expressionForAggregate:@[ + [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100] + ]]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[ + @"all", @[ @">=", @[ @"to-number", @[ @"get", @"x" ] ], @10 ], + @[ @">=", @100, @[ @"to-number", @[ @"get", @"x" ] ] ] + ]; + NSExpression *limits = [NSExpression expressionForAggregate:@[ + [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100] + ]]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:NO]; + } + { + NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ + @"in", @[ @"to-number", @[ @"id" ] ], + @[ @"literal", @[ @3002970001, @3004140052, @3002950027, @3002970033 ] ] + ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"CAST($featureIdentifier, 'NSNumber') IN { 3002970001, " + @"3004140052, 3002950027, 3002970033 }"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = + @[ @"!", @[ @"in", @[ @"get", @"x" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @[ @"get", @"a" ], @[ @"literal", @[ @"b", @"c" ] ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = + @[ @"in", @[ @"geometry-type" ], @[ @"literal", @[ @"LineString", @"Polygon" ] ] ]; + NSPredicate *predicate = [NSPredicate + predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], + @[ @"LineString", @"Polygon" ]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", + [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @[ @"get", @"x" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3 } CONTAINS x"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"x IN { 6, 5, 4, 3 }"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = + @[ @"in", @[ @"geometry-type" ], @[ @"literal", @[ @"LineString", @"Polygon" ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[ @"LineString", @"Polygon" ], + [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", + [NSExpression expressionForVariable:@"geometryType"]]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @"📍", @[ @"get", @"name" ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN name"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @"📍", @[ @"get", @"name" ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '📍'"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN name"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"in", @"📍", @[ @"literal", @"Pinole" ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); #endif - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); - } - { - NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'Pinole' CONTAINS '📍'"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); - } - { - NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} = $featureIdentifier"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"!", @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} = $featureIdentifier"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[@"!", @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} != $featureIdentifier"]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} != $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} > $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} != $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} = $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} < $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSArray *expected = @[ - @"within", - @{ - @"type": @"Polygon", - @"coordinates": @[ - @[ - @[@0, @0], - @[@0, @1], - @[@1, @1], - @[@1, @0], - ], - ], - }, - ]; - CLLocationCoordinate2D coordinates[] = { - { .latitude = 0, .longitude = 0 }, - { .latitude = 1, .longitude = 0 }, - { .latitude = 1, .longitude = 1 }, - { .latitude = 0, .longitude = 1 }, - }; - MLNPolygon *shape = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", shape]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } - { - NSArray *expected = @[ - @"within", - @{ - @"type": @"Feature", - @"id": @"unit", - @"properties": @{}, - @"geometry": @{ - @"type": @"Polygon", - @"coordinates": @[ - @[ - @[@0, @0], - @[@0, @1], - @[@1, @1], - @[@1, @0], - ], - ], - }, - }, - ]; - CLLocationCoordinate2D coordinates[] = { - { .latitude = 0, .longitude = 0 }, - { .latitude = 1, .longitude = 0 }, - { .latitude = 1, .longitude = 1 }, - { .latitude = 0, .longitude = 1 }, - }; - MLNPolygonFeature *feature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; - feature.identifier = @"unit"; - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS SELF", feature]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); - NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"SELF IN %@", feature]; - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] - mustRoundTrip:YES]; - } + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], + [NSPredicate predicateWithValue:NO]); + } + { + NSArray *expected = @[ @"in", @"📍", @[ @"literal", @"Pinole" ] ]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'Pinole' CONTAINS '📍'"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], + [NSPredicate predicateWithValue:NO]); + } + { + NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} = $featureIdentifier"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"!", @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} = $featureIdentifier"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ @"!", @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ]; + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} != $featureIdentifier"]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = + [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} != $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} > $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} != $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} = $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} < $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSArray *expected = @[ + @"within", + @{ + @"type" : @"Polygon", + @"coordinates" : @[ + @[ + @[ @0, @0 ], + @[ @0, @1 ], + @[ @1, @1 ], + @[ @1, @0 ], + ], + ], + }, + ]; + CLLocationCoordinate2D coordinates[] = { + {.latitude = 0, .longitude = 0}, + {.latitude = 1, .longitude = 0}, + {.latitude = 1, .longitude = 1}, + {.latitude = 0, .longitude = 1}, + }; + MLNPolygon *shape = + [MLNPolygon polygonWithCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0])]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", shape]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } + { + NSArray *expected = @[ + @"within", + @{ + @"type" : @"Feature", + @"id" : @"unit", + @"properties" : @{}, + @"geometry" : @{ + @"type" : @"Polygon", + @"coordinates" : @[ + @[ + @[ @0, @0 ], + @[ @0, @1 ], + @[ @1, @1 ], + @[ @1, @0 ], + ], + ], + }, + }, + ]; + CLLocationCoordinate2D coordinates[] = { + {.latitude = 0, .longitude = 0}, + {.latitude = 1, .longitude = 0}, + {.latitude = 1, .longitude = 1}, + {.latitude = 0, .longitude = 1}, + }; + MLNPolygonFeature *feature = + [MLNPolygonFeature polygonWithCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0])]; + feature.identifier = @"unit"; + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS SELF", feature]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); + NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"SELF IN %@", feature]; + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] + mustRoundTrip:YES]; + } } - (void)testComparisonPredicatesWithOptions { - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[c] 'b'"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @YES}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[c] 'b'"]; + NSArray *jsonExpression = @[ + @"==", @[ @"get", @"a" ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @YES} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); #if TARGET_OS_IPHONE - XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); + XCTExpectFailure(@"Awaiting unit test refactoring for " + @"https://github.com/maplibre/maplibre-native/issues/331"); #endif - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[d] 'b'"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @YES, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[cd] 'b'"]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[d] 'b'"]; + NSArray *jsonExpression = @[ + @"==", @[ @"get", @"a" ], @"b", + @[ @"collator", + @{@"case-sensitive" : @YES, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[cd] 'b'"]; + NSArray *jsonExpression = @[ + @"==", @[ @"get", @"a" ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a !=[cd] 'b'"]; - NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <[cd] 'b'"]; - NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <=[cd] 'b'"]; - NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >[cd] 'b'"]; - NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >=[cd] 'b'"]; - NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"TRUE = MLN_FUNCTION('==', a, 'b', MLN_FUNCTION('collator', %@))", @{ - @"case-sensitive": @NO, - @"diacritic-sensitive": @NO, - @"locale": @"tlh", + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a !=[cd] 'b'"]; + NSArray *jsonExpression = @[ + @"!=", @[ @"get", @"a" ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <[cd] 'b'"]; + NSArray *jsonExpression = @[ + @"<", @[ @"to-string", @[ @"get", @"a" ] ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <=[cd] 'b'"]; + NSArray *jsonExpression = @[ + @"<=", @[ @"to-string", @[ @"get", @"a" ] ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >[cd] 'b'"]; + NSArray *jsonExpression = @[ + @">", @[ @"to-string", @[ @"get", @"a" ] ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >=[cd] 'b'"]; + NSArray *jsonExpression = @[ + @">=", @[ @"to-string", @[ @"get", @"a" ] ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO} ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate + predicateWithFormat:@"TRUE = MLN_FUNCTION('==', a, 'b', MLN_FUNCTION('collator', %@))", @{ + @"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO, + @"locale" : @"tlh", }]; - NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", - @[@"collator", - @{@"case-sensitive": @NO, - @"diacritic-sensitive": @NO, - @"locale": @"tlh"}]]; - XCTAssertEqualObjects([predicate.mgl_jsonExpressionObject lastObject], jsonExpression); - } + NSArray *jsonExpression = @[ + @"==", @[ @"get", @"a" ], @"b", + @[ @"collator", + @{@"case-sensitive" : @NO, + @"diacritic-sensitive" : @NO, + @"locale" : @"tlh"} ] + ]; + XCTAssertEqualObjects([predicate.mgl_jsonExpressionObject lastObject], jsonExpression); + } - // https://github.com/mapbox/mapbox-gl-js/issues/9339 - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} =[c] $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} =[d] $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} !=[cd] $featureIdentifier"]; - XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); - } + // https://github.com/mapbox/mapbox-gl-js/issues/9339 + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} =[c] $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} =[d] $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } + { + NSPredicate *predicate = + [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} !=[cd] $featureIdentifier"]; + XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, + NSInvalidArgumentException); + } } - (void)testCompoundPredicates { - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]; - NSArray *jsonExpression = @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]; - NSArray *jsonExpression = @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"]; - NSArray *jsonExpression = @[@"!", @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]; - NSArray *jsonExpression = @[@"!", @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); - [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"]; - NSArray *jsonExpression = @[@"!", @[@"==", @[@"get", @"a"], [NSNull null]]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } - { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"]; - NSArray *jsonExpression = @[@"!", @[@"!=", @[@"get", @"a"], [NSNull null]]]; - XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); - [self testSymmetryWithPredicate:predicate - mustRoundTrip:NO]; - } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]; + NSArray *jsonExpression = + @[ @"all", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]; + NSArray *jsonExpression = + @[ @"any", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"]; + NSArray *jsonExpression = @[ + @"!", @[ @"all", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]; + NSArray *jsonExpression = @[ + @"!", @[ @"any", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ] + ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); + [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] + mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"]; + NSArray *jsonExpression = @[ @"!", @[ @"==", @[ @"get", @"a" ], [NSNull null] ] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } + { + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"]; + NSArray *jsonExpression = @[ @"!", @[ @"!=", @[ @"get", @"a" ], [NSNull null] ] ]; + XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); + [self testSymmetryWithPredicate:predicate mustRoundTrip:NO]; + } } -- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate mustRoundTrip:(BOOL)mustRoundTrip { - auto forwardFilter = forwardPredicate.mgl_filter; - NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; - if (mustRoundTrip) { - // A collection of ints may turn into an aggregate of longs, for - // example, so compare formats instead of the predicates themselves. - XCTAssertEqualObjects(forwardPredicate.predicateFormat.stringByRemovingPointerAddresses, forwardPredicateAfter.predicateFormat.stringByRemovingPointerAddresses); - } else { - XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); - } +- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate + mustRoundTrip:(BOOL)mustRoundTrip { + auto forwardFilter = forwardPredicate.mgl_filter; + NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; + if (mustRoundTrip) { + // A collection of ints may turn into an aggregate of longs, for + // example, so compare formats instead of the predicates themselves. + XCTAssertEqualObjects(forwardPredicate.predicateFormat.stringByRemovingPointerAddresses, + forwardPredicateAfter.predicateFormat.stringByRemovingPointerAddresses); + } else { + XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); + } } @end diff --git a/platform/darwin/test/MLNResourceTests.mm b/platform/darwin/test/MLNResourceTests.mm index 581c6fd7b..8ecc7159e 100644 --- a/platform/darwin/test/MLNResourceTests.mm +++ b/platform/darwin/test/MLNResourceTests.mm @@ -3,9 +3,9 @@ #import namespace mbgl { - extern NSURL *resourceURL(const Resource& resource); - extern BOOL isValidMapboxEndpoint(NSURL *url); -} +extern NSURL *resourceURL(const Resource &resource); +extern BOOL isValidMapboxEndpoint(NSURL *url); +} // namespace mbgl @interface MLNResourceTests : XCTestCase @end @@ -13,77 +13,78 @@ namespace mbgl { @implementation MLNResourceTests - (void)testValidEndpoints { - using namespace mbgl; + using namespace mbgl; - XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"])); - XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"])); - XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"])); - XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"])); + XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"])); + XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"])); + XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"])); + XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"])); - XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"])); - XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"])); - XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"])); - XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"])); + XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"])); + XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"])); + XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"])); + XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"])); } - (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL { + using namespace mbgl; - using namespace mbgl; + // Is our test URL "correct" for subsequent checks? + { + NSURL *url = [NSURL URLWithString:@(testURL.c_str())]; + NSURLComponents *components = [NSURLComponents componentsWithURL:url + resolvingAgainstBaseURL:NO]; + NSArray *items = components.queryItems; + XCTAssert(items.count == 2); + } - // Is our test URL "correct" for subsequent checks? - { - NSURL *url = [NSURL URLWithString:@(testURL.c_str())]; - NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - NSArray *items = components.queryItems; - XCTAssert(items.count == 2); - } + Resource resource(Resource::Kind::Unknown, testURL); - Resource resource(Resource::Kind::Unknown, testURL); + // Now check offline + resource.setUsage(Resource::Usage::Offline); - // Now check offline - resource.setUsage(Resource::Usage::Offline); + { + NSURL *url = resourceURL(resource); + NSURLComponents *components = [NSURLComponents componentsWithURL:url + resolvingAgainstBaseURL:NO]; - { - NSURL *url = resourceURL(resource); - NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - - // For offline, we expect a single offline query item - NSInteger foundCount = 0; + // For offline, we expect a single offline query item + NSInteger foundCount = 0; #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR - for (NSURLQueryItem *item in components.queryItems) { - if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) || - ([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) || - ([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) { - foundCount++; - } - XCTAssertFalse([item.name isEqualToString:@"sku"]); - } - - XCTAssert(foundCount == 3); -#else - // NOTE: Currently the macOS SDK does not supply the sku or offline query parameters - for (NSURLQueryItem *item in components.queryItems) { - if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) || - ([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) { - foundCount++; - } - XCTAssertFalse([item.name isEqualToString:@"sku"]); - } - - XCTAssert(foundCount == 2); -#endif + for (NSURLQueryItem *item in components.queryItems) { + if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) || + ([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) || + ([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) { + foundCount++; + } + XCTAssertFalse([item.name isEqualToString:@"sku"]); } + + XCTAssert(foundCount == 3); +#else + // NOTE: Currently the macOS SDK does not supply the sku or offline query parameters + for (NSURLQueryItem *item in components.queryItems) { + if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) || + ([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) { + foundCount++; + } + XCTAssertFalse([item.name isEqualToString:@"sku"]); + } + + XCTAssert(foundCount == 2); +#endif + } } - (void)testOfflineQueryParameterIsAddedForOfflineResource { - std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two"; - [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; + std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two"; + [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; } - (void)testOfflineQueryParameterIsAddedForOfflineResourceForChina { - std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two"; - [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; + std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two"; + [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; } @end diff --git a/platform/darwin/test/MLNShapeSourceTests.mm b/platform/darwin/test/MLNShapeSourceTests.mm index 12a228e3c..ec65c429e 100644 --- a/platform/darwin/test/MLNShapeSourceTests.mm +++ b/platform/darwin/test/MLNShapeSourceTests.mm @@ -13,322 +13,390 @@ @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}; + 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); + 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)); + options = @{MLNShapeSourceOptionClustered : @"number 1"}; + XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options)); } - (void)testNilShape { - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil]; - XCTAssertNil(source.shape); + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil]; + XCTAssertNil(source.shape); } - (void)testUnclusterableShape { - NSDictionary *options = @{ - MLNShapeSourceOptionClustered: @YES, - }; + NSDictionary *options = @{ + MLNShapeSourceOptionClustered : @YES, + }; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:[[MLNPointFeature alloc] init] options:options]; - XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]); + 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]]); + 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]]}}]}"; - 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]; - 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]]); + 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]]); + 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"]); + 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]; + 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]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" + shape:polylineFeature + options:nil]; - XCTAssertNotNil(source.shape); - XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]); + 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)}; + 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"}]; + 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}; + 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]; + 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); + 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 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)}; + 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]; + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; - MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; + MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates + count:5 + interiorPolygons:@[ polygon ]]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" + shape:polygonFeature + options:nil]; - XCTAssertNotNil(source.shape); - XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]); + 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]]; + 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]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" + shape:multiPolylineFeature + options:nil]; - XCTAssertNotNil(source.shape); - XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]); + 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 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)}; + 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]; + 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]]; + MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates + count:5 + interiorPolygons:@[ polygon ]]; + MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates + count:5 + interiorPolygons:@[ polygon ]]; - MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[firstPolygon, secondPolygon]]; + MLNMultiPolygonFeature *multiPolygonFeature = + [MLNMultiPolygonFeature multiPolygonWithPolygons:@[ firstPolygon, secondPolygon ]]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolygonFeature options:nil]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" + shape:multiPolygonFeature + options:nil]; - XCTAssertNotNil(source.shape); - XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]); + XCTAssertNotNil(source.shape); + XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]); } - (void)testMLNShapeSourceWithPointFeature { - MLNPointFeature *pointFeature = [MLNPointFeature new]; - pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); + MLNPointFeature *pointFeature = [MLNPointFeature new]; + pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointFeature options:nil]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" + shape:pointFeature + options:nil]; - XCTAssertNotNil(source.shape); - XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]); + 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]; + 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]]); + 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 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)}; + 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]; + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; - MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; + 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]; + CLLocationCoordinate2D coordinates_2[] = {CLLocationCoordinate2DMake(0, 0), + CLLocationCoordinate2DMake(10, 10)}; + MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2 + count:2]; - MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygonFeature, polygonFeature]]; + MLNMultiPolygonFeature *multiPolygonFeature = + [MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygonFeature, polygonFeature ]]; - MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polylineFeature, polylineFeature]]; + MLNMultiPolylineFeature *multiPolylineFeature = + [MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polylineFeature, polylineFeature ]]; - MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; + MLNPointCollectionFeature *pointCollectionFeature = + [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; - MLNPointFeature *pointFeature = [MLNPointFeature new]; - pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); + MLNPointFeature *pointFeature = [MLNPointFeature new]; + pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); - MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, pointCollectionFeature, pointFeature]]; + MLNShapeCollectionFeature *shapeCollectionFeature = + [MLNShapeCollectionFeature shapeCollectionWithShapes:@[ + polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, + pointCollectionFeature, pointFeature + ]]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shapeCollectionFeature options:nil]; + 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"); + 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)}; + 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]; + 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; + 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"); + 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]; + // 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"); + 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)}; + 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]; + 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; + 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"); + XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]); + XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape"); } @end diff --git a/platform/darwin/test/MLNStyleTests.mm b/platform/darwin/test/MLNStyleTests.mm index 9f36afeef..933537c0f 100644 --- a/platform/darwin/test/MLNStyleTests.mm +++ b/platform/darwin/test/MLNStyleTests.mm @@ -1,13 +1,13 @@ #import -#import "NSBundle+MLNAdditions.h" #import "MLNVectorTileSource_Private.h" +#import "NSBundle+MLNAdditions.h" #import #if TARGET_OS_IPHONE - #import +#import #else - #import +#import #endif #import @@ -19,422 +19,543 @@ @end @implementation MLNStyleTests { - XCTestExpectation *_styleLoadingExpectation; + XCTestExpectation *_styleLoadingExpectation; } - (void)setUp { - [super setUp]; + [super setUp]; - [MLNSettings useWellKnownTileServer:MLNMapTiler]; - [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + [MLNSettings useWellKnownTileServer:MLNMapTiler]; + [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL]; - self.mapView.delegate = self; - if (!self.mapView.style) { - _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self waitForExpectationsWithTimeout:10 handler:nil]; - } + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL]; + self.mapView.delegate = self; + if (!self.mapView.style) { + _styleLoadingExpectation = + [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + } } - (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style { - XCTAssertNotNil(mapView.style); - XCTAssertEqual(mapView.style, style); + XCTAssertNotNil(mapView.style); + XCTAssertEqual(mapView.style, style); - [_styleLoadingExpectation fulfill]; + [_styleLoadingExpectation fulfill]; } - (void)tearDown { - _styleLoadingExpectation = nil; - self.mapView = nil; + _styleLoadingExpectation = nil; + self.mapView = nil; - [super tearDown]; + [super tearDown]; } // TODO: remove backed property _style #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-property-ivar" - (MLNStyle *)style { - return self.mapView.style; + return self.mapView.style; } #pragma clang diagnostic pop - (void)testName { - XCTAssertNil(self.style.name); + XCTAssertNil(self.style.name); } - (void)testSources { - NSSet *initialSources = self.style.sources; - if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) { - XCTAssertEqual(self.style.sources.count, 1UL); - } else { - XCTAssertEqual(self.style.sources.count, 0UL); - } - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; - [self.style addSource:shapeSource]; - XCTAssertEqual(self.style.sources.count, initialSources.count + 1); - XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]); - [self.style removeSource:shapeSource]; - XCTAssertEqual(self.style.sources.count, initialSources.count); + NSSet *initialSources = self.style.sources; + if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) { + XCTAssertEqual(self.style.sources.count, 1UL); + } else { + XCTAssertEqual(self.style.sources.count, 0UL); + } + MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" + shape:nil + options:nil]; + [self.style addSource:shapeSource]; + XCTAssertEqual(self.style.sources.count, initialSources.count + 1); + XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]); + [self.style removeSource:shapeSource]; + XCTAssertEqual(self.style.sources.count, initialSources.count); } - (void)testAddingSourcesTwice { - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; - [self.style addSource:shapeSource]; - XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, MLNRedundantSourceException); + MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" + shape:nil + options:nil]; + [self.style addSource:shapeSource]; + XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, + MLNRedundantSourceException); - MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42]; - [self.style addSource:rasterTileSource]; - XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, MLNRedundantSourceException); + MLNRasterTileSource *rasterTileSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" + configurationURL:[NSURL URLWithString:@".json"] + tileSize:42]; + [self.style addSource:rasterTileSource]; + XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, + MLNRedundantSourceException); - MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]]; - [self.style addSource:vectorTileSource]; - XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, MLNRedundantSourceException); + MLNVectorTileSource *vectorTileSource = + [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" + configurationURL:[NSURL URLWithString:@".json"]]; + [self.style addSource:vectorTileSource]; + XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, + MLNRedundantSourceException); } - (void)testAddingSourcesWithDuplicateIdentifiers { - MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; - MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; + MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc] + initWithIdentifier:@"my-source" + configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; + MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc] + initWithIdentifier:@"my-source" + configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; - [self.style addSource: source1]; - XCTAssertThrowsSpecificNamed([self.style addSource: source2], NSException, MLNRedundantSourceIdentifierException); + [self.style addSource:source1]; + XCTAssertThrowsSpecificNamed([self.style addSource:source2], NSException, + MLNRedundantSourceIdentifierException); } - (void)testRemovingSourcesBeforeAddingThem { - MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; - [self.style removeSource:rasterTileSource]; - [self.style addSource:rasterTileSource]; - XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]); + MLNRasterTileSource *rasterTileSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" + tileURLTemplates:@[] + options:nil]; + [self.style removeSource:rasterTileSource]; + [self.style addSource:rasterTileSource]; + XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]); - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil]; - [self.style removeSource:shapeSource]; - [self.style addSource:shapeSource]; - XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]); + MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" + shape:nil + options:nil]; + [self.style removeSource:shapeSource]; + [self.style addSource:shapeSource]; + XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]); - MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil]; - [self.style removeSource:vectorTileSource]; - [self.style addSource:vectorTileSource]; - XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]); + MLNVectorTileSource *vectorTileSource = + [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" + tileURLTemplates:@[] + options:nil]; + [self.style removeSource:vectorTileSource]; + [self.style addSource:vectorTileSource]; + XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]); } - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { - // Add a raster tile source - MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style addSource:rasterTileSource]; + // Add a raster tile source + MLNRasterTileSource *rasterTileSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier" + tileURLTemplates:@[] + options:nil]; + [self.style addSource:rasterTileSource]; - // Attempt to remove an image source with the same identifier as the raster tile source - MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"some-identifier" coordinateQuad: { } URL:[NSURL URLWithString:@"http://host/image.png"]]; - [self.style removeSource:imageSource]; - // The raster tile source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MLNRasterTileSource class]]); + // Attempt to remove an image source with the same identifier as the raster tile source + MLNImageSource *imageSource = + [[MLNImageSource alloc] initWithIdentifier:@"some-identifier" + coordinateQuad:{} + URL:[NSURL URLWithString:@"http://host/image.png"]]; + [self.style removeSource:imageSource]; + // The raster tile source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] + isMemberOfClass:[MLNRasterTileSource class]]); - // Remove the raster tile source - [self.style removeSource:rasterTileSource]; + // Remove the raster tile source + [self.style removeSource:rasterTileSource]; - // Add the shape source - [self.style addSource:imageSource]; + // Add the shape source + [self.style addSource:imageSource]; - // Attempt to remove a vector tile source with the same identifer as the shape source - MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style removeSource:vectorTileSource]; - // The image source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNImageSource class]]); + // Attempt to remove a vector tile source with the same identifer as the shape source + MLNVectorTileSource *vectorTileSource = + [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" + tileURLTemplates:@[] + options:nil]; + [self.style removeSource:vectorTileSource]; + // The image source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] + isMemberOfClass:[MLNImageSource class]]); - // Remove the image source - [self.style removeSource:imageSource]; + // Remove the image source + [self.style removeSource:imageSource]; - // Add the vector tile source - [self.style addSource:vectorTileSource]; + // Add the vector tile source + [self.style addSource:vectorTileSource]; - // Attempt to remove the previously created raster tile source that has the same identifer as the shape source - [self.style removeSource:rasterTileSource]; - // The vector tile source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]); + // Attempt to remove the previously created raster tile source that has the same identifer as the + // shape source + [self.style removeSource:rasterTileSource]; + // The vector tile source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] + isMemberOfClass:[MLNVectorTileSource class]]); } - (void)testRemovingSourceInUse { - // Add a raster tile source - MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; - [self.style addSource:vectorTileSource]; + // Add a raster tile source + MLNVectorTileSource *vectorTileSource = + [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" + tileURLTemplates:@[] + options:nil]; + [self.style addSource:vectorTileSource]; - // Add a layer using it - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:vectorTileSource]; - [self.style addLayer:fillLayer]; + // Add a layer using it + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" + source:vectorTileSource]; + [self.style addLayer:fillLayer]; - // Attempt to remove the raster tile source - NSError *error; - BOOL result = [self.style removeSource:vectorTileSource error:&error]; + // Attempt to remove the raster tile source + NSError *error; + BOOL result = [self.style removeSource:vectorTileSource error:&error]; - XCTAssertFalse(result); - XCTAssertEqualObjects(error.domain, MLNErrorDomain); - XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove); + XCTAssertFalse(result); + XCTAssertEqualObjects(error.domain, MLNErrorDomain); + XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove); - // Ensure it is still there - XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]); + // Ensure it is still there + XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier] + isMemberOfClass:[MLNVectorTileSource class]]); } - (void)testLayers { - NSArray *initialLayers = self.style.layers; - if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { - XCTAssertEqual(self.style.layers.count, 1UL); - } else { - XCTAssertEqual(self.style.layers.count, 0UL); - } - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; - [self.style addSource:shapeSource]; - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:shapeSource]; - [self.style addLayer:fillLayer]; - XCTAssertEqual(self.style.layers.count, initialLayers.count + 1); - XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]); - [self.style removeLayer:fillLayer]; - XCTAssertEqual(self.style.layers.count, initialLayers.count); + NSArray *initialLayers = self.style.layers; + if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { + XCTAssertEqual(self.style.layers.count, 1UL); + } else { + XCTAssertEqual(self.style.layers.count, 0UL); + } + MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" + shape:nil + options:nil]; + [self.style addSource:shapeSource]; + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" + source:shapeSource]; + [self.style addLayer:fillLayer]; + XCTAssertEqual(self.style.layers.count, initialLayers.count + 1); + XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]); + [self.style removeLayer:fillLayer]; + XCTAssertEqual(self.style.layers.count, initialLayers.count); } - (void)testAddingLayersTwice { - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" + shape:nil + options:nil]; - MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"]; - [self.style addLayer:backgroundLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, MLNRedundantLayerException); + MLNBackgroundStyleLayer *backgroundLayer = + [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"]; + [self.style addLayer:backgroundLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, + MLNRedundantLayerException); - MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" source:source]; - [self.style addLayer:circleLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, MLNRedundantLayerException); + MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" + source:source]; + [self.style addLayer:circleLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, + MLNRedundantLayerException); - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:source]; - [self.style addLayer:fillLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, MLNRedundantLayerException); + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" + source:source]; + [self.style addLayer:fillLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, + MLNRedundantLayerException); - MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; - [self.style addLayer:lineLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, MLNRedundantLayerException); + MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" + source:source]; + [self.style addLayer:lineLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, + MLNRedundantLayerException); - MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" source:source]; - [self.style addLayer:rasterLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, MLNRedundantLayerException); + MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" + source:source]; + [self.style addLayer:rasterLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, + MLNRedundantLayerException); - MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" source:source]; - [self.style addLayer:symbolLayer]; - XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, MLNRedundantLayerException); + MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" + source:source]; + [self.style addLayer:symbolLayer]; + XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, + MLNRedundantLayerException); } - (void)testAddingLayersWithDuplicateIdentifiers { - // Just some source - MLNVectorTileSource *source = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; - [self.style addSource: source]; + // Just some source + MLNVectorTileSource *source = [[MLNVectorTileSource alloc] + initWithIdentifier:@"my-source" + configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; + [self.style addSource:source]; - // Add initial layer - MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]; - [self.style addLayer:initial]; + // Add initial layer + MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" + source:source]; + [self.style addLayer:initial]; - // Try to add the duplicate - XCTAssertThrowsSpecificNamed([self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]], NSException, @"MLNRedundantLayerIdentifierException"); - XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] belowLayer:initial],NSException, @"MLNRedundantLayerIdentifierException"); - XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] aboveLayer:initial], NSException, @"MLNRedundantLayerIdentifierException"); - XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); - XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); + // Try to add the duplicate + XCTAssertThrowsSpecificNamed( + [self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" + source:source]], + NSException, @"MLNRedundantLayerIdentifierException"); + XCTAssertThrowsSpecificNamed( + [self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" + source:source] + belowLayer:initial], + NSException, @"MLNRedundantLayerIdentifierException"); + XCTAssertThrowsSpecificNamed( + [self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" + source:source] + aboveLayer:initial], + NSException, @"MLNRedundantLayerIdentifierException"); + XCTAssertThrowsSpecificNamed( + [self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" + source:source] + atIndex:0], + NSException, @"MLNRedundantLayerIdentifierException"); + XCTAssertThrowsSpecificNamed( + [self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] + atIndex:0], + NSException, @"MLNRedundantLayerIdentifierException"); } - (void)testRemovingLayerBeforeAddingSameLayer { - { - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding" shape:nil options:nil]; + { + MLNShapeSource *source = + [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding" + shape:nil + options:nil]; - // Attempting to find a layer with identifier will trigger an exception if the source associated with the layer is not added - [self.style addSource:source]; + // Attempting to find a layer with identifier will trigger an exception if the source associated + // with the layer is not added + [self.style addSource:source]; - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:source]; - [self.style removeLayer:fillLayer]; - [self.style addLayer:fillLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]); + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" + source:source]; + [self.style removeLayer:fillLayer]; + [self.style addLayer:fillLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]); - MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source]; - [self.style removeLayer:symbolLayer]; - [self.style addLayer:symbolLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]); + MLNSymbolStyleLayer *symbolLayer = + [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source]; + [self.style removeLayer:symbolLayer]; + [self.style addLayer:symbolLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]); - MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source]; - [self.style removeLayer:lineLayer]; - [self.style addLayer:lineLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]); + MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" + source:source]; + [self.style removeLayer:lineLayer]; + [self.style addLayer:lineLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]); - MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source]; - [self.style removeLayer:circleLayer]; - [self.style addLayer:circleLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]); + MLNCircleStyleLayer *circleLayer = + [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source]; + [self.style removeLayer:circleLayer]; + [self.style addLayer:circleLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]); - MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"]; - [self.style removeLayer:backgroundLayer]; - [self.style addLayer:backgroundLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]); - } + MLNBackgroundStyleLayer *backgroundLayer = + [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"]; + [self.style removeLayer:backgroundLayer]; + [self.style addLayer:backgroundLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]); + } - { - MLNRasterTileSource *rasterSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; - [self.style addSource:rasterSource]; + { + MLNRasterTileSource *rasterSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" + tileURLTemplates:@[] + options:nil]; + [self.style addSource:rasterSource]; - MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource]; - [self.style removeLayer:rasterLayer]; - [self.style addLayer:rasterLayer]; - XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]); - } + MLNRasterStyleLayer *rasterLayer = + [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource]; + [self.style removeLayer:rasterLayer]; + [self.style addLayer:rasterLayer]; + XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]); + } } - (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier { - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" shape:nil options:nil]; - [self.style addSource:source]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" + shape:nil + options:nil]; + [self.style addSource:source]; - // Add a fill layer - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier" source:source]; - [self.style addLayer:fillLayer]; + // Add a fill layer + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier" + source:source]; + [self.style addLayer:fillLayer]; - // Attempt to remove a line layer with the same identifier as the fill layer - MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier source:source]; - [self.style removeLayer:lineLayer]; + // Attempt to remove a line layer with the same identifier as the fill layer + MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier + source:source]; + [self.style removeLayer:lineLayer]; - XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] isMemberOfClass:[MLNFillStyleLayer class]]); + XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] + isMemberOfClass:[MLNFillStyleLayer class]]); } - (NSString *)stringWithContentsOfStyleHeader { - NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL - URLByAppendingPathComponent:@"Headers" isDirectory:YES] - URLByAppendingPathComponent:@"MLNStyle.h"]; - NSError *styleHeaderError; - NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL usedEncoding:nil error:&styleHeaderError]; - XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h."); - return styleHeader; + NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL + URLByAppendingPathComponent:@"Headers" + isDirectory:YES] URLByAppendingPathComponent:@"MLNStyle.h"]; + NSError *styleHeaderError; + NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL + usedEncoding:nil + error:&styleHeaderError]; + XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h."); + return styleHeader; } - (void)testImages { - NSString *imageName = @"TrackingLocationMask"; + NSString *imageName = @"TrackingLocationMask"; #if TARGET_OS_IPHONE - MLNImage *image = [MLNImage imageNamed:imageName - inBundle:[NSBundle bundleForClass:[self class]] - compatibleWithTraitCollection:nil]; + MLNImage *image = [MLNImage imageNamed:imageName + inBundle:[NSBundle bundleForClass:[self class]] + compatibleWithTraitCollection:nil]; #else - MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName]; + MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName]; #endif - XCTAssertNotNil(image); + XCTAssertNotNil(image); - [self.style setImage:image forName:imageName]; - MLNImage *styleImage = [self.style imageForName:imageName]; + [self.style setImage:image forName:imageName]; + MLNImage *styleImage = [self.style imageForName:imageName]; - XCTAssertNotNil(styleImage); - XCTAssertEqual(image.size.width, styleImage.size.width); - XCTAssertEqual(image.size.height, styleImage.size.height); + XCTAssertNotNil(styleImage); + XCTAssertEqual(image.size.width, styleImage.size.width); + XCTAssertEqual(image.size.height, styleImage.size.height); } - (void)testLayersOrder { - NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; - NSURL *url = [NSURL fileURLWithPath:filePath]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" URL:url options:nil]; - [self.style addSource:source]; + NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" + ofType:@"geojson"]; + NSURL *url = [NSURL fileURLWithPath:filePath]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" + URL:url + options:nil]; + [self.style addSource:source]; - MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" source:source]; - [self.style addLayer:layer1]; + MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" + source:source]; + [self.style addLayer:layer1]; - MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" source:source]; - [self.style addLayer:layer3]; + MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" + source:source]; + [self.style addLayer:layer3]; - MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" source:source]; - [self.style insertLayer:layer2 aboveLayer:layer1]; + MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" + source:source]; + [self.style insertLayer:layer2 aboveLayer:layer1]; - MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" source:source]; - [self.style insertLayer:layer4 aboveLayer:layer3]; + MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" + source:source]; + [self.style insertLayer:layer4 aboveLayer:layer3]; - MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" source:source]; - [self.style insertLayer:layer0 belowLayer:layer1]; + MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" + source:source]; + [self.style insertLayer:layer0 belowLayer:layer1]; - NSArray *layers = [self.style layers]; - NSUInteger startIndex = 0; - if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { - startIndex++; - } + NSArray *layers = [self.style layers]; + NSUInteger startIndex = 0; + if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { + startIndex++; + } - XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier); - XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier); - XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier); - XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier); - XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier); + XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier); + XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier); + XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier); + XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier); + XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier); } // MARK: Localization tests - (void)testLanguageMatching { - { - NSArray *preferences = @[@"en"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); - } - { - NSArray *preferences = @[@"en-US"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); - } - { - NSArray *preferences = @[@"fr"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr"); - } - { - NSArray *preferences = @[@"zh-Hans"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); - } - { - NSArray *preferences = @[@"zh-Hans", @"en"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); - } - { - NSArray *preferences = @[@"zh-Hant"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant"); - } - { - NSArray *preferences = @[@"en", @"fr", @"el"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); - } - { - NSArray *preferences = @[@"tlh"]; - XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); - } - { - NSArray *preferences = @[@"tlh", @"en"]; - XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); - } - { - NSArray *preferences = @[@"mul"]; - XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); - } + { + NSArray *preferences = @[ @"en" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[ @"en-US" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[ @"fr" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr"); + } + { + NSArray *preferences = @[ @"zh-Hans" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); + } + { + NSArray *preferences = @[ @"zh-Hans", @"en" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); + } + { + NSArray *preferences = @[ @"zh-Hant" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant"); + } + { + NSArray *preferences = @[ @"en", @"fr", @"el" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[ @"tlh" ]; + XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); + } + { + NSArray *preferences = @[ @"tlh", @"en" ]; + XCTAssertEqualObjects( + [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); + } + { + NSArray *preferences = @[ @"mul" ]; + XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); + } } // MARK: Transition tests -- (void)testTransition -{ - MLNTransition transitionTest = MLNTransitionMake(5, 4); +- (void)testTransition { + MLNTransition transitionTest = MLNTransitionMake(5, 4); - self.style.transition = transitionTest; + self.style.transition = transitionTest; - XCTAssert(self.style.transition.delay == transitionTest.delay); - XCTAssert(self.style.transition.duration == transitionTest.duration); + XCTAssert(self.style.transition.delay == transitionTest.delay); + XCTAssert(self.style.transition.duration == transitionTest.duration); } -- (void)testPerformsPlacementTransitions -{ - XCTAssertTrue(self.style.performsPlacementTransitions, @"The default value for enabling placement transitions should be YES."); +- (void)testPerformsPlacementTransitions { + XCTAssertTrue(self.style.performsPlacementTransitions, + @"The default value for enabling placement transitions should be YES."); - self.style.performsPlacementTransitions = NO; - XCTAssertFalse(self.style.performsPlacementTransitions, @"Enabling placement transitions should be NO."); + self.style.performsPlacementTransitions = NO; + XCTAssertFalse(self.style.performsPlacementTransitions, + @"Enabling placement transitions should be NO."); } - (void)testStyleJSON { - // Test getting style JSON - NSString *styleJSON = self.style.styleJSON; - XCTAssertNotNil(styleJSON, @"Style JSON should not be nil"); + // Test getting style JSON + NSString *styleJSON = self.style.styleJSON; + XCTAssertNotNil(styleJSON, @"Style JSON should not be nil"); - // Verify the JSON is valid - NSError *error = nil; - id jsonObject = [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding] - options:0 - error:&error]; - XCTAssertNil(error, @"Style JSON should be valid JSON"); - XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object"); - XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], @"Style JSON should represent a dictionary"); + // Verify the JSON is valid + NSError *error = nil; + id jsonObject = + [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding] + options:0 + error:&error]; + XCTAssertNil(error, @"Style JSON should be valid JSON"); + XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object"); + XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], + @"Style JSON should represent a dictionary"); } @end diff --git a/platform/darwin/test/MLNTileSetTests.mm b/platform/darwin/test/MLNTileSetTests.mm index 25d35b2f5..3469c6f27 100644 --- a/platform/darwin/test/MLNTileSetTests.mm +++ b/platform/darwin/test/MLNTileSetTests.mm @@ -1,9 +1,9 @@ #import #import -#import "MLNVectorTileSource.h" -#import "MLNTileSource_Private.h" #import "MLNGeometry_Private.h" +#import "MLNTileSource_Private.h" +#import "MLNVectorTileSource.h" #include @@ -14,180 +14,194 @@ @implementation MLNTileSetTests namespace { - // a tile set that provides an mbgl tile set - NSArray *tileURLTemplates = @[@"tile.1", @"tile.2", @"tile.3"]; +// a tile set that provides an mbgl tile set +NSArray *tileURLTemplates = @[ @"tile.1", @"tile.2", @"tile.3" ]; -} +} // namespace - (void)testTileSetFromTileURLTemplates { - mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil); + mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil); - // has the correct URL templates - XCTAssertEqual(tileSet.tiles.size(), 3UL); - XCTAssertEqual(tileSet.tiles[0], "tile.1"); - XCTAssertEqual(tileSet.tiles[1], "tile.2"); - XCTAssertEqual(tileSet.tiles[2], "tile.3"); + // has the correct URL templates + XCTAssertEqual(tileSet.tiles.size(), 3UL); + XCTAssertEqual(tileSet.tiles[0], "tile.1"); + XCTAssertEqual(tileSet.tiles[1], "tile.2"); + XCTAssertEqual(tileSet.tiles[2], "tile.3"); - // has the default scheme - XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ); + // has the default scheme + XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ); - // when the tile set has no min or max zoom level set - // the mbgl object has default values for min and max zoom level - XCTAssertEqual(tileSet.zoomRange.min, 0); - XCTAssertEqual(tileSet.zoomRange.max, 22); + // when the tile set has no min or max zoom level set + // the mbgl object has default values for min and max zoom level + XCTAssertEqual(tileSet.zoomRange.min, 0); + XCTAssertEqual(tileSet.zoomRange.max, 22); - // when the tile set has min and/or max zoom level set - tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ - MLNTileSourceOptionMinimumZoomLevel: @1, - MLNTileSourceOptionMaximumZoomLevel: @2, - }); + // when the tile set has min and/or max zoom level set + tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{ + MLNTileSourceOptionMinimumZoomLevel : @1, + MLNTileSourceOptionMaximumZoomLevel : @2, + }); - // the mbgl object reflects the set values for min and max zoom level - XCTAssertEqual(tileSet.zoomRange.min, 1); - XCTAssertEqual(tileSet.zoomRange.max, 2); + // the mbgl object reflects the set values for min and max zoom level + XCTAssertEqual(tileSet.zoomRange.min, 1); + XCTAssertEqual(tileSet.zoomRange.max, 2); - // when the tile set has a bounds set - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78)); - tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ - MLNTileSourceOptionCoordinateBounds: @(bounds), - }); + // when the tile set has a bounds set + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), + CLLocationCoordinate2DMake(56, 78)); + tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{ + MLNTileSourceOptionCoordinateBounds : @(bounds), + }); - // the mbgl object reflects the set values for the bounds - XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds"); - if (tileSet.bounds) { - MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds); - XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip"); - } + // the mbgl object reflects the set values for the bounds + XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds"); + if (tileSet.bounds) { + MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds); + XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip"); + } - // when the tile set has an attribution - NSString *attribution = @"my tileset © ©️🎈"; - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionAttributionHTMLString: attribution, - }); + // when the tile set has an attribution + NSString *attribution = @"my tileset © ©️🎈"; + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionAttributionHTMLString : attribution, + }); - // the attribution is reflected by the mbgl tileset - XCTAssertEqual(tileSet.attribution, attribution.UTF8String); + // the attribution is reflected by the mbgl tileset + XCTAssertEqual(tileSet.attribution, attribution.UTF8String); - // when the tile set has attribution infos - MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc] initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"] - URL:[NSURL URLWithString:@"https://www.mapbox.com/"]]; + // when the tile set has attribution infos + MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc] + initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"] + URL:[NSURL URLWithString:@"https://www.mapbox.com/"]]; #if TARGET_OS_IPHONE - UIColor *redColor = [UIColor redColor]; + UIColor *redColor = [UIColor redColor]; #else - // CSS uses the sRGB color space. - // AppKit incorrectly uses calibrated RGB when exporting HTML, so input - // calibrated RGB to ensure round-tripping. - // - NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1]; + // CSS uses the sRGB color space. + // AppKit incorrectly uses calibrated RGB when exporting HTML, so input + // calibrated RGB to ensure round-tripping. + // + NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1]; #endif - NSAttributedString *gl = [[NSAttributedString alloc] initWithString:@"GL" attributes:@{ - NSBackgroundColorAttributeName: redColor, - }]; - MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil]; - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionAttributionInfos: @[mapboxInfo, glInfo], - }); + NSAttributedString *gl = + [[NSAttributedString alloc] initWithString:@"GL" + attributes:@{ + NSBackgroundColorAttributeName : redColor, + }]; + MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil]; + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionAttributionInfos : @[ mapboxInfo, glInfo ], + }); - // the attribution is reflected by the mbgl tileset + // the attribution is reflected by the mbgl tileset #if TARGET_OS_IPHONE - NSString *html; - if (@available(iOS 13.0, *)) { - // TODO: investigate visual impact - // iOS 13 evidently changes font size from points to pixels - html = (@"" - @"Mapbox " - @"GL\n"); - } else { - html = (@"" - @"Mapbox " - @"GL\n"); - } + NSString *html; + if (@available(iOS 13.0, *)) { + // TODO: investigate visual impact + // iOS 13 evidently changes font size from points to pixels + html = (@"" + @"Mapbox " + @"GL\n"); + } else { + html = (@"" + @"Mapbox " + @"GL\n"); + } #else - NSString *html = (@"" - @"Mapbox " - @"GL\n"); + NSString *html = (@"" + @"Mapbox " + @"GL\n"); #endif - XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html); + XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html); - // when the tile coordinate system is changed using an NSNumber - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionTileCoordinateSystem: @(MLNTileCoordinateSystemTMS), - }); + // when the tile coordinate system is changed using an NSNumber + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionTileCoordinateSystem : @(MLNTileCoordinateSystemTMS), + }); - // the scheme is reflected by the mbgl tileset - XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); + // the scheme is reflected by the mbgl tileset + XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); - // when the tile coordinate system is changed using an NSValue - MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS; - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionTileCoordinateSystem: [NSValue value:&tms withObjCType:@encode(MLNTileCoordinateSystem)], - }); + // when the tile coordinate system is changed using an NSValue + MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS; + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionTileCoordinateSystem : [NSValue value:&tms + withObjCType:@encode(MLNTileCoordinateSystem)], + }); - // the scheme is reflected by the mbgl tileset - XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); + // the scheme is reflected by the mbgl tileset + XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); } - (void)testTileSetFromTileURLTemplatesRasterEncodings { - mbgl::Tileset tileSet; + mbgl::Tileset tileSet; - // when the DEM encoding is changed using an NSNumber - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionDEMEncoding: @(MLNDEMEncodingTerrarium), - }); + // when the DEM encoding is changed using an NSNumber + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionDEMEncoding : @(MLNDEMEncodingTerrarium), + }); - // the encoding is reflected by the mbgl tileset - XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); - XCTAssertFalse(tileSet.vectorEncoding); + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); + XCTAssertFalse(tileSet.vectorEncoding); - // when the raster encoding is changed using an NSValue - MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium; - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MLNDEMEncoding)], - }); + // when the raster encoding is changed using an NSValue + MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium; + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNTileSourceOptionDEMEncoding : [NSValue value:&terrarium + withObjCType:@encode(MLNDEMEncoding)], + }); - // the encoding is reflected by the mbgl tileset - XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); - XCTAssertFalse(tileSet.vectorEncoding); + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); + XCTAssertFalse(tileSet.vectorEncoding); } - (void)testTileSetFromTileURLTemplatesVectorEncodings { - mbgl::Tileset tileSet; + mbgl::Tileset tileSet; - // when the raster encoding is changed using an NSNumber - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMapbox), - }); + // when the raster encoding is changed using an NSNumber + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMapbox), + }); - // the encoding is reflected by the mbgl tileset - XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox); - XCTAssertFalse(tileSet.rasterEncoding); + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox); + XCTAssertFalse(tileSet.rasterEncoding); - // when the raster encoding is changed using an NSNumber - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMLT), - }); + // when the raster encoding is changed using an NSNumber + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMLT), + }); - // the encoding is reflected by the mbgl tileset - XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); - XCTAssertFalse(tileSet.rasterEncoding); + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); + XCTAssertFalse(tileSet.rasterEncoding); - // when the raster encoding is changed using an NSValue - MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT; - tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ - MLNVectorTileSourceOptionEncoding: [NSValue value:&mlt withObjCType:@encode(MLNVectorTileSourceEncoding)], - }); + // when the raster encoding is changed using an NSValue + MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT; + tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ + MLNVectorTileSourceOptionEncoding : [NSValue value:&mlt + withObjCType:@encode(MLNVectorTileSourceEncoding)], + }); - // the encoding is reflected by the mbgl tileset - XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); - XCTAssertFalse(tileSet.rasterEncoding); + // the encoding is reflected by the mbgl tileset + XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); + XCTAssertFalse(tileSet.rasterEncoding); } - (void)testInvalidTileSet { - // a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels throws an exception - XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ - MLNTileSourceOptionMinimumZoomLevel: @10, - MLNTileSourceOptionMaximumZoomLevel: @9, - }), NSException, NSInvalidArgumentException); + // a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels + // throws an exception + XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{ + MLNTileSourceOptionMinimumZoomLevel : @10, + MLNTileSourceOptionMaximumZoomLevel : @9, + }), + NSException, NSInvalidArgumentException); } @end diff --git a/platform/glfw/glfw_apple_init.mm b/platform/glfw/glfw_apple_init.mm index 92bd639db..4a54169e3 100644 --- a/platform/glfw/glfw_apple_init.mm +++ b/platform/glfw/glfw_apple_init.mm @@ -2,10 +2,9 @@ // Called early in GLFW initialization so that the local glyph rasterizer // can find a suitable CJK font via NSUserDefaults. -__attribute__((constructor)) -static void setDefaultIdeographicFont() { - NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; - if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) { - [defaults setObject:@[@"PingFang TC"] forKey:@"MLNIdeographicFontFamilyName"]; - } +__attribute__((constructor)) static void setDefaultIdeographicFont() { + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) { + [defaults setObject:@[ @"PingFang TC" ] forKey:@"MLNIdeographicFontFamilyName"]; + } } diff --git a/platform/glfw/glfw_metal_backend.h b/platform/glfw/glfw_metal_backend.h index f1440bab1..3ae0cb3c4 100644 --- a/platform/glfw/glfw_metal_backend.h +++ b/platform/glfw/glfw_metal_backend.h @@ -10,14 +10,14 @@ struct GLFWwindow; @class NSWindow; class GLFWMetalBackend final : public GLFWBackend { - public: +public: GLFWMetalBackend(GLFWwindow *window_, const bool capFrameRate); ~GLFWMetalBackend() = default; mbgl::gfx::RendererBackend &getRendererBackend() override; void setSize(mbgl::Size) override; mbgl::Size getSize() const override; - private: +private: NSWindow *window; MetalBackend rendererBackend; }; diff --git a/platform/glfw/glfw_metal_backend.mm b/platform/glfw/glfw_metal_backend.mm index 6ef1d6970..f4edd2acb 100644 --- a/platform/glfw/glfw_metal_backend.mm +++ b/platform/glfw/glfw_metal_backend.mm @@ -7,20 +7,10 @@ #include GLFWMetalBackend::GLFWMetalBackend(GLFWwindow* window_, [[maybe_unused]] const bool capFrameRate) - : - window(glfwGetCocoaWindow(window_)), - rendererBackend(window) - { - } + : window(glfwGetCocoaWindow(window_)), rendererBackend(window) {} -mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() { - return rendererBackend; -} +mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() { return rendererBackend; } -void GLFWMetalBackend::setSize(mbgl::Size size) { - rendererBackend.setSize(size); -} +void GLFWMetalBackend::setSize(mbgl::Size size) { rendererBackend.setSize(size); } -mbgl::Size GLFWMetalBackend::getSize() const { - return rendererBackend.getSize(); -} +mbgl::Size GLFWMetalBackend::getSize() const { return rendererBackend.getSize(); } diff --git a/platform/glfw/metal_backend.mm b/platform/glfw/metal_backend.mm index 0cd69e509..f538f4c05 100644 --- a/platform/glfw/metal_backend.mm +++ b/platform/glfw/metal_backend.mm @@ -10,129 +10,130 @@ namespace mbgl { using namespace mtl; -class MetalRenderableResource final: public mtl::RenderableResource { +class MetalRenderableResource final : public mtl::RenderableResource { public: - MetalRenderableResource(MetalBackend &backend): - rendererBackend(backend), - commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())), - swapchain(NS::TransferPtr(CA::MetalLayer::layer())) - { - swapchain->setDevice(backend.getDevice().get()); + MetalRenderableResource(MetalBackend& backend) + : rendererBackend(backend), + commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())), + swapchain(NS::TransferPtr(CA::MetalLayer::layer())) { + swapchain->setDevice(backend.getDevice().get()); + } + + void setBackendSize(mbgl::Size size_) { + size = size_; + swapchain->setDrawableSize( + {static_cast(size.width), static_cast(size.height)}); + buffersInvalid = true; + } + + mbgl::Size getSize() const { return size; } + + void bind() override { + surface = NS::TransferPtr(swapchain->nextDrawable()); + auto texSize = mbgl::Size{static_cast(swapchain->drawableSize().width), + static_cast(swapchain->drawableSize().height)}; + + commandBuffer = NS::TransferPtr(commandQueue->commandBuffer()); + renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor()); + renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture()); + + if (buffersInvalid || !depthTexture || !stencilTexture) { + buffersInvalid = false; + depthTexture = rendererBackend.getContext().createTexture2D(); + depthTexture->setSize(texSize); + depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float); + depthTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear, + gfx::TextureWrapType::Clamp, + gfx::TextureWrapType::Clamp}); + static_cast(depthTexture.get()) + ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | + MTL::TextureUsageRenderTarget); + + stencilTexture = rendererBackend.getContext().createTexture2D(); + stencilTexture->setSize(texSize); + stencilTexture->setFormat(gfx::TexturePixelType::Stencil, + gfx::TextureChannelDataType::UnsignedByte); + stencilTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear, + gfx::TextureWrapType::Clamp, + gfx::TextureWrapType::Clamp}); + static_cast(stencilTexture.get()) + ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | + MTL::TextureUsageRenderTarget); } - void setBackendSize(mbgl::Size size_) { - size = size_; - swapchain->setDrawableSize({static_cast(size.width), static_cast(size.height)}); - buffersInvalid = true; + if (depthTexture) { + depthTexture->create(); + if (auto* depthTarget = renderPassDescriptor->depthAttachment()) { + depthTarget->setTexture( + static_cast(depthTexture.get())->getMetalTexture()); + } } - - mbgl::Size getSize() const { - return size; + if (stencilTexture) { + stencilTexture->create(); + if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) { + stencilTarget->setTexture( + static_cast(stencilTexture.get())->getMetalTexture()); + } } + } - void bind() override { - surface = NS::TransferPtr(swapchain->nextDrawable()); - auto texSize = mbgl::Size{ - static_cast(swapchain->drawableSize().width), - static_cast(swapchain->drawableSize().height)}; + void swap() override { + commandBuffer->presentDrawable(surface.get()); + commandBuffer->commit(); + commandBuffer.reset(); + renderPassDescriptor.reset(); + } - commandBuffer = NS::TransferPtr(commandQueue->commandBuffer()); - renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor()); - renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture()); + const mtl::RendererBackend& getBackend() const override { return rendererBackend; } - if (buffersInvalid || !depthTexture || !stencilTexture) { - buffersInvalid = false; - depthTexture = rendererBackend.getContext().createTexture2D(); - depthTexture->setSize(texSize); - depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float); - depthTexture->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - static_cast(depthTexture.get())->setUsage( - MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); + const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; } - stencilTexture = rendererBackend.getContext().createTexture2D(); - stencilTexture->setSize(texSize); - stencilTexture->setFormat(gfx::TexturePixelType::Stencil, gfx::TextureChannelDataType::UnsignedByte); - stencilTexture->setSamplerConfiguration( - {gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); - static_cast(stencilTexture.get()) - ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); - } + mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { + return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); + } - if (depthTexture) { - depthTexture->create(); - if (auto* depthTarget = renderPassDescriptor->depthAttachment()) { - depthTarget->setTexture(static_cast(depthTexture.get())->getMetalTexture()); - } - } - if (stencilTexture) { - stencilTexture->create(); - if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) { - stencilTarget->setTexture(static_cast(stencilTexture.get())->getMetalTexture()); - } - } - } + const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { + return renderPassDescriptor; + } - void swap() override { - commandBuffer->presentDrawable(surface.get()); - commandBuffer->commit(); - commandBuffer.reset(); - renderPassDescriptor.reset(); - } - - const mtl::RendererBackend& getBackend() const override { - return rendererBackend; - } - - const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { - return commandBuffer; - } - - mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { - return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); - } - - const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { - return renderPassDescriptor; - } - - const CAMetalLayerPtr& getSwapchain() const { - return swapchain; - } + const CAMetalLayerPtr& getSwapchain() const { return swapchain; } private: - MetalBackend &rendererBackend; - MTLCommandQueuePtr commandQueue; - MTLCommandBufferPtr commandBuffer; - MTLRenderPassDescriptorPtr renderPassDescriptor; - CAMetalDrawablePtr surface; - CAMetalLayerPtr swapchain; - gfx::Texture2DPtr depthTexture; - gfx::Texture2DPtr stencilTexture; - mbgl::Size size; - bool buffersInvalid = true; + MetalBackend& rendererBackend; + MTLCommandQueuePtr commandQueue; + MTLCommandBufferPtr commandBuffer; + MTLRenderPassDescriptorPtr renderPassDescriptor; + CAMetalDrawablePtr surface; + CAMetalLayerPtr swapchain; + gfx::Texture2DPtr depthTexture; + gfx::Texture2DPtr stencilTexture; + mbgl::Size size; + bool buffersInvalid = true; }; -} // namespace mbgl +} // namespace mbgl -MetalBackend::MetalBackend(NSWindow *window): -mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique), -mbgl::gfx::Renderable(mbgl::Size{0, 0}, std::make_unique(*this)) -{ - window.contentView.layer = (__bridge CALayer *)getDefaultRenderable().getResource().getSwapchain().get(); - window.contentView.wantsLayer = YES; +MetalBackend::MetalBackend(NSWindow* window) + : mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique), + mbgl::gfx::Renderable(mbgl::Size{0, 0}, + std::make_unique(*this)) { + window.contentView.layer = (__bridge CALayer*)getDefaultRenderable() + .getResource() + .getSwapchain() + .get(); + window.contentView.wantsLayer = YES; } -mbgl::gfx::Renderable &MetalBackend::getDefaultRenderable() { - return *this; -} +mbgl::gfx::Renderable& MetalBackend::getDefaultRenderable() { return *this; } void MetalBackend::activate() {} void MetalBackend::deactivate() {} void MetalBackend::updateAssumedState() {} void MetalBackend::setSize(mbgl::Size size_) { - getResource().setBackendSize(size_); + getResource().setBackendSize(size_); } -mbgl::Size MetalBackend::getSize() const { return getResource().getSize(); } +mbgl::Size MetalBackend::getSize() const { + return getResource().getSize(); +} diff --git a/platform/ios/Integration_Tests/Annotation Tests/MLNAnnotationViewIntegrationTests.mm b/platform/ios/Integration_Tests/Annotation Tests/MLNAnnotationViewIntegrationTests.mm index ea6f7b9f8..56e1dc2f1 100644 --- a/platform/ios/Integration_Tests/Annotation Tests/MLNAnnotationViewIntegrationTests.mm +++ b/platform/ios/Integration_Tests/Annotation Tests/MLNAnnotationViewIntegrationTests.mm @@ -1,15 +1,15 @@ -#import "MLNMapViewIntegrationTest.h" -#import "MLNTestUtility.h" -#import "MLNMapAccessibilityElement.h" -#import "MLNTestLocationManager.h" #import "MLNCompactCalloutView.h" +#import "MLNMapAccessibilityElement.h" +#import "MLNMapViewIntegrationTest.h" +#import "MLNTestLocationManager.h" +#import "MLNTestUtility.h" #import "MLNGeometry_Private.h" #import "MLNMapView_Private.h" -#include #include #include +#include @interface MLNTestCalloutView : MLNCompactCalloutView @property (nonatomic) BOOL implementsMarginHints; @@ -17,19 +17,19 @@ @implementation MLNTestCalloutView - (BOOL)respondsToSelector:(SEL)aSelector { - if (!self.implementsMarginHints && - (aSelector == @selector(marginInsetsHintForPresentationFromRect:))) { - return NO; - } - return [super respondsToSelector:aSelector]; + if (!self.implementsMarginHints && + (aSelector == @selector(marginInsetsHintForPresentationFromRect:))) { + return NO; + } + return [super respondsToSelector:aSelector]; } @end - @interface MLNMapView (Tests) - (MLNAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist; -- (id )annotationWithTag:(MLNAnnotationTag)tag; -- (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint; +- (id)annotationWithTag:(MLNAnnotationTag)tag; +- (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees + aroundAnchorPoint:(CGPoint)anchorPoint; - (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint; - (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions; @property (nonatomic) UIView *calloutViewForSelectedAnnotation; @@ -43,784 +43,812 @@ // MARK: - Offscreen/panning selection tests typedef struct PanTestData { - CGPoint relativeCoord; - BOOL showsCallout; - BOOL implementsMargins; - BOOL moveIntoView; - BOOL expectMapToHavePanned; - BOOL calloutOnScreen; + CGPoint relativeCoord; + BOOL showsCallout; + BOOL implementsMargins; + BOOL moveIntoView; + BOOL expectMapToHavePanned; + BOOL calloutOnScreen; } PanTestData; #define PAN_TEST_TERMINATOR {{FLT_MAX, FLT_MAX}, NO, NO, NO, NO, NO} -static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f }; +static const CGPoint kAnnotationRelativeScale = {0.05f, 0.125f}; -- (void)internalTestOffscreenSelectionTitle:(NSString*)title withTestData:(PanTestData)test animateSelection:(BOOL)animateSelection { +- (void)internalTestOffscreenSelectionTitle:(NSString *)title + withTestData:(PanTestData)test + animateSelection:(BOOL)animateSelection { + CGPoint relativeCoordinate = test.relativeCoord; + BOOL showsCallout = test.showsCallout; + BOOL calloutImplementsMarginHints = test.implementsMargins; + BOOL moveIntoView = test.moveIntoView; + BOOL expectMapToHavePanned = test.expectMapToHavePanned; + BOOL expectCalloutToBeFullyOnscreen = test.calloutOnScreen; - CGPoint relativeCoordinate = test.relativeCoord; - BOOL showsCallout = test.showsCallout; - BOOL calloutImplementsMarginHints = test.implementsMargins; - BOOL moveIntoView = test.moveIntoView; - BOOL expectMapToHavePanned = test.expectMapToHavePanned; - BOOL expectCalloutToBeFullyOnscreen = test.calloutOnScreen; + // Reset the map to a consistent state - want the map to be zoomed in, so that + // it's free to be panned without hitting boundaries. + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0, 0) zoomLevel:14 animated:NO]; + [self waitForMapViewToBeRenderedWithTimeout:1.0]; - // Reset the map to a consistent state - want the map to be zoomed in, so that - // it's free to be panned without hitting boundaries. - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0, 0) zoomLevel:14 animated:NO]; - [self waitForMapViewToBeRenderedWithTimeout:1.0]; + XCTAssert(self.mapView.annotations.count == 0); - XCTAssert(self.mapView.annotations.count == 0); + NSString *const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; + CGSize size = self.mapView.bounds.size; + CGSize annotationSize = CGSizeMake(floor(size.width * kAnnotationRelativeScale.x), + floor(size.height * kAnnotationRelativeScale.y)); - NSString * const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; - CGSize size = self.mapView.bounds.size; - CGSize annotationSize = CGSizeMake(floor(size.width*kAnnotationRelativeScale.x), floor(size.height*kAnnotationRelativeScale.y)); + self.viewForAnnotation = ^MLNAnnotationView *(MLNMapView *view, id annotation) { + if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { + return nil; + } - self.viewForAnnotation = ^MLNAnnotationView*(MLNMapView *view, id annotation) { + // No dequeue + MLNAnnotationView *annotationView = + [[MLNAnnotationView alloc] initWithAnnotation:annotation + reuseIdentifier:MLNTestAnnotationReuseIdentifer]; + annotationView.bounds = (CGRect){.origin = CGPointZero, .size = annotationSize}; + annotationView.backgroundColor = UIColor.redColor; + annotationView.enabled = YES; - if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { - return nil; - } + return annotationView; + }; - // No dequeue - MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MLNTestAnnotationReuseIdentifer]; - annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; - annotationView.backgroundColor = UIColor.redColor; - annotationView.enabled = YES; + // Coordinate for annotation screen coordinate + CGPoint annotationPoint = CGPointMake(floor(relativeCoordinate.x * size.width), + floor(relativeCoordinate.y * size.height)); + CLLocationCoordinate2D coordinate = [self.mapView convertPoint:annotationPoint + toCoordinateFromView:self.mapView]; - return annotationView; - }; + MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; + point.title = title; + point.coordinate = coordinate; - // Coordinate for annotation screen coordinate - CGPoint annotationPoint = CGPointMake(floor(relativeCoordinate.x * size.width), floor(relativeCoordinate.y * size.height) ); - CLLocationCoordinate2D coordinate = [self.mapView convertPoint:annotationPoint toCoordinateFromView:self.mapView]; - - MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; - point.title = title; - point.coordinate = coordinate; - - self.mapViewAnnotationCanShowCalloutForAnnotation = ^BOOL(MLNMapView *mapView, id annotation) { + self.mapViewAnnotationCanShowCalloutForAnnotation = + ^BOOL(MLNMapView *mapView, id annotation) { return showsCallout; - }; + }; - self.mapViewCalloutViewForAnnotation = ^id(MLNMapView *mapView, id annotation) { - if (!showsCallout) - return nil; + self.mapViewCalloutViewForAnnotation = + ^id(MLNMapView *mapView, id annotation) { + if (!showsCallout) return nil; - MLNTestCalloutView *calloutView = [[MLNTestCalloutView alloc] init]; - calloutView.representedObject = annotation; - calloutView.implementsMarginHints = calloutImplementsMarginHints; - return calloutView; - }; + MLNTestCalloutView *calloutView = [[MLNTestCalloutView alloc] init]; + calloutView.representedObject = annotation; + calloutView.implementsMarginHints = calloutImplementsMarginHints; + return calloutView; + }; - [self.mapView addAnnotation:point]; + [self.mapView addAnnotation:point]; - // Check assumptions before selection - UIView *annotationViewBeforeSelection = [self.mapView viewForAnnotation:point]; - XCTAssertNotNil(annotationViewBeforeSelection); + // Check assumptions before selection + UIView *annotationViewBeforeSelection = [self.mapView viewForAnnotation:point]; + XCTAssertNotNil(annotationViewBeforeSelection); - CLLocationCoordinate2D mapCenterBeforeSelection = self.mapView.centerCoordinate; - XCTAssert(CLLocationCoordinate2DIsValid(mapCenterBeforeSelection)); + CLLocationCoordinate2D mapCenterBeforeSelection = self.mapView.centerCoordinate; + XCTAssert(CLLocationCoordinate2DIsValid(mapCenterBeforeSelection)); - // Also note, that at this point, the internal mechanism that determines if - // an annotation view is offscreen and should be put back in the reuse queue - // will have run, and `viewForAnnotation` may return nil + // Also note, that at this point, the internal mechanism that determines if + // an annotation view is offscreen and should be put back in the reuse queue + // will have run, and `viewForAnnotation` may return nil - XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; - [self.mapView selectAnnotation:point moveIntoView:moveIntoView animateSelection:animateSelection completionHandler:^{ - [selectionCompleted fulfill]; - }]; + XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; + [self.mapView selectAnnotation:point + moveIntoView:moveIntoView + animateSelection:animateSelection + completionHandler:^{ + [selectionCompleted fulfill]; + }]; - // Animated selection takes MLNAnimationDuration (0.3 seconds), so wait a little - // longer. We don't need to wait as long if we're not animated (but we do - // want the runloop to tick over) + // Animated selection takes MLNAnimationDuration (0.3 seconds), so wait a little + // longer. We don't need to wait as long if we're not animated (but we do + // want the runloop to tick over) #if TARGET_OS_SIMULATOR - // Something is amiss with dispatch_async & XCTest on simulators in a recent - // iOS SDK release. - // TODO: Add issue - [self waitForExpectations:@[selectionCompleted] timeout:animateSelection ? 0.4: 0.1]; + // Something is amiss with dispatch_async & XCTest on simulators in a recent + // iOS SDK release. + // TODO: Add issue + [self waitForExpectations:@[ selectionCompleted ] timeout:animateSelection ? 0.4 : 0.1]; #else - [self waitForExpectations:@[selectionCompleted] timeout:animateSelection ? 0.4: 0.05]; + [self waitForExpectations:@[ selectionCompleted ] timeout:animateSelection ? 0.4 : 0.05]; #endif - UIView *annotationViewAfterSelection = [self.mapView viewForAnnotation:point]; - CLLocationCoordinate2D mapCenterAfterSelection = self.mapView.centerCoordinate; - XCTAssert(CLLocationCoordinate2DIsValid(mapCenterAfterSelection)); + UIView *annotationViewAfterSelection = [self.mapView viewForAnnotation:point]; + CLLocationCoordinate2D mapCenterAfterSelection = self.mapView.centerCoordinate; + XCTAssert(CLLocationCoordinate2DIsValid(mapCenterAfterSelection)); - // If the annotation is still "offscreen" at this point, then the above annotation view - // may be nil, which is expected. - BOOL (^CGRectContainsRectWithAccuracy)(CGRect, CGRect, CGFloat) = ^(CGRect rect1, CGRect rect2, CGFloat accuracy) { + // If the annotation is still "offscreen" at this point, then the above annotation view + // may be nil, which is expected. + BOOL (^CGRectContainsRectWithAccuracy)(CGRect, CGRect, CGFloat) = + ^(CGRect rect1, CGRect rect2, CGFloat accuracy) { CGRect expandedRect1 = CGRectInset(rect1, -accuracy, -accuracy); return (BOOL)CGRectContainsRect(expandedRect1, rect2); - }; + }; - CGFloat epsilon = 0.00001; - if (expectMapToHavePanned) { - CLLocationDegrees latitudeDelta = fabs(mapCenterAfterSelection.latitude - mapCenterBeforeSelection.latitude); - CLLocationDegrees longitudeDelta = fabs(mapCenterAfterSelection.longitude - mapCenterBeforeSelection.longitude); + CGFloat epsilon = 0.00001; + if (expectMapToHavePanned) { + CLLocationDegrees latitudeDelta = + fabs(mapCenterAfterSelection.latitude - mapCenterBeforeSelection.latitude); + CLLocationDegrees longitudeDelta = + fabs(mapCenterAfterSelection.longitude - mapCenterBeforeSelection.longitude); - XCTAssert( (latitudeDelta > epsilon) || (longitudeDelta > epsilon), @"Deltas: lat=%f, long=%f", latitudeDelta, longitudeDelta); // One of them should have moved + XCTAssert((latitudeDelta > epsilon) || (longitudeDelta > epsilon), @"Deltas: lat=%f, long=%f", + latitudeDelta, longitudeDelta); // One of them should have moved - // If the map panned - the intention is that the annotation is on-screen, - // and so should have an annotation view and that it is fully on screen - CGRect annotationFrameAfterSelection = annotationViewAfterSelection.frame; + // If the map panned - the intention is that the annotation is on-screen, + // and so should have an annotation view and that it is fully on screen + CGRect annotationFrameAfterSelection = annotationViewAfterSelection.frame; - XCTAssertNotNil(annotationViewAfterSelection); + XCTAssertNotNil(annotationViewAfterSelection); - XCTAssert(CGRectContainsRectWithAccuracy(self.mapView.bounds, annotationFrameAfterSelection, 0.25), @"Mapview:%@ frame:%@", NSStringFromCGRect(self.mapView.bounds), NSStringFromCGRect(annotationFrameAfterSelection)); + XCTAssert( + CGRectContainsRectWithAccuracy(self.mapView.bounds, annotationFrameAfterSelection, 0.25), + @"Mapview:%@ frame:%@", NSStringFromCGRect(self.mapView.bounds), + NSStringFromCGRect(annotationFrameAfterSelection)); - // Check the callout - if (showsCallout) { - UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation; - XCTAssertNotNil(calloutView); + // Check the callout + if (showsCallout) { + UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation; + XCTAssertNotNil(calloutView); - // This can fail if the callout view's width is < the annotations. This is really a warning, so - // if you need this NOT to fail the tests, consider replacing with MLNTestWarning - XCTAssert(expectCalloutToBeFullyOnscreen == CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25), - @"Expect contains:%d, Mapview:%@ annotation:%@ callout:%@", - expectCalloutToBeFullyOnscreen, - NSStringFromCGRect(self.mapView.bounds), - NSStringFromCGRect(annotationFrameAfterSelection), - NSStringFromCGRect(calloutView.frame)); - } + // This can fail if the callout view's width is < the annotations. This is really a warning, + // so if you need this NOT to fail the tests, consider replacing with MLNTestWarning + XCTAssert(expectCalloutToBeFullyOnscreen == + CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25), + @"Expect contains:%d, Mapview:%@ annotation:%@ callout:%@", + expectCalloutToBeFullyOnscreen, NSStringFromCGRect(self.mapView.bounds), + NSStringFromCGRect(annotationFrameAfterSelection), + NSStringFromCGRect(calloutView.frame)); } - else { - // The map shouldn't have moved, so use equality (rather than an error check) - XCTAssertEqual(mapCenterBeforeSelection.latitude, mapCenterAfterSelection.latitude); - XCTAssertEqual(mapCenterBeforeSelection.longitude, mapCenterAfterSelection.longitude); + } else { + // The map shouldn't have moved, so use equality (rather than an error check) + XCTAssertEqual(mapCenterBeforeSelection.latitude, mapCenterAfterSelection.latitude); + XCTAssertEqual(mapCenterBeforeSelection.longitude, mapCenterAfterSelection.longitude); - // Annotation shouldn't have moved - CGPoint annotationPoint2 = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; - CGFloat xDelta = fabs(annotationPoint2.x - annotationPoint.x); - CGFloat yDelta = fabs(annotationPoint2.y - annotationPoint.y); + // Annotation shouldn't have moved + CGPoint annotationPoint2 = [self.mapView convertCoordinate:point.coordinate + toPointToView:self.mapView]; + CGFloat xDelta = fabs(annotationPoint2.x - annotationPoint.x); + CGFloat yDelta = fabs(annotationPoint2.y - annotationPoint.y); - XCTAssert((xDelta < epsilon) && (yDelta < epsilon)); + XCTAssert((xDelta < epsilon) && (yDelta < epsilon)); - if (showsCallout) { - UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation; + if (showsCallout) { + UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation; - if (annotationViewAfterSelection) { - XCTAssertNotNil(calloutView); + if (annotationViewAfterSelection) { + XCTAssertNotNil(calloutView); - // If kAnnotationScale == 0.25, then the following assert can fail. - // This is really a warning (see https://github.com/mapbox/mapbox-gl-native/issues/13744 ) - // If you need this NOT to fail the tests, consider replacing with MLNTestWarning - XCTAssert((expectCalloutToBeFullyOnscreen == CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25)), - @"Mapview:%@ annotation:%@ callout:%@", - NSStringFromCGRect(self.mapView.bounds), - NSStringFromCGRect(annotationViewAfterSelection.frame), - NSStringFromCGRect(calloutView.frame)); - } - else { - // If there's no annotation view, should we expect a callout? - XCTAssertNil(calloutView); - XCTAssertFalse(expectCalloutToBeFullyOnscreen); - } - } + // If kAnnotationScale == 0.25, then the following assert can fail. + // This is really a warning (see https://github.com/mapbox/mapbox-gl-native/issues/13744 ) + // If you need this NOT to fail the tests, consider replacing with MLNTestWarning + XCTAssert((expectCalloutToBeFullyOnscreen == + CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25)), + @"Mapview:%@ annotation:%@ callout:%@", NSStringFromCGRect(self.mapView.bounds), + NSStringFromCGRect(annotationViewAfterSelection.frame), + NSStringFromCGRect(calloutView.frame)); + } else { + // If there's no annotation view, should we expect a callout? + XCTAssertNil(calloutView); + XCTAssertFalse(expectCalloutToBeFullyOnscreen); + } } + } - // Remove the annotation - [self.mapView removeAnnotation:point]; + // Remove the annotation + [self.mapView removeAnnotation:point]; - XCTAssert(self.mapView.annotations.count == 0); + XCTAssert(self.mapView.annotations.count == 0); } // See https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 // What follows are tests based on this table. // This is not a full-set of possible combinations, just the most important/likely // ones -- (void)internalRunTests:(PanTestData*)testData -{ - // Test both animated and not-animated. - for (int i = 0; i<2; i++) { - int row = 0; - PanTestData *test = testData; - while (test->relativeCoord.x != FLT_MAX) { - NSString *activityTitle = [NSString stringWithFormat:@"Row %d/%d", row, i]; - [XCTContext runActivityNamed:activityTitle - block:^(id _Nonnull activity) { - [self internalTestOffscreenSelectionTitle:activityTitle - withTestData:*test - animateSelection:!i]; - }]; - ++test; - ++row; - } +- (void)internalRunTests:(PanTestData *)testData { + // Test both animated and not-animated. + for (int i = 0; i < 2; i++) { + int row = 0; + PanTestData *test = testData; + while (test->relativeCoord.x != FLT_MAX) { + NSString *activityTitle = [NSString stringWithFormat:@"Row %d/%d", row, i]; + [XCTContext runActivityNamed:activityTitle + block:^(id _Nonnull activity) { + [self internalTestOffscreenSelectionTitle:activityTitle + withTestData:*test + animateSelection:!i]; + }]; + ++test; + ++row; } + } } -- (void)testBasicSelection { - // Tests moveIntoView:NO - // WITHOUT a callout +- (void)testBasicSelection { + // Tests moveIntoView:NO + // WITHOUT a callout - PanTestData tests[] = { - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - // Offscreen - { {-1.0f, 0.5f}, NO, NO, NO, NO, NO }, - { { 2.0f, 0.5f}, NO, NO, NO, NO, NO }, - { { 0.5f,-1.0f}, NO, NO, NO, NO, NO }, - { { 0.5f, 2.0f}, NO, NO, NO, NO, NO }, + PanTestData tests[] = { + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + // Offscreen + {{-1.0f, 0.5f}, NO, NO, NO, NO, NO}, + {{2.0f, 0.5f}, NO, NO, NO, NO, NO}, + {{0.5f, -1.0f}, NO, NO, NO, NO, NO}, + {{0.5f, 2.0f}, NO, NO, NO, NO, NO}, - // Partial - { { 0.0f, 0.5f}, NO, NO, NO, NO, NO }, - { { 1.0f, 0.5f}, NO, NO, NO, NO, NO }, - { { 0.5f, 0.0f}, NO, NO, NO, NO, NO }, - { { 0.5f, 1.0f}, NO, NO, NO, NO, NO }, + // Partial + {{0.0f, 0.5f}, NO, NO, NO, NO, NO}, + {{1.0f, 0.5f}, NO, NO, NO, NO, NO}, + {{0.5f, 0.0f}, NO, NO, NO, NO, NO}, + {{0.5f, 1.0f}, NO, NO, NO, NO, NO}, - // Onscreen - { { 0.5f, 0.5f}, NO, NO, NO, NO, NO }, + // Onscreen + {{0.5f, 0.5f}, NO, NO, NO, NO, NO}, - PAN_TEST_TERMINATOR - }; + PAN_TEST_TERMINATOR}; - [self internalRunTests:tests]; + [self internalRunTests:tests]; } -- (void)testBasicSelectionWithCallout { - // Tests moveIntoView:NO - // WITH the default callout (implements marginshint) +- (void)testBasicSelectionWithCallout { + // Tests moveIntoView:NO + // WITH the default callout (implements marginshint) - PanTestData tests[] = { - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - { {-1.0f, 0.5f}, YES, YES, NO, NO, NO }, - { { 0.0f, 0.5f}, YES, YES, NO, NO, NO }, - { { 0.5f, 1.0f}, YES, YES, NO, NO, YES }, // Because annotation was off the bottom of screen, and callout is above annotation - { { 0.5f, 0.5f}, YES, YES, NO, NO, YES }, + PanTestData tests[] = { + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + {{-1.0f, 0.5f}, YES, YES, NO, NO, NO}, + {{0.0f, 0.5f}, YES, YES, NO, NO, NO}, + {{0.5f, 1.0f}, YES, YES, NO, NO, YES}, // Because annotation was off the bottom of screen, + // and callout is above annotation + {{0.5f, 0.5f}, YES, YES, NO, NO, YES}, - PAN_TEST_TERMINATOR - }; + PAN_TEST_TERMINATOR}; - [self internalRunTests:tests]; + [self internalRunTests:tests]; } -- (void)testSelectionMoveIntoView { - // Tests moveIntoView:YES - // without a callout +- (void)testSelectionMoveIntoView { + // Tests moveIntoView:YES + // without a callout - // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 - // - // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when selected with moveIntoView=YES? | - // |---------------------|--------------|---------------------------------------|-----------------------------------------------| - // | Offscreen | No | n/a | Yes (no margins) | - // | Partially | No | n/a | No | - // | Onscreen | No | n/a | No | - // + // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 + // + // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when + // selected with moveIntoView=YES? | + // |---------------------|--------------|---------------------------------------|-----------------------------------------------| + // | Offscreen | No | n/a | Yes (no margins) + // | | Partially | No | n/a | No | | + // Onscreen | No | n/a | No | + // - PanTestData tests[] = { - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - // Offscreen - { {-1.0f, 0.5f}, NO, NO, YES, YES, NO }, - { { 2.0f, 0.5f}, NO, NO, YES, YES, NO }, - { { 0.5f,-1.0f}, NO, NO, YES, YES, NO }, - { { 0.5f, 2.0f}, NO, NO, YES, YES, NO }, + PanTestData tests[] = { + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + // Offscreen + {{-1.0f, 0.5f}, NO, NO, YES, YES, NO}, + {{2.0f, 0.5f}, NO, NO, YES, YES, NO}, + {{0.5f, -1.0f}, NO, NO, YES, YES, NO}, + {{0.5f, 2.0f}, NO, NO, YES, YES, NO}, - // Partial - { { 0.0f, 0.5f}, NO, NO, YES, NO, NO }, - { { 1.0f, 0.5f}, NO, NO, YES, NO, NO }, - { { 0.5f, 0.0f}, NO, NO, YES, NO, NO }, - { { 0.5f, 1.0f}, NO, NO, YES, NO, NO }, + // Partial + {{0.0f, 0.5f}, NO, NO, YES, NO, NO}, + {{1.0f, 0.5f}, NO, NO, YES, NO, NO}, + {{0.5f, 0.0f}, NO, NO, YES, NO, NO}, + {{0.5f, 1.0f}, NO, NO, YES, NO, NO}, - // Onscreen - { { 0.5f, 0.5f}, NO, NO, YES, NO, NO }, + // Onscreen + {{0.5f, 0.5f}, NO, NO, YES, NO, NO}, - PAN_TEST_TERMINATOR - }; + PAN_TEST_TERMINATOR}; - [self internalRunTests:tests]; + [self internalRunTests:tests]; } -- (void)testSelectionMoveIntoViewWithCallout { - // Tests moveIntoView:YES - // WITH the default callout (implements marginshint) +- (void)testSelectionMoveIntoViewWithCallout { + // Tests moveIntoView:YES + // WITH the default callout (implements marginshint) - // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 - // - // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when selected with moveIntoView=YES? | - // |---------------------|--------------|---------------------------------------|-----------------------------------------------| - // | Offscreen | Yes | Yes | Yes to ensure callout is fully visible | - // | Partially | Yes | Yes | Yes to ensure callout is fully visible | - // | Onscreen | Yes | Yes | Yes, but *only* to ensure callout is fully visible | - // + // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 + // + // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when + // selected with moveIntoView=YES? | + // |---------------------|--------------|---------------------------------------|-----------------------------------------------| + // | Offscreen | Yes | Yes | Yes to ensure + // callout is fully visible | | Partially | Yes | Yes | Yes to ensure + // callout is fully visible | | Onscreen | Yes | Yes | Yes, but *only* + // to ensure callout is fully visible | + // - CGFloat offset = kAnnotationRelativeScale.x * 0.5f; + CGFloat offset = kAnnotationRelativeScale.x * 0.5f; - PanTestData tests[] = { - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - // Offscreen - { {-1.0f, 0.5f}, YES, YES, YES, YES, YES }, - { { 2.0f, 0.5f}, YES, YES, YES, YES, YES }, - { { 0.5f,-1.0f}, YES, YES, YES, YES, YES }, - { { 0.5f, 2.0f}, YES, YES, YES, YES, YES }, + PanTestData tests[] = { + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + // Offscreen + {{-1.0f, 0.5f}, YES, YES, YES, YES, YES}, + {{2.0f, 0.5f}, YES, YES, YES, YES, YES}, + {{0.5f, -1.0f}, YES, YES, YES, YES, YES}, + {{0.5f, 2.0f}, YES, YES, YES, YES, YES}, - // Partial - { { 0.0f, 0.5f}, YES, YES, YES, YES, YES }, - { { 1.0f, 0.5f}, YES, YES, YES, YES, YES }, - { { 0.5f, 0.0f}, YES, YES, YES, YES, YES }, - { { 0.5f, 1.0f}, YES, YES, YES, YES, YES }, + // Partial + {{0.0f, 0.5f}, YES, YES, YES, YES, YES}, + {{1.0f, 0.5f}, YES, YES, YES, YES, YES}, + {{0.5f, 0.0f}, YES, YES, YES, YES, YES}, + {{0.5f, 1.0f}, YES, YES, YES, YES, YES}, - // Onscreen - { { 0.5f, 0.5f}, YES, YES, YES, NO, YES }, + // Onscreen + {{0.5f, 0.5f}, YES, YES, YES, NO, YES}, - // Just at the edge of the screen. - // Expects to move, because although onscreen, callout would not be. - // However, if the scale is 0.25, then expectToPan should be NO, because - // of the width of the annotation + // Just at the edge of the screen. + // Expects to move, because although onscreen, callout would not be. + // However, if the scale is 0.25, then expectToPan should be NO, because + // of the width of the annotation - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - { {offset, 0.5f}, YES, YES, YES, YES, YES }, - { {(CGFloat)(1.0 - offset), 0.5f}, YES, YES, YES, YES, YES }, + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + {{offset, 0.5f}, YES, YES, YES, YES, YES}, + {{(CGFloat)(1.0 - offset), 0.5f}, YES, YES, YES, YES, YES}, - PAN_TEST_TERMINATOR - }; + PAN_TEST_TERMINATOR}; - [self internalRunTests:tests]; + [self internalRunTests:tests]; } -- (void)testSelectionMoveIntoViewWithBasicCallout { - // Tests moveIntoView:YES - // WITH a callout that DOES NOT implement marginshint +- (void)testSelectionMoveIntoViewWithBasicCallout { + // Tests moveIntoView:YES + // WITH a callout that DOES NOT implement marginshint - // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 - // - // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when selected with moveIntoView=YES? | - // |---------------------|--------------|---------------------------------------|-----------------------------------------------| - // | Offscreen | Yes | No | Yes, but only to show annotation (not callout) with no margins | - // | Partially | Yes | No | No | - // | Onscreen | Yes | No | No | - // + // From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 + // + // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when + // selected with moveIntoView=YES? | + // |---------------------|--------------|---------------------------------------|-----------------------------------------------| + // | Offscreen | Yes | No | Yes, but only to + // show annotation (not callout) with no margins | | Partially | Yes | No | No + // | | Onscreen | Yes | No | No | + // - PanTestData tests[] = { - // Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen - // Offscreen - { {-1.0f, 0.5f}, YES, NO, YES, YES, NO }, - { { 2.0f, 0.5f}, YES, NO, YES, YES, NO }, - { { 0.5f,-1.0f}, YES, NO, YES, YES, NO }, - { { 0.5f, 2.0f}, YES, NO, YES, YES, YES }, // Because annotation was off the bottom of screen, and callout is above annotation - { { 2.0f, 2.0f}, YES, NO, YES, YES, NO }, + PanTestData tests[] = { + // Coord showsCallout impl margins? moveIntoView expectMapToPan + // calloutOnScreen + // Offscreen + {{-1.0f, 0.5f}, YES, NO, YES, YES, NO}, + {{2.0f, 0.5f}, YES, NO, YES, YES, NO}, + {{0.5f, -1.0f}, YES, NO, YES, YES, NO}, + {{0.5f, 2.0f}, YES, NO, YES, YES, YES}, // Because annotation was off the bottom of screen, + // and callout is above annotation + {{2.0f, 2.0f}, YES, NO, YES, YES, NO}, - // Partial - { { 0.0f, 0.5f}, YES, NO, YES, NO, NO }, - { { 1.0f, 0.5f}, YES, NO, YES, NO, NO }, - { { 0.5f, 0.0f}, YES, NO, YES, NO, NO }, - { { 0.5f, 1.0f}, YES, NO, YES, NO, YES }, // Because annotation was off the bottom of screen, and callout is above annotation - { { 1.0f, 1.0f}, YES, NO, YES, NO, NO }, + // Partial + {{0.0f, 0.5f}, YES, NO, YES, NO, NO}, + {{1.0f, 0.5f}, YES, NO, YES, NO, NO}, + {{0.5f, 0.0f}, YES, NO, YES, NO, NO}, + {{0.5f, 1.0f}, YES, NO, YES, NO, YES}, // Because annotation was off the bottom of screen, + // and callout is above annotation + {{1.0f, 1.0f}, YES, NO, YES, NO, NO}, - // Onscreen - { { 0.5f, 0.5f}, YES, NO, YES, NO, YES }, + // Onscreen + {{0.5f, 0.5f}, YES, NO, YES, NO, YES}, - PAN_TEST_TERMINATOR - }; + PAN_TEST_TERMINATOR}; - [self internalRunTests:tests]; + [self internalRunTests:tests]; } // MARK: - Selection with an offset - (void)testSelectingAnnotationWithCenterOffset { - - for (CGFloat dx = -100.0; dx <= 100.0; dx += 100.0 ) { - for (CGFloat dy = -100.0; dy <= 100.0; dy += 100.0 ) { - CGVector offset = CGVectorMake(dx, dy); - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(fmax(-dy, 0.0), fmax(-dy, 0.0), fmax(dy, 0.0), fmax(dx, 0.0)); - [self internalTestSelectingAnnotationWithCenterOffsetWithOffset:offset edgeInsets:edgeInsets]; - } + for (CGFloat dx = -100.0; dx <= 100.0; dx += 100.0) { + for (CGFloat dy = -100.0; dy <= 100.0; dy += 100.0) { + CGVector offset = CGVectorMake(dx, dy); + UIEdgeInsets edgeInsets = + UIEdgeInsetsMake(fmax(-dy, 0.0), fmax(-dy, 0.0), fmax(dy, 0.0), fmax(dx, 0.0)); + [self internalTestSelectingAnnotationWithCenterOffsetWithOffset:offset edgeInsets:edgeInsets]; } + } } -- (void)internalTestSelectingAnnotationWithCenterOffsetWithOffset:(CGVector)offset edgeInsets:(UIEdgeInsets)edgeInsets { +- (void)internalTestSelectingAnnotationWithCenterOffsetWithOffset:(CGVector)offset + edgeInsets:(UIEdgeInsets)edgeInsets { + NSString *const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; - NSString * const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; + self.mapView.contentInset = edgeInsets; + CGSize size = self.mapView.bounds.size; - self.mapView.contentInset = edgeInsets; - CGSize size = self.mapView.bounds.size; + CGSize annotationSize = CGSizeMake(40.0, 40.0); - CGSize annotationSize = CGSizeMake(40.0, 40.0); - - self.viewForAnnotation = ^MLNAnnotationView*(MLNMapView *view, id annotation) { - - if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { - return nil; - } - - // No dequeue - MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MLNTestAnnotationReuseIdentifer]; - annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; - annotationView.backgroundColor = UIColor.redColor; - annotationView.enabled = YES; - annotationView.centerOffset = offset; - - return annotationView; - }; - - MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; - point.title = NSStringFromSelector(_cmd); - point.coordinate = CLLocationCoordinate2DMake(0.0, 0.0); - [self.mapView addAnnotation:point]; - - // From https://github.com/mapbox/mapbox-gl-native/issues/12259#issuecomment-401414168 - // - // queryRenderedFeatures depends on collision detection having been run - // before it shows results [...]. Collision detection runs asynchronously - // (at least every 300ms, sometimes more often), and therefore the results - // of queryRenderedFeatures are similarly asynchronous. - // - // So, we need to wait before `annotationTagAtPoint:persistingResults:` will - // return out newly added annotation - - [self waitForCollisionDetectionToRun]; - - // Check that the annotation is in the center of the view - CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; - XCTAssertEqualWithAccuracy(annotationPoint.x, (size.width - edgeInsets.right + edgeInsets.left)/2.0, 0.25); - XCTAssertEqualWithAccuracy(annotationPoint.y, (size.height - edgeInsets.bottom + edgeInsets.top)/2.0, 0.25); - - // Now test taps around the annotation - CGPoint tapPoint = CGPointMake(annotationPoint.x + offset.dx, annotationPoint.y + offset.dy); - - MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:tapPoint persistingResults:YES]; - XCTAssert(tagAtPoint != UINT32_MAX, @"Should have tapped on annotation"); - - CGPoint testPoints[] = { - { tapPoint.x - annotationSize.width, tapPoint.y }, - { tapPoint.x + annotationSize.width, tapPoint.y }, - { tapPoint.x, tapPoint.y - annotationSize.height }, - { tapPoint.x, tapPoint.y + annotationSize.height }, - CGPointZero - }; - - CGPoint *testPoint = testPoints; - - while (!CGPointEqualToPoint(*testPoint, CGPointZero)) { - tagAtPoint = [self.mapView annotationTagAtPoint:*testPoints persistingResults:YES]; - XCTAssert(tagAtPoint == UINT32_MAX, @"Tap should to the side of the annotation"); - testPoint++; + self.viewForAnnotation = ^MLNAnnotationView *(MLNMapView *view, id annotation) { + if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { + return nil; } + + // No dequeue + MLNAnnotationView *annotationView = + [[MLNAnnotationView alloc] initWithAnnotation:annotation + reuseIdentifier:MLNTestAnnotationReuseIdentifer]; + annotationView.bounds = (CGRect){.origin = CGPointZero, .size = annotationSize}; + annotationView.backgroundColor = UIColor.redColor; + annotationView.enabled = YES; + annotationView.centerOffset = offset; + + return annotationView; + }; + + MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; + point.title = NSStringFromSelector(_cmd); + point.coordinate = CLLocationCoordinate2DMake(0.0, 0.0); + [self.mapView addAnnotation:point]; + + // From https://github.com/mapbox/mapbox-gl-native/issues/12259#issuecomment-401414168 + // + // queryRenderedFeatures depends on collision detection having been run + // before it shows results [...]. Collision detection runs asynchronously + // (at least every 300ms, sometimes more often), and therefore the results + // of queryRenderedFeatures are similarly asynchronous. + // + // So, we need to wait before `annotationTagAtPoint:persistingResults:` will + // return out newly added annotation + + [self waitForCollisionDetectionToRun]; + + // Check that the annotation is in the center of the view + CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate + toPointToView:self.mapView]; + XCTAssertEqualWithAccuracy(annotationPoint.x, + (size.width - edgeInsets.right + edgeInsets.left) / 2.0, 0.25); + XCTAssertEqualWithAccuracy(annotationPoint.y, + (size.height - edgeInsets.bottom + edgeInsets.top) / 2.0, 0.25); + + // Now test taps around the annotation + CGPoint tapPoint = CGPointMake(annotationPoint.x + offset.dx, annotationPoint.y + offset.dy); + + MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:tapPoint persistingResults:YES]; + XCTAssert(tagAtPoint != UINT32_MAX, @"Should have tapped on annotation"); + + CGPoint testPoints[] = {{tapPoint.x - annotationSize.width, tapPoint.y}, + {tapPoint.x + annotationSize.width, tapPoint.y}, + {tapPoint.x, tapPoint.y - annotationSize.height}, + {tapPoint.x, tapPoint.y + annotationSize.height}, + CGPointZero}; + + CGPoint *testPoint = testPoints; + + while (!CGPointEqualToPoint(*testPoint, CGPointZero)) { + tagAtPoint = [self.mapView annotationTagAtPoint:*testPoints persistingResults:YES]; + XCTAssert(tagAtPoint == UINT32_MAX, @"Tap should to the side of the annotation"); + testPoint++; + } } - (void)testUserLocationWithOffsetAnchorPoint { - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(37.787357, -122.39899)]; - MLNTestLocationManager *locationManager = [[MLNTestLocationManager alloc] init]; - self.mapView.locationManager = locationManager; + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(37.787357, -122.39899)]; + MLNTestLocationManager *locationManager = [[MLNTestLocationManager alloc] init]; + self.mapView.locationManager = locationManager; - [self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil]; - CGRect originalFrame = [self.mapView viewForAnnotation:self.mapView.userLocation].frame; + [self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil]; + CGRect originalFrame = [self.mapView viewForAnnotation:self.mapView.userLocation].frame; - // Temporarily disable location tracking so we can save the value of - // the originalFrame in memory - [self.mapView setUserTrackingMode:MLNUserTrackingModeNone animated:NO completionHandler:nil]; + // Temporarily disable location tracking so we can save the value of + // the originalFrame in memory + [self.mapView setUserTrackingMode:MLNUserTrackingModeNone animated:NO completionHandler:nil]; - CGPoint offset = CGPointMake(20, 20); + CGPoint offset = CGPointMake(20, 20); - self.mapViewUserLocationAnchorPoint = ^CGPoint (MLNMapView *mapView) { - return offset;; - }; + self.mapViewUserLocationAnchorPoint = ^CGPoint(MLNMapView *mapView) { + return offset; + ; + }; - [self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil]; - CGRect offsetFrame = [self.mapView viewForAnnotation:self.mapView.userLocation].frame; + [self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil]; + CGRect offsetFrame = [self.mapView viewForAnnotation:self.mapView.userLocation].frame; - XCTAssertEqual(originalFrame.origin.x + offset.x, offsetFrame.origin.x); - XCTAssertEqual(originalFrame.origin.y + offset.y, offsetFrame.origin.y); + XCTAssertEqual(originalFrame.origin.x + offset.x, offsetFrame.origin.x); + XCTAssertEqual(originalFrame.origin.y + offset.y, offsetFrame.origin.y); } // MARK: - Rotating/zooming - (void)testSelectingAnnotationWhenMapIsRotated { + CLLocationCoordinate2D coordinates[] = {{40.0, 40.0}, {NAN, NAN}}; - CLLocationCoordinate2D coordinates[] = { - { 40.0, 40.0 }, - { NAN, NAN } - }; + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MLNPointAnnotation *annotation = annotations.firstObject; - NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; - MLNPointAnnotation *annotation = annotations.firstObject; + // Rotate + CLLocationDirection lastAngle = 0.0; - // Rotate - CLLocationDirection lastAngle = 0.0; + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++) { + CLLocationDirection angle = (CLLocationDirection)((drand48() * 1080.0) - 540.0); - srand48(0); - for (NSInteger iter = 0; iter < 10; iter++ ) { + CGPoint anchor = CGPointMake(drand48() * CGRectGetWidth(self.mapView.bounds), + drand48() * CGRectGetHeight(self.mapView.bounds)); - CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + NSString *activityTitle = + [NSString stringWithFormat:@"Rotate to: %0.1f from: %0.1f", angle, lastAngle]; + [XCTContext runActivityNamed:activityTitle + block:^(id _Nonnull activity) { + MLNMapCamera *toCamera = + [self.mapView cameraByRotatingToDirection:angle + aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; - CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); - - NSString *activityTitle = [NSString stringWithFormat:@"Rotate to: %0.1f from: %0.1f", angle, lastAngle]; - [XCTContext runActivityNamed:activityTitle - block:^(id _Nonnull activity) { - - MLNMapCamera *toCamera = [self.mapView cameraByRotatingToDirection:angle aroundAnchorPoint:anchor]; - [self internalTestSelecting:annotation withCamera:toCamera]; - }]; - - lastAngle = angle; - } + lastAngle = angle; + } } - (void)testSelectingAnnotationWhenMapIsScaled { + CLLocationCoordinate2D coordinates[] = {{0.005, 0.005}, {NAN, NAN}}; - CLLocationCoordinate2D coordinates[] = { - { 0.005, 0.005 }, - { NAN, NAN } - }; + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MLNPointAnnotation *annotation = annotations.firstObject; - NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; - MLNPointAnnotation *annotation = annotations.firstObject; + CGPoint anchor = + CGPointMake(CGRectGetMidX(self.mapView.bounds), CGRectGetMidY(self.mapView.bounds)); - CGPoint anchor = CGPointMake(CGRectGetMidX(self.mapView.bounds), CGRectGetMidY(self.mapView.bounds)); + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++) { + double zoom = (double)(drand48() * 14.0); - srand48(0); - for (NSInteger iter = 0; iter < 10; iter++ ) { - - double zoom = (double)(drand48()*14.0); - - NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; - [XCTContext runActivityNamed:activityTitle - block:^(id _Nonnull activity) { - MLNMapCamera *toCamera = [self.mapView cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:anchor]; - [self internalTestSelecting:annotation withCamera:toCamera]; - }]; - } + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id _Nonnull activity) { + MLNMapCamera *toCamera = + [self.mapView cameraByZoomingToZoomLevel:zoom + aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } } - (void)testSelectingAnnotationWhenMapIsScaledAndRotated { + CLLocationCoordinate2D coordinates[] = {{0.005, 0.005}, {NAN, NAN}}; - CLLocationCoordinate2D coordinates[] = { - { 0.005, 0.005 }, - { NAN, NAN } - }; + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MLNPointAnnotation *annotation = annotations.firstObject; - NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; - MLNPointAnnotation *annotation = annotations.firstObject; + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++) { + double zoom = (double)(7.0 + drand48() * 7.0); + CLLocationDirection angle = (CLLocationDirection)((drand48() * 1080.0) - 540.0); - srand48(0); - for (NSInteger iter = 0; iter < 10; iter++ ) { + CGPoint anchor = CGPointMake(drand48() * CGRectGetWidth(self.mapView.bounds), + drand48() * CGRectGetHeight(self.mapView.bounds)); - double zoom = (double)(7.0 + drand48()*7.0); - CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id _Nonnull activity) { + mbgl::CameraOptions currentCameraOptions; - CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); + currentCameraOptions.bearing = angle; + currentCameraOptions.anchor = + mbgl::ScreenCoordinate{anchor.x, anchor.y}; + currentCameraOptions.zoom = zoom; + MLNMapCamera *toCamera = + [self.mapView cameraForCameraOptions:currentCameraOptions]; - NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; - [XCTContext runActivityNamed:activityTitle - block:^(id _Nonnull activity) - { - mbgl::CameraOptions currentCameraOptions; - - currentCameraOptions.bearing = angle; - currentCameraOptions.anchor = mbgl::ScreenCoordinate { anchor.x, anchor.y }; - currentCameraOptions.zoom = zoom; - MLNMapCamera *toCamera = [self.mapView cameraForCameraOptions:currentCameraOptions]; - - [self internalTestSelecting:annotation withCamera:toCamera]; - }]; - } + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } } - - (void)testShowingAnnotationsThenSelectingAnimated { - [self internalTestShowingAnnotationsThenSelectingAnimated:YES]; + [self internalTestShowingAnnotationsThenSelectingAnimated:YES]; } - (void)testShowingAnnotationsThenSelecting { - [self internalTestShowingAnnotationsThenSelectingAnimated:NO]; + [self internalTestShowingAnnotationsThenSelectingAnimated:NO]; } - (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated { - srand48(0); + srand48(0); - CGFloat maxXPadding = std::max(CGRectGetWidth(self.mapView.bounds)/5.0, 100.0); - CGFloat maxYPadding = std::max(CGRectGetHeight(self.mapView.bounds)/5.0, 100.0); + CGFloat maxXPadding = std::max(CGRectGetWidth(self.mapView.bounds) / 5.0, 100.0); + CGFloat maxYPadding = std::max(CGRectGetHeight(self.mapView.bounds) / 5.0, 100.0); - for (int i = 0; i < 10; i++) { - UIEdgeInsets edgePadding; - edgePadding.top = floor(drand48()*maxYPadding); - edgePadding.bottom = floor(drand48()*maxYPadding); - edgePadding.left = floor(drand48()*maxXPadding); - edgePadding.right = floor(drand48()*maxXPadding); + for (int i = 0; i < 10; i++) { + UIEdgeInsets edgePadding; + edgePadding.top = floor(drand48() * maxYPadding); + edgePadding.bottom = floor(drand48() * maxYPadding); + edgePadding.left = floor(drand48() * maxXPadding); + edgePadding.right = floor(drand48() * maxXPadding); - UIEdgeInsets contentInsets; - contentInsets.top = floor(drand48()*maxYPadding); - contentInsets.bottom = floor(drand48()*maxYPadding); - contentInsets.left = floor(drand48()*maxXPadding); - contentInsets.right = floor(drand48()*maxXPadding); + UIEdgeInsets contentInsets; + contentInsets.top = floor(drand48() * maxYPadding); + contentInsets.bottom = floor(drand48() * maxYPadding); + contentInsets.left = floor(drand48() * maxXPadding); + contentInsets.right = floor(drand48() * maxXPadding); - [self internalTestShowingAnnotationsThenSelectingAnimated:animated edgePadding:edgePadding contentInsets:contentInsets]; - } + [self internalTestShowingAnnotationsThenSelectingAnimated:animated + edgePadding:edgePadding + contentInsets:contentInsets]; + } } -- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgeInsets contentInsets:(UIEdgeInsets)contentInsets { - CLLocationCoordinate2D coordinates[21]; +- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated + edgePadding:(UIEdgeInsets)edgeInsets + contentInsets:(UIEdgeInsets)contentInsets { + CLLocationCoordinate2D coordinates[21]; - for (int i = 0; i < (int)(sizeof(coordinates)/sizeof(coordinates[0])); i++) - { - coordinates[i].latitude = drand48(); - coordinates[i].longitude = drand48(); - } - coordinates[20] = CLLocationCoordinate2DMake(NAN, NAN); + for (int i = 0; i < (int)(sizeof(coordinates) / sizeof(coordinates[0])); i++) { + coordinates[i].latitude = drand48(); + coordinates[i].longitude = drand48(); + } + coordinates[20] = CLLocationCoordinate2DMake(NAN, NAN); - NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; - XCTestExpectation *showCompleted = [self expectationWithDescription:@"showCompleted"]; + XCTestExpectation *showCompleted = [self expectationWithDescription:@"showCompleted"]; - self.mapView.contentInset = contentInsets; - [self.mapView showAnnotations:annotations - edgePadding:edgeInsets - animated:animated - completionHandler:^{ - [showCompleted fulfill]; - }]; + self.mapView.contentInset = contentInsets; + [self.mapView showAnnotations:annotations + edgePadding:edgeInsets + animated:animated + completionHandler:^{ + [showCompleted fulfill]; + }]; - [self waitForExpectations:@[showCompleted] timeout:3.5]; + [self waitForExpectations:@[ showCompleted ] timeout:3.5]; - // These tests will fail if this isn't here. But this isn't quite what we're - // seeing in https://github.com/mapbox/mapbox-gl-native/issues/15106 - [self waitForCollisionDetectionToRun]; + // These tests will fail if this isn't here. But this isn't quite what we're + // seeing in https://github.com/mapbox/mapbox-gl-native/issues/15106 + [self waitForCollisionDetectionToRun]; - for (MLNPointAnnotation *point in annotations) { - [self internalSelectDeselectAnnotation:point]; - } - - [self.mapView removeAnnotations:annotations]; - self.mapView.contentInset = UIEdgeInsetsZero; - [self waitForCollisionDetectionToRun]; -} - -- (NSArray*)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D*)coordinates -{ - __block NSMutableArray *annotations = [NSMutableArray array]; - - [XCTContext runActivityNamed:@"Map setup" - block:^(id _Nonnull activity) - { - - NSString * const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; - - CGSize annotationSize = CGSizeMake(40.0, 40.0); - - self.viewForAnnotation = ^MLNAnnotationView*(MLNMapView *view, id annotation2) { - - if (![annotation2 isKindOfClass:[MLNPointAnnotation class]]) { - return nil; - } - - // No dequeue - MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation2 reuseIdentifier:MLNTestAnnotationReuseIdentifer]; - annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; - annotationView.backgroundColor = UIColor.redColor; - annotationView.enabled = YES; - - return annotationView; - }; - - CLLocationCoordinate2D *coordinatePtr = coordinates; - while (!isnan(coordinatePtr->latitude)) { - CLLocationCoordinate2D coordinate = *coordinatePtr++; - - MLNPointAnnotation *annotation = [[MLNPointAnnotation alloc] init]; - annotation.title = NSStringFromSelector(_cmd); - annotation.coordinate = coordinate; - [annotations addObject:annotation]; - } - - [self.mapView addAnnotations:annotations]; - - }]; - - NSArray *copiedAnnotations = [annotations copy]; - annotations = nil; - - return copiedAnnotations; -} - -- (void)internalTestSelecting:(MLNPointAnnotation*)point withCamera:(MLNMapCamera*)camera { - - // Rotate - XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"]; - [self.mapView setCamera:camera withDuration:0.1 animationTimingFunction:nil completionHandler:^{ - [rotationCompleted fulfill]; - }]; - - [self waitForExpectations:@[rotationCompleted] timeout:1.5]; - - // Collision detection may not have completed, if not we may not get our annotation. - [self waitForCollisionDetectionToRun]; - - // Look up annotation at point + for (MLNPointAnnotation *point in annotations) { [self internalSelectDeselectAnnotation:point]; + } + + [self.mapView removeAnnotations:annotations]; + self.mapView.contentInset = UIEdgeInsetsZero; + [self waitForCollisionDetectionToRun]; } -- (void)internalSelectDeselectAnnotation:(MLNPointAnnotation*)point { - [XCTContext runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point] - block:^(id _Nonnull activity) - { - CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; +- (NSArray *)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D *)coordinates { + __block NSMutableArray *annotations = [NSMutableArray array]; - MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint persistingResults:YES]; - if (tagAtPoint != UINT32_MAX) - { - id annotation = [self.mapView annotationWithTag:tagAtPoint]; - XCTAssertNotNil(annotation); + [XCTContext runActivityNamed:@"Map setup" + block:^(id _Nonnull activity) { + NSString *const MLNTestAnnotationReuseIdentifer = + @"MLNTestAnnotationReuseIdentifer"; - // Select - XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; - [self.mapView selectAnnotation:annotation moveIntoView:NO animateSelection:NO completionHandler:^{ - [selectionCompleted fulfill]; - }]; + CGSize annotationSize = CGSizeMake(40.0, 40.0); - [self waitForExpectations:@[selectionCompleted] timeout:0.05]; + self.viewForAnnotation = ^MLNAnnotationView *( + MLNMapView *view, id annotation2) { + if (![annotation2 isKindOfClass:[MLNPointAnnotation class]]) { + return nil; + } - XCTAssert(self.mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation"); - XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); + // No dequeue + MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] + initWithAnnotation:annotation2 + reuseIdentifier:MLNTestAnnotationReuseIdentifer]; + annotationView.bounds = + (CGRect){.origin = CGPointZero, .size = annotationSize}; + annotationView.backgroundColor = UIColor.redColor; + annotationView.enabled = YES; - // Deselect - [self.mapView deselectAnnotation:annotation animated:NO]; - } - else - { - XCTFail(@"Should be an annotation at this point: %@", NSStringFromCGPoint(annotationPoint)); - } - }]; + return annotationView; + }; + CLLocationCoordinate2D *coordinatePtr = coordinates; + while (!isnan(coordinatePtr->latitude)) { + CLLocationCoordinate2D coordinate = *coordinatePtr++; + + MLNPointAnnotation *annotation = [[MLNPointAnnotation alloc] init]; + annotation.title = NSStringFromSelector(_cmd); + annotation.coordinate = coordinate; + [annotations addObject:annotation]; + } + + [self.mapView addAnnotations:annotations]; + }]; + + NSArray *copiedAnnotations = [annotations copy]; + annotations = nil; + + return copiedAnnotations; +} + +- (void)internalTestSelecting:(MLNPointAnnotation *)point withCamera:(MLNMapCamera *)camera { + // Rotate + XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"]; + [self.mapView setCamera:camera + withDuration:0.1 + animationTimingFunction:nil + completionHandler:^{ + [rotationCompleted fulfill]; + }]; + + [self waitForExpectations:@[ rotationCompleted ] timeout:1.5]; + + // Collision detection may not have completed, if not we may not get our annotation. + [self waitForCollisionDetectionToRun]; + + // Look up annotation at point + [self internalSelectDeselectAnnotation:point]; +} + +- (void)internalSelectDeselectAnnotation:(MLNPointAnnotation *)point { + [XCTContext + runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point] + block:^(id _Nonnull activity) { + CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate + toPointToView:self.mapView]; + + MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint + persistingResults:YES]; + if (tagAtPoint != UINT32_MAX) { + id annotation = [self.mapView annotationWithTag:tagAtPoint]; + XCTAssertNotNil(annotation); + + // Select + XCTestExpectation *selectionCompleted = + [self expectationWithDescription:@"Selection completed"]; + [self.mapView selectAnnotation:annotation + moveIntoView:NO + animateSelection:NO + completionHandler:^{ + [selectionCompleted fulfill]; + }]; + + [self waitForExpectations:@[ selectionCompleted ] timeout:0.05]; + + XCTAssert(self.mapView.selectedAnnotations.count == 1, + @"There should only be 1 selected annotation"); + XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation, + @"The annotation should be selected"); + + // Deselect + [self.mapView deselectAnnotation:annotation animated:NO]; + } else { + XCTFail(@"Should be an annotation at this point: %@", + NSStringFromCGPoint(annotationPoint)); + } + }]; } // MARK: - Utilities - (void)runRunLoop { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } - (void)waitFor:(NSTimeInterval)seconds { - XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; + XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; - NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 - target:self - selector:@selector(runRunLoop) - userInfo:nil - repeats:YES]; + NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 + target:self + selector:@selector(runRunLoop) + userInfo:nil + repeats:YES]; - double duration = seconds * (double)NSEC_PER_SEC; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)duration), dispatch_get_main_queue(), ^{ - [timerExpired fulfill]; - }); + double duration = seconds * (double)NSEC_PER_SEC; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)duration), dispatch_get_main_queue(), ^{ + [timerExpired fulfill]; + }); - [self waitForExpectations:@[timerExpired] timeout:seconds + 1.0]; - [timer invalidate]; + [self waitForExpectations:@[ timerExpired ] timeout:seconds + 1.0]; + [timer invalidate]; } - (void)waitForCollisionDetectionToRun { - XCTAssertNil(self.renderFinishedExpectation, @"Incorrect test setup"); + XCTAssertNil(self.renderFinishedExpectation, @"Incorrect test setup"); - [self.mapView setNeedsRerender]; - self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; - XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; + [self.mapView setNeedsRerender]; + self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; + XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; - // Wait 1/2 second - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC >> 1)), dispatch_get_main_queue(), ^{ - [timerExpired fulfill]; - }); + // Wait 1/2 second + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC >> 1)), + dispatch_get_main_queue(), ^{ + [timerExpired fulfill]; + }); - [self waitForExpectations:@[timerExpired, self.renderFinishedExpectation] timeout:5]; + [self waitForExpectations:@[ timerExpired, self.renderFinishedExpectation ] timeout:5]; - self.renderFinishedExpectation = nil; + self.renderFinishedExpectation = nil; } @end diff --git a/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionFinishTests.mm b/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionFinishTests.mm index fc0bb7b51..0f57b8158 100644 --- a/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionFinishTests.mm +++ b/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionFinishTests.mm @@ -1,6 +1,6 @@ +#import "../../../darwin/src/MLNGeometry_Private.h" #import "MLNMapViewIntegrationTest.h" #import "MLNTestUtility.h" -#import "../../../darwin/src/MLNGeometry_Private.h" #include @@ -10,90 +10,93 @@ @implementation MLNCameraTransitionFinishTests - (void)testEaseToCompletionHandler { + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), - CLLocationCoordinate2DMake(1.0, 1.0)); - MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Completion block should be called"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; - - [self.mapView setCamera:camera - withDuration:0.0 - animationTimingFunction:nil - completionHandler:^{ + [self.mapView setCamera:camera + withDuration:0.0 + animationTimingFunction:nil + completionHandler:^{ [expectation fulfill]; - }]; + }]; - [self waitForExpectations:@[expectation] timeout:0.5]; + [self waitForExpectations:@[ expectation ] timeout:0.5]; } - (void)testEaseToCompletionHandlerAnimated { + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), - CLLocationCoordinate2DMake(1.0, 1.0)); - MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Completion block should be called"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; - - [self.mapView setCamera:camera - withDuration:0.3 - animationTimingFunction:nil - completionHandler:^{ + [self.mapView setCamera:camera + withDuration:0.3 + animationTimingFunction:nil + completionHandler:^{ [expectation fulfill]; - }]; + }]; - [self waitForExpectations:@[expectation] timeout:0.5]; + [self waitForExpectations:@[ expectation ] timeout:0.5]; } - (void)testFlyToCompletionHandler { + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), - CLLocationCoordinate2DMake(1.0, 1.0)); - MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Completion block should be called"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + [self.mapView flyToCamera:camera + withDuration:0.0 + completionHandler:^{ + [expectation fulfill]; + }]; - [self.mapView flyToCamera:camera - withDuration:0.0 - completionHandler:^{ - [expectation fulfill]; - }]; - - [self waitForExpectations:@[expectation] timeout:0.5]; + [self waitForExpectations:@[ expectation ] timeout:0.5]; } - (void)testFlyToCompletionHandlerAnimated { + MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; - MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), - CLLocationCoordinate2DMake(1.0, 1.0)); - MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + XCTestExpectation *expectation = + [self expectationWithDescription:@"Completion block should be called"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + [self.mapView flyToCamera:camera + withDuration:0.3 + completionHandler:^{ + [expectation fulfill]; + }]; - [self.mapView flyToCamera:camera - withDuration:0.3 - completionHandler:^{ - [expectation fulfill]; - }]; - - [self waitForExpectations:@[expectation] timeout:0.5]; + [self waitForExpectations:@[ expectation ] timeout:0.5]; } @end // MARK: - camera transitions with NaN values @interface MLNMapView (MLNCameraTransitionFinishNaNTests) -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets; +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera + edgePadding:(UIEdgeInsets)insets; @end -@interface MLNCameraTransitionNaNZoomMapView: MLNMapView +@interface MLNCameraTransitionNaNZoomMapView : MLNMapView @end @implementation MLNCameraTransitionNaNZoomMapView -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets { - mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; - options.zoom = NAN; - return options; +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera + edgePadding:(UIEdgeInsets)insets { + mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera + edgePadding:insets]; + options.zoom = NAN; + return options; } @end @@ -103,6 +106,6 @@ @implementation MLNCameraTransitionFinishNaNTests - (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL { - return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL]; + return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL]; } @end diff --git a/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionTests.mm b/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionTests.mm index 85dab267f..6ffa54e42 100644 --- a/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionTests.mm +++ b/platform/ios/Integration_Tests/Camera Tests/MLNCameraTransitionTests.mm @@ -1,6 +1,6 @@ +#import "../../../darwin/src/MLNGeometry_Private.h" #import "MLNMapViewIntegrationTest.h" #import "MLNTestUtility.h" -#import "../../../darwin/src/MLNGeometry_Private.h" @interface MLNCameraTransitionTests : MLNMapViewIntegrationTest @end @@ -8,387 +8,350 @@ @implementation MLNCameraTransitionTests - (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod { + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 2; + expectation.assertForOverFulfill = YES; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 2; - expectation.assertForOverFulfill = YES; + __weak MLNCameraTransitionTests *weakself = self; - __weak MLNCameraTransitionTests *weakself = self; + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + MLNCameraTransitionTests *strongSelf = weakself; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + if (!strongSelf) return; - MLNCameraTransitionTests *strongSelf = weakself; + [expectation fulfill]; - if (!strongSelf) return; + MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); + if (mapView.direction != 0.0) { + dispatch_async(dispatch_get_main_queue(), ^{ + [mapView resetNorth]; + }); + } + }; - [expectation fulfill]; + [self.mapView setDirection:90 animated:YES]; - MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); - if (mapView.direction != 0.0) { - dispatch_async(dispatch_get_main_queue(), ^{ - [mapView resetNorth]; - }); - } - }; - - [self.mapView setDirection:90 animated:YES]; - - // loop, render, and wait - [self waitForExpectations:@[expectation] timeout:10]; + // loop, render, and wait + [self waitForExpectations:@[ expectation ] timeout:10]; } - - (void)testSetAndResetNorthInDelegateMethod { + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 2; + expectation.assertForOverFulfill = YES; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 2; - expectation.assertForOverFulfill = YES; + __weak MLNCameraTransitionTests *weakself = self; - __weak MLNCameraTransitionTests *weakself = self; + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + MLNCameraTransitionTests *strongSelf = weakself; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + if (!strongSelf) return; - MLNCameraTransitionTests *strongSelf = weakself; + [expectation fulfill]; - if (!strongSelf) return; + MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); + if (mapView.direction != 0.0) { + NSLog(@"Reset to north"); + [mapView resetNorth]; + } + }; - [expectation fulfill]; - - MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); - if (mapView.direction != 0.0) { - NSLog(@"Reset to north"); - [mapView resetNorth]; - } - }; - - [self.mapView setDirection:90 animated:YES]; - [self waitForExpectations:@[expectation] timeout:10]; + [self.mapView setDirection:90 animated:YES]; + [self waitForExpectations:@[ expectation ] timeout:10]; } - (void)testInterruptingAndResetNorthOnlyOnceInIsChanging { + // Reset to non-zero, prior to testing + [self.mapView setDirection:45 animated:NO]; - // Reset to non-zero, prior to testing - [self.mapView setDirection:45 animated:NO]; + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 1; + expectation.assertForOverFulfill = YES; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 1; - expectation.assertForOverFulfill = YES; + __weak MLNCameraTransitionTests *weakself = self; + __block BOOL startedReset = NO; + __block BOOL finishedReset = NO; - __weak MLNCameraTransitionTests *weakself = self; - __block BOOL startedReset = NO; - __block BOOL finishedReset = NO; + self.regionIsChanging = ^(MLNMapView *mapView) { + MLNCameraTransitionTests *strongSelf = weakself; + if (!strongSelf) return; - self.regionIsChanging = ^(MLNMapView *mapView) { - MLNCameraTransitionTests *strongSelf = weakself; - if (!strongSelf) return; + if (!startedReset) { + NSLog(@"Reset to north, interrupting the previous transition"); + startedReset = YES; + [mapView resetNorth]; + finishedReset = YES; + } + }; - if (!startedReset) { - NSLog(@"Reset to north, interrupting the previous transition"); - startedReset = YES; - [mapView resetNorth]; - finishedReset = YES; - } - }; + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + MLNCameraTransitionTests *strongSelf = weakself; + if (!strongSelf) return; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { - MLNCameraTransitionTests *strongSelf = weakself; - if (!strongSelf) return; + MLNTestAssert(strongSelf, startedReset); - MLNTestAssert(strongSelf, startedReset); + if (finishedReset) { + MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled)); + [expectation fulfill]; + } else { + MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled); + } + }; - if (finishedReset) { - MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled)); - [expectation fulfill]; - } - else { - MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled); - } - }; + [self.mapView setDirection:90 animated:YES]; + [self waitForExpectations:@[ expectation ] timeout:10]; - [self.mapView setDirection:90 animated:YES]; - [self waitForExpectations:@[expectation] timeout:10]; - - XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction); + XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, + @"Camera should have reset to north. %0.3f", self.mapView.direction); } - (void)testSetCenterCancelsTransitions { - XCTestExpectation *cameraIsInDCExpectation = [self expectationWithDescription:@"camera reset to DC"]; + XCTestExpectation *cameraIsInDCExpectation = + [self expectationWithDescription:@"camera reset to DC"]; - CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487); - CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487); + CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487); + CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487); - double zoomLevel = 15.0; + double zoomLevel = 15.0; - [self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; - [self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES]; + [self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; + [self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES]; - __weak MLNCameraTransitionTests *weakself = self; + __weak MLNCameraTransitionTests *weakself = self; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), - dispatch_get_main_queue(), - ^{ - MLNCameraTransitionTests *strongSelf = weakself; + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + MLNCameraTransitionTests *strongSelf = weakself; - [strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; - MLNTestAssertEqualWithAccuracy(strongSelf, - dc.latitude, - strongSelf.mapView.centerCoordinate.latitude, - 0.0005, - @"setting center coordinate should cancel transitions"); - MLNTestAssertEqualWithAccuracy(strongSelf, - dc.longitude, - strongSelf.mapView.centerCoordinate.longitude, - 0.0005, - @"setting center coordinate should cancel transitions"); - [cameraIsInDCExpectation fulfill]; - }); + [strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; + MLNTestAssertEqualWithAccuracy(strongSelf, dc.latitude, + strongSelf.mapView.centerCoordinate.latitude, 0.0005, + @"setting center coordinate should cancel transitions"); + MLNTestAssertEqualWithAccuracy(strongSelf, dc.longitude, + strongSelf.mapView.centerCoordinate.longitude, 0.0005, + @"setting center coordinate should cancel transitions"); + [cameraIsInDCExpectation fulfill]; + }); - [self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0]; + [self waitForExpectations:@[ cameraIsInDCExpectation ] timeout:10.0]; } - (void)testSetCenterCoordinateInDelegateMethod { + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 2; + expectation.assertForOverFulfill = YES; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 2; - expectation.assertForOverFulfill = YES; + __weak MLNCameraTransitionTests *weakself = self; + __block NSInteger delegateCallCount = 0; - __weak MLNCameraTransitionTests *weakself = self; - __block NSInteger delegateCallCount = 0; + CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); + CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0); - CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); - CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0); + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + MLNCameraTransitionTests *strongSelf = weakself; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + if (!strongSelf) return; - MLNCameraTransitionTests *strongSelf = weakself; + MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); - if (!strongSelf) return; + CLLocationCoordinate2D center = mapView.centerCoordinate; - MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); + switch (delegateCallCount) { + case 0: { + // Our center coordinate should match our target (assuming we're not + // constrained by zoom level) + MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005, + @"center coordinate longitude should be at target"); - CLLocationCoordinate2D center = mapView.centerCoordinate; + MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005, + @"center coordinate latitude should be at target"); - switch(delegateCallCount) { - case 0: - { - // Our center coordinate should match our target (assuming we're not - // constrained by zoom level) - MLNTestAssertEqualWithAccuracy(strongSelf, - target.longitude, - center.longitude, - 0.0005, - @"center coordinate longitude should be at target"); + // Now set another coordinate. + // Should take MLNAnimationDuration seconds (0.3s) + [mapView setCenterCoordinate:target2 animated:YES]; + break; + } - MLNTestAssertEqualWithAccuracy(strongSelf, - target.latitude, - center.latitude, - 0.0005, - @"center coordinate latitude should be at target"); + case 1: { + // Our center coordinate should match our target (assuming we're not + // constrained by zoom level) + MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005, + @"center coordinate longitude should be at target2"); - // Now set another coordinate. - // Should take MLNAnimationDuration seconds (0.3s) - [mapView setCenterCoordinate:target2 animated:YES]; - break; - } + MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005, + @"center coordinate latitude should be at target2"); + break; + } - case 1: - { - // Our center coordinate should match our target (assuming we're not - // constrained by zoom level) - MLNTestAssertEqualWithAccuracy(strongSelf, - target2.longitude, - center.longitude, - 0.0005, - @"center coordinate longitude should be at target2"); + default: + MLNTestFail(strongSelf); + break; + } - MLNTestAssertEqualWithAccuracy(strongSelf, - target2.latitude, - center.latitude, - 0.0005, - @"center coordinate latitude should be at target2"); - break; + delegateCallCount++; - } + [expectation fulfill]; + }; - default: - MLNTestFail(strongSelf); - break; - } - - delegateCallCount++; - - [expectation fulfill]; - }; - - // Should take MLNAnimationDuration seconds (0.3) - [self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES]; - [self waitForExpectations:@[expectation] timeout:10]; + // Should take MLNAnimationDuration seconds (0.3) + [self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES]; + [self waitForExpectations:@[ expectation ] timeout:10]; } - (void)testFlyToCameraInDelegateMethod { + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + __weak MLNCameraTransitionTests *weakself = self; + __block NSInteger delegateCallCount = 0; + expectation.expectedFulfillmentCount = 3; + expectation.assertForOverFulfill = YES; - __weak MLNCameraTransitionTests *weakself = self; - __block NSInteger delegateCallCount = 0; - expectation.expectedFulfillmentCount = 3; - expectation.assertForOverFulfill = YES; + CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); + CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0); - CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); - CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0); + __block BOOL runloop = YES; - __block BOOL runloop = YES; + NSTimeInterval stop0 = CACurrentMediaTime(); + __block NSTimeInterval stop1 = 0.0; + __block NSTimeInterval stop2 = 0.0; - NSTimeInterval stop0 = CACurrentMediaTime(); - __block NSTimeInterval stop1 = 0.0; - __block NSTimeInterval stop2 = 0.0; + double zoomLevel = 5.0; + double altitude = + MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size); - double zoomLevel = 5.0; - double altitude = MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size); + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + MLNCameraTransitionTests *strongSelf = weakself; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + if (!strongSelf) return; - MLNCameraTransitionTests *strongSelf = weakself; + MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); - if (!strongSelf) return; + CLLocationCoordinate2D center = mapView.centerCoordinate; - MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); + switch (delegateCallCount) { + case 0: { + stop1 = CACurrentMediaTime(); - CLLocationCoordinate2D center = mapView.centerCoordinate; + // Our center coordinate should match our target (assuming we're not + // constrained by zoom level) + MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005, + @"center coordinate longitude should be at target"); - switch(delegateCallCount) { - case 0: - { - stop1 = CACurrentMediaTime(); + MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005, + @"center coordinate latitude should be at target"); - // Our center coordinate should match our target (assuming we're not - // constrained by zoom level) - MLNTestAssertEqualWithAccuracy(strongSelf, - target.longitude, - center.longitude, - 0.0005, - @"center coordinate longitude should be at target"); + // Now set another coordinate. + MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2 + altitude:altitude + pitch:0.0 + heading:0.0]; - MLNTestAssertEqualWithAccuracy(strongSelf, - target.latitude, - center.latitude, - 0.0005, - @"center coordinate latitude should be at target"); + // flyToCamera can take a while... + [mapView flyToCamera:camera + completionHandler:^{ + MLNTestAssert(strongSelf, !runloop, + @"Completion block should be called after delegate method"); + [expectation fulfill]; + stop2 = CACurrentMediaTime(); + }]; + break; + } - // Now set another coordinate. - MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2 - altitude:altitude - pitch:0.0 - heading:0.0]; + case 1: { + // Our center coordinate should match our target (assuming we're not + // constrained by zoom level) + MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005, + @"center coordinate longitude should be at target2"); - // flyToCamera can take a while... - [mapView flyToCamera:camera completionHandler:^{ - MLNTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method"); - [expectation fulfill]; - stop2 = CACurrentMediaTime(); - }]; - break; - } + MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005, + @"center coordinate latitude should be at target2"); - case 1: - { - // Our center coordinate should match our target (assuming we're not - // constrained by zoom level) - MLNTestAssertEqualWithAccuracy(strongSelf, - target2.longitude, - center.longitude, - 0.0005, - @"center coordinate longitude should be at target2"); + runloop = NO; + break; + } - MLNTestAssertEqualWithAccuracy(strongSelf, - target2.latitude, - center.latitude, - 0.0005, - @"center coordinate latitude should be at target2"); + default: + MLNTestFail(strongSelf); + break; + } - runloop = NO; - break; - } + delegateCallCount++; - default: - MLNTestFail(strongSelf); - break; - } + [expectation fulfill]; + }; - delegateCallCount++; + // Should take MLNAnimationDuration + [self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES]; - [expectation fulfill]; - }; + [self waitForExpectations:@[ expectation ] timeout:10]; - // Should take MLNAnimationDuration - [self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES]; + NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0); + NSLog(@"flyToCamera: %0.4fs", stop2 - stop1); - [self waitForExpectations:@[expectation] timeout:10]; - - NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0); - NSLog(@"flyToCamera: %0.4fs", stop2 - stop1); - - XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", (long)delegateCallCount); // Once for the setDirection and once for the reset north + XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", + (long)delegateCallCount); // Once for the setDirection and once for the reset north } // MARK: - Pending tests -- (void)testContinuallyResettingNorthInIsChangingPENDING{ - // See https://github.com/mapbox/mapbox-gl-native/pull/11614 - // This test currently fails, unsurprisingly, since we're continually - // setting the camera to the same parameters during its update. - // - // Possible solutions/expectations: - // - If you set camera parameters that match the *current* target parameters - // then the transition could be a no-op. We'd need to consider any completion - // block - // - Ideally we would detect this case and disallow it. +- (void)testContinuallyResettingNorthInIsChangingPENDING { + // See https://github.com/mapbox/mapbox-gl-native/pull/11614 + // This test currently fails, unsurprisingly, since we're continually + // setting the camera to the same parameters during its update. + // + // Possible solutions/expectations: + // - If you set camera parameters that match the *current* target parameters + // then the transition could be a no-op. We'd need to consider any completion + // block + // - Ideally we would detect this case and disallow it. - // Reset to non-zero, prior to testing - [self.mapView setDirection:45 animated:NO]; + // Reset to non-zero, prior to testing + [self.mapView setDirection:45 animated:NO]; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 2; - expectation.assertForOverFulfill = YES; + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 2; + expectation.assertForOverFulfill = YES; - self.regionIsChanging = ^(MLNMapView *mapView) { - [mapView resetNorth]; - }; + self.regionIsChanging = ^(MLNMapView *mapView) { + [mapView resetNorth]; + }; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { - [expectation fulfill]; - }; + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + [expectation fulfill]; + }; - [self.mapView setDirection:90 animated:YES]; - [self waitForExpectations:@[expectation] timeout:10]; + [self.mapView setDirection:90 animated:YES]; + [self waitForExpectations:@[ expectation ] timeout:10]; - XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction); + XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, + @"Camera should have reset to north. %0.3f", self.mapView.direction); } - (void)testContinuallySettingCoordinateInIsChangingPENDING { - // See above comment in `-testContinuallyResettingNorthInIsChangingPENDING` + // See above comment in `-testContinuallyResettingNorthInIsChangingPENDING` - // Reset to non-zero, prior to testing - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO]; + // Reset to non-zero, prior to testing + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO]; - XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; - expectation.expectedFulfillmentCount = 2; - expectation.assertForOverFulfill = YES; + XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; + expectation.expectedFulfillmentCount = 2; + expectation.assertForOverFulfill = YES; - __weak MLNCameraTransitionTests *weakself = self; + __weak MLNCameraTransitionTests *weakself = self; - self.regionIsChanging = ^(MLNMapView *mapView) { - [weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES]; - }; + self.regionIsChanging = ^(MLNMapView *mapView) { + [weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES]; + }; - self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { - [expectation fulfill]; - }; + self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { + [expectation fulfill]; + }; - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES]; - [self waitForExpectations:@[expectation] timeout:10]; + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES]; + [self waitForExpectations:@[ expectation ] timeout:10]; - XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction); + XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, + @"Camera should have reset to north. %0.3f", self.mapView.direction); } @end diff --git a/platform/ios/Integration_Tests/MBGLIntegrationTests.mm b/platform/ios/Integration_Tests/MBGLIntegrationTests.mm index 3a6b63379..5d8ca957a 100644 --- a/platform/ios/Integration_Tests/MBGLIntegrationTests.mm +++ b/platform/ios/Integration_Tests/MBGLIntegrationTests.mm @@ -1,6 +1,6 @@ +#import "MLNMapView+Impl.h" #import "MLNMapViewIntegrationTest.h" #import "MLNMapView_Private.h" -#import "MLNMapView+Impl.h" #include @@ -12,240 +12,247 @@ // MARK: - Tests - (void)waitForMapViewToBeRendered { - [self waitForMapViewToBeRenderedWithTimeout:10]; + [self waitForMapViewToBeRenderedWithTimeout:10]; } // This test does not strictly need to be in this test file/target. Including here for convenience. - (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - __weak id weakLayer = layer; - layer = nil; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + __weak id weakLayer = layer; + layer = nil; - XCTAssertNil(weakLayer); + XCTAssertNil(weakLayer); } - (void)testAddingRemovingOpenGLLayerWithoutRendering { - XCTAssertNotNil(self.style); + XCTAssertNotNil(self.style); - void(^addRemoveGLLayer)(void) = ^{ - __weak id weakLayer = nil; + void (^addRemoveGLLayer)(void) = ^{ + __weak id weakLayer = nil; - @autoreleasepool { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - weakLayer = layer; + @autoreleasepool { + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + weakLayer = layer; - // Nil the layer prior to remove to ensure it's being retained - layer = nil; - [self.style removeLayer:weakLayer]; - } + // Nil the layer prior to remove to ensure it's being retained + layer = nil; + [self.style removeLayer:weakLayer]; + } - XCTAssertNil(weakLayer); - }; + XCTAssertNil(weakLayer); + }; - addRemoveGLLayer(); - addRemoveGLLayer(); - addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); } - (void)testReusingOpenGLLayerIdentifier { - __weak MLNCustomStyleLayer *weakLayer2; + __weak MLNCustomStyleLayer *weakLayer2; - @autoreleasepool { - MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer1 atIndex:0]; - [self waitForMapViewToBeRendered]; - [self.style removeLayer:layer1]; - - MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer2 = layer2; - - XCTAssertNotNil(layer2); - XCTAssert(layer1 != layer2); - - [self.style insertLayer:layer2 atIndex:0]; - [self waitForMapViewToBeRendered]; - [self.style removeLayer:layer2]; - - XCTAssertNil([layer1 style]); - XCTAssertNil([layer2 style]); - } - - // At this point, layer2 (and layer1) should still be around, - // since the render process needs to keep a reference to them. - XCTAssertNotNil(weakLayer2); - - // Let render loop run enough to release the layers + @autoreleasepool { + MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer1 atIndex:0]; [self waitForMapViewToBeRendered]; - XCTAssertNil(weakLayer2); + [self.style removeLayer:layer1]; + + MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer2 = layer2; + + XCTAssertNotNil(layer2); + XCTAssert(layer1 != layer2); + + [self.style insertLayer:layer2 atIndex:0]; + [self waitForMapViewToBeRendered]; + [self.style removeLayer:layer2]; + + XCTAssertNil([layer1 style]); + XCTAssertNil([layer2 style]); + } + + // At this point, layer2 (and layer1) should still be around, + // since the render process needs to keep a reference to them. + XCTAssertNotNil(weakLayer2); + + // Let render loop run enough to release the layers + [self waitForMapViewToBeRendered]; + XCTAssertNil(weakLayer2); } - (void)testAddingRemovingOpenGLLayer { - XCTAssertNotNil(self.style); + XCTAssertNotNil(self.style); - void(^addRemoveGLLayer)(void) = ^{ + void (^addRemoveGLLayer)(void) = ^{ + __weak id retrievedLayer = nil; - __weak id retrievedLayer = nil; + @autoreleasepool { + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + layer = nil; - @autoreleasepool { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - layer = nil; + [self waitForMapViewToBeRendered]; - [self waitForMapViewToBeRendered]; + retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"]; + XCTAssertNotNil(retrievedLayer); - retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"]; - XCTAssertNotNil(retrievedLayer); + [self.style removeLayer:retrievedLayer]; + [self waitForMapViewToBeRendered]; + } - [self.style removeLayer:retrievedLayer]; - [self waitForMapViewToBeRendered]; - } + XCTAssertNil(retrievedLayer); + }; - XCTAssertNil(retrievedLayer); - }; - - addRemoveGLLayer(); - addRemoveGLLayer(); - addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); } - (void)testReusingOpenGLLayer { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - [self waitForMapViewToBeRendered]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + [self waitForMapViewToBeRendered]; - [self.style removeLayer:layer]; - [self waitForMapViewToBeRendered]; + [self.style removeLayer:layer]; + [self waitForMapViewToBeRendered]; - [self.style insertLayer:layer atIndex:0]; - [self waitForMapViewToBeRendered]; + [self.style insertLayer:layer atIndex:0]; + [self waitForMapViewToBeRendered]; - [self.style removeLayer:layer]; - [self waitForMapViewToBeRendered]; + [self.style removeLayer:layer]; + [self waitForMapViewToBeRendered]; } - (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { - __weak id weakLayer; - @autoreleasepool { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [self.style insertLayer:layer atIndex:0]; - layer = nil; + __weak id weakLayer; + @autoreleasepool { + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [self.style insertLayer:layer atIndex:0]; + layer = nil; - [self waitForMapViewToBeRendered]; - [self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]]; - } - - MLNStyleLayer *layer2 = weakLayer; - - XCTAssertNotNil(weakLayer); [self waitForMapViewToBeRendered]; + [self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]]; + } - layer2 = nil; - XCTAssertNil(weakLayer); + MLNStyleLayer *layer2 = weakLayer; + + XCTAssertNotNil(weakLayer); + [self waitForMapViewToBeRendered]; + + layer2 = nil; + XCTAssertNil(weakLayer); } - (void)testOpenGLLayerDoesNotLeakWhenStyleChanged { - __weak MLNCustomStyleLayer *weakLayer; + __weak MLNCustomStyleLayer *weakLayer; - @autoreleasepool { - { - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [self.style insertLayer:layer atIndex:0]; - layer = nil; - } + @autoreleasepool { + { + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [self.style insertLayer:layer atIndex:0]; + layer = nil; } + } - XCTAssertNotNil(weakLayer); + XCTAssertNotNil(weakLayer); - [self waitForMapViewToBeRendered]; + [self waitForMapViewToBeRendered]; - MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; + MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self.mapView setStyleURL:styleURL]; - [self waitForExpectations:@[self.styleLoadingExpectation] timeout:10]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.styleLoadingExpectation = + [self expectationWithDescription:@"Map view should finish loading style."]; + [self.mapView setStyleURL:styleURL]; + [self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:10]; - // At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed - XCTAssert(weakLayer == layer2); - XCTAssertNotNil(weakLayer); + // At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been + // NULLed + XCTAssert(weakLayer == layer2); + XCTAssertNotNil(weakLayer); - // Asking the style for the layer should return nil - MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; - XCTAssertNil(layer3); + // Asking the style for the layer should return nil + MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; + XCTAssertNil(layer3); } - - (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs { - __weak id weakLayer; + __weak id weakLayer; - @autoreleasepool { + @autoreleasepool { + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds + styleURL:styleURL]; + mapView2.delegate = self; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; - mapView2.delegate = self; + XCTAssertNil(mapView2.style); - XCTAssertNil(mapView2.style); + self.styleLoadingExpectation = + [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:10 handler:nil]; - self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [mapView2.style insertLayer:layer atIndex:0]; + layer = nil; - MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [mapView2.style insertLayer:layer atIndex:0]; - layer = nil; - - [self waitForMapViewToBeRendered]; - } - XCTAssertNil(weakLayer); + [self waitForMapViewToBeRendered]; + } + XCTAssertNil(weakLayer); } - (void)testMLNMapViewImplHasCorrectSize { - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self.mapView setStyleURL:styleURL]; - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO]; - [self waitForExpectations:@[self.styleLoadingExpectation] timeout:1]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.styleLoadingExpectation = + [self expectationWithDescription:@"Map view should finish loading style."]; + [self.mapView setStyleURL:styleURL]; + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO]; + [self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:1]; - MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl]; - CGFloat scaleFactor = [UIScreen mainScreen].scale; - mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); - mbgl::Size viewSize = { - static_cast(self.mapView.bounds.size.width * scaleFactor), - static_cast(self.mapView.bounds.size.height * scaleFactor) - }; + MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl]; + CGFloat scaleFactor = [UIScreen mainScreen].scale; + mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); + mbgl::Size viewSize = {static_cast(self.mapView.bounds.size.width * scaleFactor), + static_cast(self.mapView.bounds.size.height * scaleFactor)}; - // Test that mapView and default renderable have the same size. - XCTAssertTrue(renderableSize == viewSize); + // Test that mapView and default renderable have the same size. + XCTAssertTrue(renderableSize == viewSize); - CLLocationCoordinate2D coordinates[] = { - CLLocationCoordinate2DMake(9.6315313, 52.4133574), - CLLocationCoordinate2DMake(24.9410248, 60.1733244)}; + CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(9.6315313, 52.4133574), + CLLocationCoordinate2DMake(24.9410248, 60.1733244)}; - MLNPointCollectionFeature *points = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0])]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap" shape:points options:nil]; - [self.style addSource:source]; + MLNPointCollectionFeature *points = [MLNPointCollectionFeature + pointCollectionWithCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0])]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap" + shape:points + options:nil]; + [self.style addSource:source]; - MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; - [self.style addLayer:heatmapLayer]; + MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" + source:source]; + [self.style addLayer:heatmapLayer]; - // Test that heatmap layer can create a texture and be successfully rendered. - [self waitForMapViewToBeRendered]; + // Test that heatmap layer can create a texture and be successfully rendered. + [self waitForMapViewToBeRendered]; - // Resize frame of the view. - [self.mapView setFrame: CGRect{self.mapView.bounds.origin, {256, 256}}]; + // Resize frame of the view. + [self.mapView setFrame:CGRect{self.mapView.bounds.origin, {256, 256}}]; - // Force sync re-layout. - [self.mapView layoutIfNeeded]; + // Force sync re-layout. + [self.mapView layoutIfNeeded]; - // Test that mapView and default renderable have the same size after re-layout. - renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); - viewSize = { static_cast(self.mapView.bounds.size.width * scaleFactor), - static_cast(self.mapView.bounds.size.height * scaleFactor) }; - XCTAssertTrue(renderableSize == viewSize); - [self waitForMapViewToBeRendered]; + // Test that mapView and default renderable have the same size after re-layout. + renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); + viewSize = {static_cast(self.mapView.bounds.size.width * scaleFactor), + static_cast(self.mapView.bounds.size.height * scaleFactor)}; + XCTAssertTrue(renderableSize == viewSize); + [self waitForMapViewToBeRendered]; } @end diff --git a/platform/ios/Integration_Tests/MLNNetworkConfigurationIntegrationTests.mm b/platform/ios/Integration_Tests/MLNNetworkConfigurationIntegrationTests.mm index 94fb2b86e..88732a8ff 100644 --- a/platform/ios/Integration_Tests/MLNNetworkConfigurationIntegrationTests.mm +++ b/platform/ios/Integration_Tests/MLNNetworkConfigurationIntegrationTests.mm @@ -1,314 +1,327 @@ #import @import Mapbox; +#import "MLNFoundation_Private.h" #import "MLNMapViewIntegrationTest.h" #import "MLNNetworkConfiguration_Private.h" #import "MLNOfflineStorage_Private.h" -#import "MLNFoundation_Private.h" @interface MLNNetworkConfiguration (Testing) + (void)testing_clearNativeNetworkManagerDelegate; + (id)testing_nativeNetworkManagerDelegate; @end -@interface MLNNetworkConfigurationTestDelegate: NSObject -@property (nonatomic) NSURLSession *(^handler)(); +@interface MLNNetworkConfigurationTestDelegate : NSObject +@property (nonatomic) NSURLSession * (^handler)(); @end -@interface MLNNetworkConfigurationSessionDelegate: NSObject +@interface MLNNetworkConfigurationSessionDelegate : NSObject @property (nonatomic) dispatch_block_t authHandler; @end -@interface MLNNetworkConfigurationSessionDataDelegate: NSObject +@interface MLNNetworkConfigurationSessionDataDelegate : NSObject @property (nonatomic) void (^dataHandler)(NSURLSessionDataTask *, NSData *); @end - @interface MLNNetworkConfigurationIntegrationTests : MLNIntegrationTestCase @end #define ASSERT_NATIVE_DELEGATE_IS_NIL() \ - XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) + XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) #define ASSERT_NATIVE_DELEGATE_IS_NOT_NIL() \ - XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) + XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) // NOTE: These tests are currently assumed to run in this specific order. @implementation MLNNetworkConfigurationIntegrationTests - (void)setUp { - [super setUp]; + [super setUp]; - // Reset before each test - [MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate]; + // Reset before each test + [MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate]; } -- (void)test0_NativeNetworkManagerDelegateIsSet -{ - ASSERT_NATIVE_DELEGATE_IS_NIL(); - MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init]; - [config resetNativeNetworkManagerDelegate]; +- (void)test0_NativeNetworkManagerDelegateIsSet { + ASSERT_NATIVE_DELEGATE_IS_NIL(); + MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init]; + [config resetNativeNetworkManagerDelegate]; - id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; + id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; - id manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); - XCTAssertNotNil(manager); + id manager = + MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); + XCTAssertNotNil(manager); - // Expected properties - XCTAssertNotNil([manager sessionConfiguration]); + // Expected properties + XCTAssertNotNil([manager sessionConfiguration]); - [MLNNetworkConfiguration sharedManager]; - id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; - XCTAssert(delegate != delegate2); + [MLNNetworkConfiguration sharedManager]; + id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; + XCTAssert(delegate != delegate2); } -- (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager -{ - ASSERT_NATIVE_DELEGATE_IS_NIL(); +- (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager { + ASSERT_NATIVE_DELEGATE_IS_NIL(); - // Just calling the shared manager is also sufficient (even though, it's a - // singleton and created with a dispatch_once, the delegate is re-set for - // each call. - [MLNNetworkConfiguration sharedManager]; - ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); + // Just calling the shared manager is also sufficient (even though, it's a + // singleton and created with a dispatch_once, the delegate is re-set for + // each call. + [MLNNetworkConfiguration sharedManager]; + ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); - id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; - id manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); - XCTAssertNotNil(manager); + id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; + id manager = + MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); + XCTAssertNotNil(manager); - // Expected properties - XCTAssertNotNil([manager sessionConfiguration]); + // Expected properties + XCTAssertNotNil([manager sessionConfiguration]); } -- (void)test2_NativeNetworkManagerDelegateIsSet -{ - ASSERT_NATIVE_DELEGATE_IS_NIL(); - [MLNNetworkConfiguration sharedManager]; - id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; +- (void)test2_NativeNetworkManagerDelegateIsSet { + ASSERT_NATIVE_DELEGATE_IS_NIL(); + [MLNNetworkConfiguration sharedManager]; + id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; - [[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate]; - id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; - XCTAssert(delegate == delegate2); + [[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate]; + id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; + XCTAssert(delegate == delegate2); } -- (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage -{ - ASSERT_NATIVE_DELEGATE_IS_NIL(); +- (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage { + ASSERT_NATIVE_DELEGATE_IS_NIL(); - // Similar to `[MLNNetworkConfiguration sharedManager]`, - // `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate. - [MLNOfflineStorage sharedOfflineStorage]; - ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); + // Similar to `[MLNNetworkConfiguration sharedManager]`, + // `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate. + [MLNOfflineStorage sharedOfflineStorage]; + ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); } -- (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime -{ - // Testing a second time... - ASSERT_NATIVE_DELEGATE_IS_NIL(); - [MLNOfflineStorage sharedOfflineStorage]; - ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); +- (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime { + // Testing a second time... + ASSERT_NATIVE_DELEGATE_IS_NIL(); + [MLNOfflineStorage sharedOfflineStorage]; + ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); } -- (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit -{ - ASSERT_NATIVE_DELEGATE_IS_NIL(); - (void)[[MLNMapView alloc] init]; - ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); +- (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit { + ASSERT_NATIVE_DELEGATE_IS_NIL(); + (void)[[MLNMapView alloc] init]; + ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); } -- (void)testNetworkConfigurationDelegateIsNil -{ - MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; - XCTAssertNil(manager.delegate); +- (void)testNetworkConfigurationDelegateIsNil { + MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; + XCTAssertNil(manager.delegate); } -- (void)internalTestNetworkConfigurationWithSession:(NSURLSession*)session shouldDownload:(BOOL)shouldDownload { +- (void)internalTestNetworkConfigurationWithSession:(NSURLSession *)session + shouldDownload:(BOOL)shouldDownload { + __block BOOL didCallSessionDelegate = NO; + __block BOOL isMainThread = YES; - __block BOOL didCallSessionDelegate = NO; - __block BOOL isMainThread = YES; - - // Setup delegate object that provides a NSURLSession - MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; - MLNNetworkConfigurationTestDelegate *delegate = [[MLNNetworkConfigurationTestDelegate alloc] init]; - delegate.handler = ^{ - NSURLSession *internalSession; - @synchronized (self) { - didCallSessionDelegate = YES; - isMainThread = [NSThread isMainThread]; - internalSession = session; - } - return internalSession; - }; - - manager.delegate = delegate; - - // The following is modified/taken from MLNOfflineStorageTests as we do not yet have a - // good mechanism to test FileSource (in this SDK) - // - // Want to ensure we download from the network; nuclear option - { - XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."]; - [[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) { - XCTAssertNil(error); - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:10 handler:nil]; + // Setup delegate object that provides a NSURLSession + MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; + MLNNetworkConfigurationTestDelegate *delegate = + [[MLNNetworkConfigurationTestDelegate alloc] init]; + delegate.handler = ^{ + NSURLSession *internalSession; + @synchronized(self) { + didCallSessionDelegate = YES; + isMainThread = [NSThread isMainThread]; + internalSession = session; } + return internalSession; + }; - // Boston - MLNCoordinateBounds bounds = { - { .latitude = 42.360, .longitude = -71.056 }, - { .latitude = 42.358, .longitude = -71.053 }, - }; - NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; - MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL - bounds:bounds - fromZoomLevel:20 - toZoomLevel:20]; + manager.delegate = delegate; - NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ - @"Name": @"Faneuil Hall" - }]; - - __block MLNOfflinePack *pack = nil; - - // Add pack - { - XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; - - [[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region - withContext:context - completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { - pack = completionHandlerPack; - [additionCompletionHandlerExpectation fulfill]; + // The following is modified/taken from MLNOfflineStorageTests as we do not yet have a + // good mechanism to test FileSource (in this SDK) + // + // Want to ensure we download from the network; nuclear option + { + XCTestExpectation *expectation = + [self expectationWithDescription:@"Expect database to be reset without an error."]; + [[MLNOfflineStorage sharedOfflineStorage] + resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) { + XCTAssertNil(error); + [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; - } + [self waitForExpectationsWithTimeout:10 handler:nil]; + } - XCTAssert(pack.state == MLNOfflinePackStateInactive); + // Boston + MLNCoordinateBounds bounds = { + {.latitude = 42.360, .longitude = -71.056}, + {.latitude = 42.358, .longitude = -71.053}, + }; + NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; + MLNTilePyramidOfflineRegion *region = + [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL + bounds:bounds + fromZoomLevel:20 + toZoomLevel:20]; - // Download - { - XCTestExpectation *expectation = [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { - return pack.state == MLNOfflinePackStateComplete; + NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"Name" : @"Faneuil Hall"}]; + + __block MLNOfflinePack *pack = nil; + + // Add pack + { + XCTestExpectation *additionCompletionHandlerExpectation = + [self expectationWithDescription:@"add pack completion handler"]; + + [[MLNOfflineStorage sharedOfflineStorage] + addPackForRegion:region + withContext:context + completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack, + NSError *_Nullable error) { + pack = completionHandlerPack; + [additionCompletionHandlerExpectation fulfill]; }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; + } - expectation.inverted = !shouldDownload; - [pack resume]; + XCTAssert(pack.state == MLNOfflinePackStateInactive); - [self waitForExpectations:@[expectation] timeout:15]; - } + // Download + { + XCTestExpectation *expectation = + [self expectationForNotification:MLNOfflinePackProgressChangedNotification + object:pack + handler:^BOOL(NSNotification *_Nonnull notification) { + return pack.state == MLNOfflinePackStateComplete; + }]; - XCTAssert(didCallSessionDelegate); - XCTAssertFalse(isMainThread); + expectation.inverted = !shouldDownload; + [pack resume]; - // Remove pack, so we don't affect other tests - { - XCTestExpectation *expectation = [self expectationWithDescription:@"remove pack completion handler"]; - [[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { - [expectation fulfill]; - }]; - [self waitForExpectationsWithTimeout:5 handler:nil]; - } + [self waitForExpectations:@[ expectation ] timeout:15]; + } + + XCTAssert(didCallSessionDelegate); + XCTAssertFalse(isMainThread); + + // Remove pack, so we don't affect other tests + { + XCTestExpectation *expectation = + [self expectationWithDescription:@"remove pack completion handler"]; + [[MLNOfflineStorage sharedOfflineStorage] removePack:pack + withCompletionHandler:^(NSError *_Nullable error) { + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:5 handler:nil]; + } } - (void)testNetworkConfigurationWithSharedSessionLOCKED { - NSURLSession *session = [NSURLSession sharedSession]; - [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; + NSURLSession *session = [NSURLSession sharedSession]; + [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; } - (void)testNetworkConfigurationWithBackgroundSessionConfiguration { - // Background session configurations are NOT supported, we expect this test - // trigger an exception - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; + // Background session configurations are NOT supported, we expect this test + // trigger an exception + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration + backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; - XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]); + XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]); - // We cannot do this yet, as it requires intecepting the exception in gl-native - // It makes more sense to support background configs (requiring delegation - // rather than blocks in gl-native) - // [self internalTestNetworkConfigurationWithSession:session], NSException, NSInvalidArgumentException); + // We cannot do this yet, as it requires intecepting the exception in gl-native + // It makes more sense to support background configs (requiring delegation + // rather than blocks in gl-native) + // [self internalTestNetworkConfigurationWithSession:session], NSException, + // NSInvalidArgumentException); } - (void)testNetworkConfigurationWithDefaultSessionConfigurationLOCKED { - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; - [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; + NSURLSessionConfiguration *sessionConfig = + [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; + [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; } - (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED { - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; - [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; + NSURLSessionConfiguration *sessionConfig = + [NSURLSessionConfiguration ephemeralSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; + [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; } - (void)testNetworkConfigurationWithSessionConfigurationWithDelegateLOCKED { - __block BOOL didCallAuthChallenge = NO; - __block BOOL isMainThread = YES; + __block BOOL didCallAuthChallenge = NO; + __block BOOL isMainThread = YES; - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - MLNNetworkConfigurationSessionDelegate *delegate = [[MLNNetworkConfigurationSessionDelegate alloc] init]; - delegate.authHandler = ^{ - @synchronized (self) { - didCallAuthChallenge = YES; - isMainThread = [NSThread isMainThread]; - } - }; + NSURLSessionConfiguration *sessionConfig = + [NSURLSessionConfiguration defaultSessionConfiguration]; + MLNNetworkConfigurationSessionDelegate *delegate = + [[MLNNetworkConfigurationSessionDelegate alloc] init]; + delegate.authHandler = ^{ + @synchronized(self) { + didCallAuthChallenge = YES; + isMainThread = [NSThread isMainThread]; + } + }; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig - delegate:delegate - delegateQueue:nil]; - [self internalTestNetworkConfigurationWithSession:session shouldDownload:NO]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig + delegate:delegate + delegateQueue:nil]; + [self internalTestNetworkConfigurationWithSession:session shouldDownload:NO]; - [session finishTasksAndInvalidate]; + [session finishTasksAndInvalidate]; - XCTAssertFalse(isMainThread); - XCTAssert(didCallAuthChallenge); + XCTAssertFalse(isMainThread); + XCTAssert(didCallAuthChallenge); } - (void)testFailureForNetworkConfigurationWithSessionWithDataDelegateLOCKED { - __block BOOL didCallReceiveData = NO; + __block BOOL didCallReceiveData = NO; - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - MLNNetworkConfigurationSessionDataDelegate *delegate = [[MLNNetworkConfigurationSessionDataDelegate alloc] init]; - delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) { - @synchronized (self) { - didCallReceiveData = YES; - } - }; - - // NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate - // are NOT supported. - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig - delegate:delegate - delegateQueue:nil]; - - BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)]; - XCTAssert(conforms); -#ifdef DEBUG - if (conforms) { - NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported"); + NSURLSessionConfiguration *sessionConfig = + [NSURLSessionConfiguration defaultSessionConfiguration]; + MLNNetworkConfigurationSessionDataDelegate *delegate = + [[MLNNetworkConfigurationSessionDataDelegate alloc] init]; + delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) { + @synchronized(self) { + didCallReceiveData = YES; } -#else - [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; -#endif - [session finishTasksAndInvalidate]; + }; - XCTAssertFalse(didCallReceiveData); + // NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate + // are NOT supported. + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig + delegate:delegate + delegateQueue:nil]; + + BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)]; + XCTAssert(conforms); +#ifdef DEBUG + if (conforms) { + NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported"); + } +#else + [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; +#endif + [session finishTasksAndInvalidate]; + + XCTAssertFalse(didCallReceiveData); } - (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED { - // Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration`` - NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - sessionConfig.HTTPAdditionalHeaders = @{ @"testing" : @YES }; - sessionConfig.HTTPMaximumConnectionsPerHost = 1; - sessionConfig.timeoutIntervalForResource = 30; - sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - sessionConfig.URLCache = nil; + // Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration`` + NSURLSessionConfiguration *sessionConfig = + [NSURLSessionConfiguration defaultSessionConfiguration]; + sessionConfig.HTTPAdditionalHeaders = @{@"testing" : @YES}; + sessionConfig.HTTPMaximumConnectionsPerHost = 1; + sessionConfig.timeoutIntervalForResource = 30; + sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + sessionConfig.URLCache = nil; - NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; - [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; + [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; } @end @@ -317,33 +330,38 @@ @implementation MLNNetworkConfigurationTestDelegate - (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration { - if (self.handler) { - return self.handler(); - } + if (self.handler) { + return self.handler(); + } - return nil; + return nil; } @end // MARK: - NSURLSession delegate @implementation MLNNetworkConfigurationSessionDelegate -- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { - if (self.authHandler) { - self.authHandler(); - } +- (void)URLSession:(NSURLSession *)session + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential *_Nullable credential))completionHandler { + if (self.authHandler) { + self.authHandler(); + } - // Cancel the challenge - completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + // Cancel the challenge + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); } @end // MARK: - NSURLSession data delegate @implementation MLNNetworkConfigurationSessionDataDelegate -- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - if (self.dataHandler) { - self.dataHandler(dataTask, data); - } +- (void)URLSession:(NSURLSession *)session + dataTask:(NSURLSessionDataTask *)dataTask + didReceiveData:(NSData *)data { + if (self.dataHandler) { + self.dataHandler(dataTask, data); + } } @end diff --git a/platform/ios/app/MBXViewController.mm b/platform/ios/app/MBXViewController.mm index ef4285bce..c14d843ca 100644 --- a/platform/ios/app/MBXViewController.mm +++ b/platform/ios/app/MBXViewController.mm @@ -2,15 +2,15 @@ #import "MBXViewController.h" +#import "MBXAnnotationView.h" #import "MBXAppDelegate.h" #import "MBXCustomCalloutView.h" -#import "MBXOfflinePacksTableViewController.h" -#import "MBXAnnotationView.h" -#import "MBXUserLocationAnnotationView.h" #import "MBXEmbeddedMapViewController.h" +#import "MBXOfflinePacksTableViewController.h" #import "MBXOrnamentsViewController.h" -#import "MBXStateManager.h" #import "MBXState.h" +#import "MBXStateManager.h" +#import "MBXUserLocationAnnotationView.h" #import "MLNSettings.h" #import "MLNMapView_Private.h" @@ -19,186 +19,182 @@ #import "ExampleCustomDrawableStyleLayer.h" +#import #import "MBXFrameTimeGraphView.h" #import "MLNMapView_Experimental.h" -#import // Plug In Examples +#import "MLNPluginStyleLayer.h" #import "PluginLayerExample.h" #import "PluginLayerExampleMetalRendering.h" -#import "MLNPluginStyleLayer.h" static const CLLocationCoordinate2D WorldTourDestinations[] = { - { .latitude = 38.8999418, .longitude = -77.033996 }, - { .latitude = 37.7884307, .longitude = -122.3998631 }, - { .latitude = 52.5003103, .longitude = 13.4197763 }, - { .latitude = 60.1712627, .longitude = 24.9378866 }, - { .latitude = 53.8948782, .longitude = 27.5558476 }, + {.latitude = 38.8999418, .longitude = -77.033996}, + {.latitude = 37.7884307, .longitude = -122.3998631}, + {.latitude = 52.5003103, .longitude = 13.4197763}, + {.latitude = 60.1712627, .longitude = 24.9378866}, + {.latitude = 53.8948782, .longitude = 27.5558476}, }; static const MLNCoordinateBounds colorado = { - .sw = { .latitude = 36.986207, .longitude = -109.049896}, - .ne = { .latitude = 40.989329, .longitude = -102.062592}, + .sw = {.latitude = 36.986207, .longitude = -109.049896}, + .ne = {.latitude = 40.989329, .longitude = -102.062592}, }; -static NSString * const MBXViewControllerAnnotationViewReuseIdentifer = @"MBXViewControllerAnnotationViewReuseIdentifer"; +static NSString *const MBXViewControllerAnnotationViewReuseIdentifer = + @"MBXViewControllerAnnotationViewReuseIdentifer"; typedef NS_ENUM(NSInteger, MBXSettingsSections) { - MBXSettingsDebugTools = 0, - MBXSettingsAnnotations, - MBXSettingsRuntimeStyling, - MBXSettingsMiscellaneous, + MBXSettingsDebugTools = 0, + MBXSettingsAnnotations, + MBXSettingsRuntimeStyling, + MBXSettingsMiscellaneous, }; typedef NS_ENUM(NSInteger, MBXSettingsDebugToolsRows) { - MBXSettingsDebugToolsResetPosition = 0, - MBXSettingsDebugToolsTileBoundaries, - MBXSettingsDebugToolsTileInfo, - MBXSettingsDebugToolsTimestamps, - MBXSettingsDebugToolsCollisionBoxes, - MBXSettingsDebugToolsOverdrawVisualization, - MBXSettingsDebugToolsShowZoomLevel, - MBXSettingsDebugToolsShowFrameTimeGraph, - MBXSettingsDebugToolsShowReuseQueueStats, - MBXSettingsDebugToolsShowRenderingStats + MBXSettingsDebugToolsResetPosition = 0, + MBXSettingsDebugToolsTileBoundaries, + MBXSettingsDebugToolsTileInfo, + MBXSettingsDebugToolsTimestamps, + MBXSettingsDebugToolsCollisionBoxes, + MBXSettingsDebugToolsOverdrawVisualization, + MBXSettingsDebugToolsShowZoomLevel, + MBXSettingsDebugToolsShowFrameTimeGraph, + MBXSettingsDebugToolsShowReuseQueueStats, + MBXSettingsDebugToolsShowRenderingStats }; typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { - MBXSettingsAnnotations100Views = 0, - MBXSettingsAnnotations1000Views, - MBXSettingsAnnotations10000Views, - MBXSettingsAnnotations100Sprites, - MBXSettingsAnnotations1000Sprites, - MBXSettingsAnnotations10000Sprites, - MBXSettingsAnnotationAnimation, - MBXSettingsAnnotationsTestShapes, - MBXSettingsAnnotationsManyTestShapes, - MBXSettingsAnnotationsCustomCallout, - MBXSettingsAnnotationsQueryAnnotations, - MBXSettingsAnnotationsCustomUserDot, - MBXSettingsAnnotationsRemoveAnnotations, - MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation, - MBXSettingsAnnotationCenterSelectedAnnotation, - MBXSettingsAnnotationAddVisibleAreaPolyline + MBXSettingsAnnotations100Views = 0, + MBXSettingsAnnotations1000Views, + MBXSettingsAnnotations10000Views, + MBXSettingsAnnotations100Sprites, + MBXSettingsAnnotations1000Sprites, + MBXSettingsAnnotations10000Sprites, + MBXSettingsAnnotationAnimation, + MBXSettingsAnnotationsTestShapes, + MBXSettingsAnnotationsManyTestShapes, + MBXSettingsAnnotationsCustomCallout, + MBXSettingsAnnotationsQueryAnnotations, + MBXSettingsAnnotationsCustomUserDot, + MBXSettingsAnnotationsRemoveAnnotations, + MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation, + MBXSettingsAnnotationCenterSelectedAnnotation, + MBXSettingsAnnotationAddVisibleAreaPolyline }; typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { - MBXSettingsRuntimeStylingBuildingExtrusions = 0, - MBXSettingsRuntimeStylingWater, - MBXSettingsRuntimeStylingRoads, - MBXSettingsRuntimeStylingRaster, - MBXSettingsRuntimeStylingShape, - MBXSettingsRuntimeStylingSymbols, - MBXSettingsRuntimeStylingBuildings, - MBXSettingsRuntimeStylingFerry, - MBXSettingsRuntimeStylingParks, - MBXSettingsRuntimeStylingFilteredFill, - MBXSettingsRuntimeStylingFilteredLines, - MBXSettingsRuntimeStylingNumericFilteredFill, - MBXSettingsRuntimeStylingStyleQuery, - MBXSettingsRuntimeStylingFeatureSource, - MBXSettingsRuntimeStylingPointCollection, - MBXSettingsRuntimeStylingUpdateShapeSourceData, - MBXSettingsRuntimeStylingUpdateShapeSourceURL, - MBXSettingsRuntimeStylingUpdateShapeSourceFeatures, - MBXSettingsRuntimeStylingVectorTileSource, - MBXSettingsRuntimeStylingRasterTileSource, - MBXSettingsRuntimeStylingImageSource, - MBXSettingsRuntimeStylingRouteLine, - MBXSettingsRuntimeStylingAddCustomTriangleLayer, - MBXSettingsRuntimeStylingDDSPolygon, - MBXSettingsRuntimeStylingCustomLatLonGrid, - MBXSettingsRuntimeStylingLineGradient, - MBXSettingsRuntimeStylingCustomDrawableLayer, - MBXSettingsRuntimeStylingAddFoursquarePOIsPMTiles, - MBXSettingsRuntimeStylingHeatmapLayer + MBXSettingsRuntimeStylingBuildingExtrusions = 0, + MBXSettingsRuntimeStylingWater, + MBXSettingsRuntimeStylingRoads, + MBXSettingsRuntimeStylingRaster, + MBXSettingsRuntimeStylingShape, + MBXSettingsRuntimeStylingSymbols, + MBXSettingsRuntimeStylingBuildings, + MBXSettingsRuntimeStylingFerry, + MBXSettingsRuntimeStylingParks, + MBXSettingsRuntimeStylingFilteredFill, + MBXSettingsRuntimeStylingFilteredLines, + MBXSettingsRuntimeStylingNumericFilteredFill, + MBXSettingsRuntimeStylingStyleQuery, + MBXSettingsRuntimeStylingFeatureSource, + MBXSettingsRuntimeStylingPointCollection, + MBXSettingsRuntimeStylingUpdateShapeSourceData, + MBXSettingsRuntimeStylingUpdateShapeSourceURL, + MBXSettingsRuntimeStylingUpdateShapeSourceFeatures, + MBXSettingsRuntimeStylingVectorTileSource, + MBXSettingsRuntimeStylingRasterTileSource, + MBXSettingsRuntimeStylingImageSource, + MBXSettingsRuntimeStylingRouteLine, + MBXSettingsRuntimeStylingAddCustomTriangleLayer, + MBXSettingsRuntimeStylingDDSPolygon, + MBXSettingsRuntimeStylingCustomLatLonGrid, + MBXSettingsRuntimeStylingLineGradient, + MBXSettingsRuntimeStylingCustomDrawableLayer, + MBXSettingsRuntimeStylingAddFoursquarePOIsPMTiles, + MBXSettingsRuntimeStylingHeatmapLayer }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - MBXSettingsMiscellaneousWorldTour, - MBXSettingsMiscellaneousRandomTour, - MBXSettingsMiscellaneousScrollView, - MBXSettingsMiscellaneousToggleTwoMaps, - MBXSettingsMiscellaneousLocalizeLabels, - MBXSettingsMiscellaneousShowSnapshots, - MBXSettingsMiscellaneousMissingIcon, - MBXSettingsMiscellaneousShouldLimitCameraChanges, - MBXSettingsMiscellaneousSetContentInsets, - MBXSettingsMiscellaneousShowCustomLocationManager, - MBXSettingsMiscellaneousOrnamentsPlacement, - MBXSettingsMiscellaneousLatLngBoundsWithPadding, - MBXSettingsMiscellaneousCycleTileLOD, - MBXSettingsMiscellaneousPrintLogFile, - MBXSettingsMiscellaneousDeleteLogFile + MBXSettingsMiscellaneousWorldTour, + MBXSettingsMiscellaneousRandomTour, + MBXSettingsMiscellaneousScrollView, + MBXSettingsMiscellaneousToggleTwoMaps, + MBXSettingsMiscellaneousLocalizeLabels, + MBXSettingsMiscellaneousShowSnapshots, + MBXSettingsMiscellaneousMissingIcon, + MBXSettingsMiscellaneousShouldLimitCameraChanges, + MBXSettingsMiscellaneousSetContentInsets, + MBXSettingsMiscellaneousShowCustomLocationManager, + MBXSettingsMiscellaneousOrnamentsPlacement, + MBXSettingsMiscellaneousLatLngBoundsWithPadding, + MBXSettingsMiscellaneousCycleTileLOD, + MBXSettingsMiscellaneousPrintLogFile, + MBXSettingsMiscellaneousDeleteLogFile }; typedef NS_ENUM(NSInteger, MBXTileLodMode) { - MBXTileLodModeNoLod = 0, - MBXTileLodModeDefault, - MBXTileLodModeReduced, - MBXTileLodModeAggressive, - MBXTileLodModeCount + MBXTileLodModeNoLod = 0, + MBXTileLodModeDefault, + MBXTileLodModeReduced, + MBXTileLodModeAggressive, + MBXTileLodModeCount }; // Utility methods -CLLocationCoordinate2D coordinateCentered(CLLocationCoordinate2D origin, CLLocationDegrees bearing, CLLocationDistance distance) { +CLLocationCoordinate2D coordinateCentered(CLLocationCoordinate2D origin, CLLocationDegrees bearing, + CLLocationDistance distance) { + // Convert to radians + double multiplier = M_PI / 180.0; + double sourceLatitude = multiplier * origin.latitude; + double sourceLongitude = multiplier * origin.longitude; + bearing *= multiplier; + distance /= 6378137.0; - // Convert to radians - double multiplier = M_PI / 180.0; - double sourceLatitude = multiplier * origin.latitude; - double sourceLongitude = multiplier * origin.longitude; - bearing *= multiplier; - distance /= 6378137.0; + // Pulled from MLNRadianCoordinateAtDistanceFacingDirection: + double latitude = asin((sin(sourceLatitude) * cos(distance)) + + (cos(sourceLatitude) * sin(distance) * cos(bearing))); - // Pulled from MLNRadianCoordinateAtDistanceFacingDirection: - double latitude = asin((sin(sourceLatitude) * cos(distance)) + - (cos(sourceLatitude) * sin(distance) * cos(bearing))); + double longitude = sourceLongitude + atan2((sin(bearing) * sin(distance) * cos(sourceLatitude)), + cos(distance) - (sin(sourceLatitude) * sin(latitude))); - double longitude = sourceLongitude + atan2((sin(bearing) * sin(distance) * cos(sourceLatitude)), - cos(distance) - (sin(sourceLatitude) * sin(latitude))); - - CLLocationCoordinate2D result; - result.latitude = fmin(85.0, fmax(-85.0, (latitude / multiplier))); - result.longitude = longitude / multiplier; - return result; + CLLocationCoordinate2D result; + result.latitude = fmin(85.0, fmax(-85.0, (latitude / multiplier))); + result.longitude = longitude / multiplier; + return result; } CLLocationCoordinate2D randomWorldCoordinate(void) { + static const struct { + CLLocationCoordinate2D coordinate; + CLLocationDistance radius; + } landmasses[] = { + // Rough land masses + {{38.328531, 94.778736}, 4100000}, // Asia + {{1.477244, 18.138111}, 4100000}, // Africa + {{52.310059, 22.295425}, 2000000}, // Europe + {{42.344216, -96.532700}, 3000000}, // N America + {{-11.537273, -57.035181}, 2220000}, // S America + {{-20.997030, 134.660541}, 2220000}, // Australia - static const struct { - CLLocationCoordinate2D coordinate; - CLLocationDistance radius; - } landmasses[] = { - // Rough land masses - {{ 38.328531, 94.778736 }, 4100000 }, // Asia - {{ 1.477244, 18.138111 }, 4100000 }, // Africa - {{ 52.310059, 22.295425 }, 2000000 }, // Europe - {{ 42.344216, -96.532700 }, 3000000 }, // N America - {{ -11.537273, -57.035181 }, 2220000 }, // S America - {{ -20.997030, 134.660541 }, 2220000 }, // Australia + // A few cities + {{51.504787, -0.106977}, 33000}, // London + {{37.740186, -122.437086}, 8500}, // SF + {{52.509978, 13.406510}, 12000}, // Berlin + {{12.966246, 77.586505}, 19000} // Bengaluru + }; - // A few cities - {{ 51.504787, -0.106977 }, 33000 }, // London - {{ 37.740186, -122.437086 }, 8500 }, // SF - {{ 52.509978, 13.406510 }, 12000 }, // Berlin - {{ 12.966246, 77.586505 }, 19000 } // Bengaluru - }; + NSInteger index = arc4random_uniform(sizeof(landmasses) / sizeof(landmasses[0])); + CLLocationCoordinate2D coordinate = landmasses[index].coordinate; + CLLocationDistance radius = landmasses[index].radius; - NSInteger index = arc4random_uniform(sizeof(landmasses)/sizeof(landmasses[0])); - CLLocationCoordinate2D coordinate = landmasses[index].coordinate; - CLLocationDistance radius = landmasses[index].radius; - - // Now create a world coord - CLLocationDegrees heading = (CLLocationDegrees)arc4random_uniform(360); - CLLocationDistance distance = (CLLocationDistance)arc4random_uniform(radius); - CLLocationCoordinate2D newLocation = coordinateCentered(coordinate, heading, distance); - return newLocation; + // Now create a world coord + CLLocationDegrees heading = (CLLocationDegrees)arc4random_uniform(360); + CLLocationDistance distance = (CLLocationDistance)arc4random_uniform(radius); + CLLocationCoordinate2D newLocation = coordinateCentered(coordinate, heading, distance); + return newLocation; } - - - - @interface MBXDroppedPinAnnotation : MLNPointAnnotation @end @@ -224,14 +220,13 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { MLNMapViewDelegate, MLNComputedShapeSourceDataSource> - @property (nonatomic) IBOutlet MLNMapView *mapView; @property (nonatomic) MBXState *currentState; @property (weak, nonatomic) IBOutlet UIButton *hudLabel; @property (weak, nonatomic) IBOutlet MBXFrameTimeGraphView *frameTimeGraphView; @property (nonatomic) NSInteger styleIndex; -@property (nonatomic) NSMutableArray* styleNames; -@property (nonatomic) NSMutableArray* styleURLs; +@property (nonatomic) NSMutableArray *styleNames; +@property (nonatomic) NSMutableArray *styleURLs; @property (nonatomic) BOOL customUserLocationAnnnotationEnabled; @property (nonatomic, getter=isLocalizingLabels) BOOL localizingLabels; @property (nonatomic) BOOL reuseQueueStatsEnabled; @@ -249,848 +244,868 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { @property (nonatomic) NSDictionary *annotationViewReuseQueueByIdentifier; @end -@implementation MBXViewController -{ - BOOL _isTouringWorld; - BOOL _contentInsetsEnabled; - UIEdgeInsets _originalContentInsets; +@implementation MBXViewController { + BOOL _isTouringWorld; + BOOL _contentInsetsEnabled; + UIEdgeInsets _originalContentInsets; - NSLayoutConstraint* _firstMapLayout; - NSArray* _secondMapLayout; + NSLayoutConstraint *_firstMapLayout; + NSArray *_secondMapLayout; - NSDictionary* _pointFeatures; - NSLock* _loadLock; + NSDictionary *_pointFeatures; + NSLock *_loadLock; - MBXTileLodMode _tileLodMode; + MBXTileLodMode _tileLodMode; } // MARK: - Setup & Teardown - (instancetype)init { - if (self = [super init]) { - _firstMapLayout = nil; - _secondMapLayout = nil; - _pointFeatures = nil; - _loadLock = [NSLock new]; - } - return self; + if (self = [super init]) { + _firstMapLayout = nil; + _secondMapLayout = nil; + _pointFeatures = nil; + _loadLock = [NSLock new]; + } + return self; } // This will add the plug-in layers. This is a demo of how // extensible layers for the style can be added to the map view --(void)addPluginLayers { - - [self.mapView addPluginLayerType:[PluginLayerExample class]]; - [self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]]; - +- (void)addPluginLayers { + [self.mapView addPluginLayerType:[PluginLayerExample class]]; + [self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]]; } -- (void)viewDidLoad -{ - [super viewDidLoad]; +- (void)viewDidLoad { + [super viewDidLoad]; - [self addPluginLayers]; + [self addPluginLayers]; - // Keep track of current map state and debug preferences, - // saving and restoring when the application's state changes. - self.currentState = [MBXStateManager sharedManager].currentState; + // Keep track of current map state and debug preferences, + // saving and restoring when the application's state changes. + self.currentState = [MBXStateManager sharedManager].currentState; - if (!self.currentState) { - // Create a new state with the below default values - self.currentState = [[MBXState alloc] init]; + if (!self.currentState) { + // Create a new state with the below default values + self.currentState = [[MBXState alloc] init]; - self.mapView.showsUserHeadingIndicator = YES; - self.mapView.showsScale = YES; - self.mapView.showsLogoView = YES; - self.mapView.showsCompassView = YES; - self.mapView.showsAttributionButton = YES; - self.zoomLevelOrnamentEnabled = NO; - self.frameTimeGraphEnabled = NO; - } else { - // Revert to the previously saved state - [self restoreMapState:nil]; + self.mapView.showsUserHeadingIndicator = YES; + self.mapView.showsScale = YES; + self.mapView.showsLogoView = YES; + self.mapView.showsCompassView = YES; + self.mapView.showsAttributionButton = YES; + self.zoomLevelOrnamentEnabled = NO; + self.frameTimeGraphEnabled = NO; + } else { + // Revert to the previously saved state + [self restoreMapState:nil]; + } + + [self updateHUD]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(saveCurrentMapState:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(restoreMapState:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(saveCurrentMapState:) + name:UIApplicationWillTerminateNotification + object:nil]; + + self.styleIndex = -1; + [self setStyles]; + [self cycleStyles:self]; + + self.mapView.experimental_enableFrameRateMeasurement = YES; + self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 + weight:UIFontWeightRegular]; + + // Add fall-through single tap gesture recognizer. This will be called when + // the map view's tap recognizers fail. + UITapGestureRecognizer *singleTap = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; + for (UIGestureRecognizer *gesture in self.mapView.gestureRecognizers) { + if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) { + [singleTap requireGestureRecognizerToFail:gesture]; } + } + [self.mapView addGestureRecognizer:singleTap]; - [self updateHUD]; - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCurrentMapState:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreMapState:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCurrentMapState:) name:UIApplicationWillTerminateNotification object:nil]; - - self.styleIndex = -1; - [self setStyles]; - [self cycleStyles:self]; - - self.mapView.experimental_enableFrameRateMeasurement = YES; - self.hudLabel.titleLabel.font = [UIFont monospacedDigitSystemFontOfSize:10 weight:UIFontWeightRegular]; - - // Add fall-through single tap gesture recognizer. This will be called when - // the map view's tap recognizers fail. - UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)]; - for (UIGestureRecognizer *gesture in self.mapView.gestureRecognizers) { - if ([gesture isKindOfClass:[UITapGestureRecognizer class]]) { - [singleTap requireGestureRecognizerToFail:gesture]; - } - } - [self.mapView addGestureRecognizer:singleTap]; - - // Display a secondary map on any connected external display. - // https://developer.apple.com/documentation/uikit/windows_and_screens/displaying_content_on_a_connected_screen?language=objc - self.helperWindows = [NSMutableArray array]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidConnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - UIScreen *helperScreen = note.object; - UIWindow *helperWindow = [[UIWindow alloc] initWithFrame:helperScreen.bounds]; - helperWindow.screen = helperScreen; - UIViewController *helperViewController = [[UIViewController alloc] init]; - MLNMapView *helperMapView = [[MLNMapView alloc] initWithFrame:helperWindow.bounds styleURL:[[MLNStyle predefinedStyle:@"Hybrid"] url]]; - helperMapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - helperMapView.camera = self.mapView.camera; - helperMapView.compassView.hidden = YES; - helperViewController.view = helperMapView; - helperWindow.rootViewController = helperViewController; - helperWindow.hidden = NO; - [self.helperWindows addObject:helperWindow]; - }]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { - UIScreen *helperScreen = note.object; - for (UIWindow *window in self.helperWindows) { - if (window.screen == helperScreen) { - [self.helperWindows removeObject:window]; - } - } - }]; + // Display a secondary map on any connected external display. + // https://developer.apple.com/documentation/uikit/windows_and_screens/displaying_content_on_a_connected_screen?language=objc + self.helperWindows = [NSMutableArray array]; + [[NSNotificationCenter defaultCenter] + addObserverForName:UIScreenDidConnectNotification + object:nil + queue:nil + usingBlock:^(NSNotification *_Nonnull note) { + UIScreen *helperScreen = note.object; + UIWindow *helperWindow = [[UIWindow alloc] initWithFrame:helperScreen.bounds]; + helperWindow.screen = helperScreen; + UIViewController *helperViewController = [[UIViewController alloc] init]; + MLNMapView *helperMapView = + [[MLNMapView alloc] initWithFrame:helperWindow.bounds + styleURL:[[MLNStyle predefinedStyle:@"Hybrid"] url]]; + helperMapView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + helperMapView.camera = self.mapView.camera; + helperMapView.compassView.hidden = YES; + helperViewController.view = helperMapView; + helperWindow.rootViewController = helperViewController; + helperWindow.hidden = NO; + [self.helperWindows addObject:helperWindow]; + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenDidDisconnectNotification + object:nil + queue:nil + usingBlock:^(NSNotification *_Nonnull note) { + UIScreen *helperScreen = note.object; + for (UIWindow *window in self.helperWindows) { + if (window.screen == helperScreen) { + [self.helperWindows removeObject:window]; + } + } + }]; } -- (UIInterfaceOrientationMask)supportedInterfaceOrientations -{ - return UIInterfaceOrientationMaskAll; +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return UIInterfaceOrientationMaskAll; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(__unused id)sender { - if ([segue.identifier isEqualToString:@"ShowOfflinePacks"]) { - MBXOfflinePacksTableViewController *controller = [segue destinationViewController]; - controller.mapView = self.mapView; - } + if ([segue.identifier isEqualToString:@"ShowOfflinePacks"]) { + MBXOfflinePacksTableViewController *controller = [segue destinationViewController]; + controller.mapView = self.mapView; + } } -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; } // MARK: - Debugging Interface -- (IBAction)showSettings:(__unused id)sender -{ - self.randomWalk = NO; +- (IBAction)showSettings:(__unused id)sender { + self.randomWalk = NO; - UITableViewController *settingsViewController = [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped]; - settingsViewController.tableView.delegate = self; - settingsViewController.tableView.dataSource = self; - settingsViewController.title = @"Debugging"; - settingsViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(dismissSettings:)]; - UINavigationController *wrapper = [[UINavigationController alloc] initWithRootViewController:settingsViewController]; - wrapper.navigationBar.tintColor = self.navigationController.navigationBar.tintColor; - [self.navigationController presentViewController:wrapper animated:YES completion:nil]; + UITableViewController *settingsViewController = + [[UITableViewController alloc] initWithStyle:UITableViewStyleGrouped]; + settingsViewController.tableView.delegate = self; + settingsViewController.tableView.dataSource = self; + settingsViewController.title = @"Debugging"; + settingsViewController.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone + target:self + action:@selector(dismissSettings:)]; + UINavigationController *wrapper = + [[UINavigationController alloc] initWithRootViewController:settingsViewController]; + wrapper.navigationBar.tintColor = self.navigationController.navigationBar.tintColor; + [self.navigationController presentViewController:wrapper animated:YES completion:nil]; } -- (void)dismissSettings:(__unused id)sender -{ - [self dismissViewControllerAnimated:YES completion:nil]; +- (void)dismissSettings:(__unused id)sender { + [self dismissViewControllerAnimated:YES completion:nil]; } -- (NSArray *)settingsSectionTitles -{ - return @[ - @"Debug Tools", - @"Annotations", - @"Runtime Styling", - @"Miscellaneous" - ]; +- (NSArray *)settingsSectionTitles { + return @[ @"Debug Tools", @"Annotations", @"Runtime Styling", @"Miscellaneous" ]; } -- (NSArray *)settingsTitlesForSection:(NSInteger)section -{ - NSMutableArray *settingsTitles = [NSMutableArray array]; +- (NSArray *)settingsTitlesForSection:(NSInteger)section { + NSMutableArray *settingsTitles = [NSMutableArray array]; - MLNMapDebugMaskOptions debugMask = self.mapView.debugMask; + MLNMapDebugMaskOptions debugMask = self.mapView.debugMask; - switch (section) - { - case MBXSettingsDebugTools: - [settingsTitles addObjectsFromArray:@[ - @"Reset position", - [NSString stringWithFormat:@"%@ tile boundaries", - (debugMask & MLNMapDebugTileBoundariesMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ tile info", - (debugMask & MLNMapDebugTileInfoMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ tile timestamps", - (debugMask & MLNMapDebugTimestampsMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ collision boxes", - (debugMask & MLNMapDebugCollisionBoxesMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ overdraw visualization", - (debugMask & MLNMapDebugOverdrawVisualizationMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ zoom level ornament", (self.zoomLevelOrnamentEnabled ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ frame time graph", (self.frameTimeGraphEnabled ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ reuse queue stats", (self.reuseQueueStatsEnabled ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ rendering stats", (self.renderingStatsEnabled ? @"Hide" :@"Show")] - ]]; - break; - case MBXSettingsAnnotations: - [settingsTitles addObjectsFromArray:@[ - @"Add 100 Views", - @"Add 1,000 Views", - @"Add 10,000 Views", - @"Add 100 Sprites", - @"Add 1,000 Sprites", - @"Add 10,000 Sprites", - @"Animate an Annotation View", - @"Add Test Shapes", - @"Add 10x Test Shapes", - @"Add Point With Custom Callout", - @"Query Annotations", - [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], - @"Remove Annotations", - @"Select an offscreen point annotation", - @"Center selected annotation", - @"Add visible area polyline" - ]]; - break; - case MBXSettingsRuntimeStyling: - [settingsTitles addObjectsFromArray:@[ - @"Add Building Extrusions", - @"Style Water With Function", - @"Style Roads With Function", - @"Add Raster & Apply Function", - @"Add Shapes & Apply Fill", - @"Style Symbol Color", - @"Style Building Fill Color", - @"Style Ferry Line Color", - @"Remove Parks", - @"Style Fill With Filter", - @"Style Lines With Filter", - @"Style Fill With Numeric Filter", - @"Query and Style Features", - @"Style Feature", - @"Style Dynamic Point Collection", - @"Update Shape Source: Data", - @"Update Shape Source: URL", - @"Update Shape Source: Features", - @"Style Vector Tile Source", - @"Style Raster Tile Source", - @"Style Image Source", - @"Add Route Line", + switch (section) { + case MBXSettingsDebugTools: + [settingsTitles addObjectsFromArray:@[ + @"Reset position", + [NSString stringWithFormat:@"%@ tile boundaries", + (debugMask & MLNMapDebugTileBoundariesMask ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ tile info", + (debugMask & MLNMapDebugTileInfoMask ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ tile timestamps", + (debugMask & MLNMapDebugTimestampsMask ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ collision boxes", + (debugMask & MLNMapDebugCollisionBoxesMask ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ overdraw visualization", + (debugMask & MLNMapDebugOverdrawVisualizationMask ? @"Hide" + : @"Show")], + [NSString stringWithFormat:@"%@ zoom level ornament", + (self.zoomLevelOrnamentEnabled ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ frame time graph", + (self.frameTimeGraphEnabled ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ reuse queue stats", + (self.reuseQueueStatsEnabled ? @"Hide" : @"Show")], + [NSString stringWithFormat:@"%@ rendering stats", + (self.renderingStatsEnabled ? @"Hide" : @"Show")] + ]]; + break; + case MBXSettingsAnnotations: + [settingsTitles addObjectsFromArray:@[ + @"Add 100 Views", @"Add 1,000 Views", @"Add 10,000 Views", @"Add 100 Sprites", + @"Add 1,000 Sprites", @"Add 10,000 Sprites", @"Animate an Annotation View", + @"Add Test Shapes", @"Add 10x Test Shapes", @"Add Point With Custom Callout", + @"Query Annotations", + [NSString + stringWithFormat:@"%@ Custom User Dot", + (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], + @"Remove Annotations", @"Select an offscreen point annotation", + @"Center selected annotation", @"Add visible area polyline" + ]]; + break; + case MBXSettingsRuntimeStyling: + [settingsTitles addObjectsFromArray:@[ + @"Add Building Extrusions", + @"Style Water With Function", + @"Style Roads With Function", + @"Add Raster & Apply Function", + @"Add Shapes & Apply Fill", + @"Style Symbol Color", + @"Style Building Fill Color", + @"Style Ferry Line Color", + @"Remove Parks", + @"Style Fill With Filter", + @"Style Lines With Filter", + @"Style Fill With Numeric Filter", + @"Query and Style Features", + @"Style Feature", + @"Style Dynamic Point Collection", + @"Update Shape Source: Data", + @"Update Shape Source: URL", + @"Update Shape Source: Features", + @"Style Vector Tile Source", + @"Style Raster Tile Source", + @"Style Image Source", + @"Add Route Line", #if MLN_RENDER_BACKEND_METAL - @"Add Custom Triangle Layer (Metal)", + @"Add Custom Triangle Layer (Metal)", #else - @"Add Custom Triangle Layer (OpenGL)", + @"Add Custom Triangle Layer (OpenGL)", #endif - @"Dynamically Style Polygon", - @"Add Custom Lat/Lon Grid", - @"Style Route line with gradient", - @"Add Custom Drawable Layer", - @"Add FourSquare POIs PMTiles Layer", - @"Add Earthquake Heatmap" - ]]; - break; - case MBXSettingsMiscellaneous: - [settingsTitles addObjectsFromArray:@[ - @"Start World Tour", - @"Random Tour", - @"Embedded Map View", - [NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], - [NSString stringWithFormat:@"Show Labels in %@", (_localizingLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], - @"Show Snapshots", - @"Missing Icon", - [NSString stringWithFormat:@"%@ Camera Changes", (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")], - [NSString stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")], - @"View Route Simulation", - @"Ornaments Placement", - @"Lat Long bounds with padding", - [NSString stringWithFormat:@"Cycle tile LOD mode: %@", [self getTileLodModeName]] - ]]; + @"Dynamically Style Polygon", + @"Add Custom Lat/Lon Grid", + @"Style Route line with gradient", + @"Add Custom Drawable Layer", + @"Add FourSquare POIs PMTiles Layer", + @"Add Earthquake Heatmap" + ]]; + break; + case MBXSettingsMiscellaneous: + [settingsTitles addObjectsFromArray:@[ + @"Start World Tour", @"Random Tour", @"Embedded Map View", + [NSString stringWithFormat:@"%@ Second Map", + ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], + [NSString stringWithFormat:@"Show Labels in %@", + (_localizingLabels + ? @"Default Language" + : [[NSLocale currentLocale] + displayNameForKey:NSLocaleIdentifier + value:[self bestLanguageForUser]])], + @"Show Snapshots", @"Missing Icon", + [NSString stringWithFormat:@"%@ Camera Changes", + (_shouldLimitCameraChanges ? @"Unlimit" : @"Limit")], + [NSString + stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")], + @"View Route Simulation", @"Ornaments Placement", @"Lat Long bounds with padding", + [NSString stringWithFormat:@"Cycle tile LOD mode: %@", [self getTileLodModeName]] + ]]; - break; + break; + default: + NSAssert(NO, @"All settings sections should be implemented"); + break; + } + + return settingsTitles; +} + +- (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath { + switch (indexPath.section) { + case MBXSettingsDebugTools: + switch (indexPath.row) { + case MBXSettingsDebugToolsResetPosition: + [self.mapView resetPosition]; + break; + case MBXSettingsDebugToolsTileBoundaries: + self.currentState.debugMask ^= MLNMapDebugTileBoundariesMask; + break; + case MBXSettingsDebugToolsTileInfo: + self.currentState.debugMask ^= MLNMapDebugTileInfoMask; + break; + case MBXSettingsDebugToolsTimestamps: + self.currentState.debugMask ^= MLNMapDebugTimestampsMask; + break; + case MBXSettingsDebugToolsCollisionBoxes: + self.currentState.debugMask ^= MLNMapDebugCollisionBoxesMask; + break; + case MBXSettingsDebugToolsOverdrawVisualization: + self.currentState.debugMask ^= MLNMapDebugOverdrawVisualizationMask; + break; + case MBXSettingsDebugToolsShowZoomLevel: { + self.zoomLevelOrnamentEnabled = !self.zoomLevelOrnamentEnabled; + self.currentState.showsZoomLevelOrnament = self.zoomLevelOrnamentEnabled; + self.hudLabel.hidden = !self.zoomLevelOrnamentEnabled; + self.reuseQueueStatsEnabled = NO; + [self updateHUD]; + break; + } + case MBXSettingsDebugToolsShowFrameTimeGraph: { + self.frameTimeGraphEnabled = !self.frameTimeGraphEnabled; + self.currentState.showsTimeFrameGraph = !self.currentState.showsTimeFrameGraph; + self.frameTimeGraphView.hidden = !self.frameTimeGraphEnabled; + [self updateHUD]; + break; + } + case MBXSettingsDebugToolsShowReuseQueueStats: { + self.reuseQueueStatsEnabled = !self.currentState.reuseQueueStatsEnabled; + self.hudLabel.hidden = !self.currentState.reuseQueueStatsEnabled; + self.zoomLevelOrnamentEnabled = NO; + [self updateHUD]; + break; + } + case MBXSettingsDebugToolsShowRenderingStats: { + self.renderingStatsEnabled = !self.renderingStatsEnabled; + [self.mapView enableRenderingStatsView:self.renderingStatsEnabled]; + break; + } default: - NSAssert(NO, @"All settings sections should be implemented"); - break; - } + NSAssert(NO, @"All debug tools setting rows should be implemented"); + break; + } - return settingsTitles; -} + self.mapView.debugMask = self.currentState.debugMask; -- (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath -{ - switch (indexPath.section) - { - case MBXSettingsDebugTools: - switch (indexPath.row) - { - case MBXSettingsDebugToolsResetPosition: - [self.mapView resetPosition]; - break; - case MBXSettingsDebugToolsTileBoundaries: - self.currentState.debugMask ^= MLNMapDebugTileBoundariesMask; - break; - case MBXSettingsDebugToolsTileInfo: - self.currentState.debugMask ^= MLNMapDebugTileInfoMask; - break; - case MBXSettingsDebugToolsTimestamps: - self.currentState.debugMask ^= MLNMapDebugTimestampsMask; - break; - case MBXSettingsDebugToolsCollisionBoxes: - self.currentState.debugMask ^= MLNMapDebugCollisionBoxesMask; - break; - case MBXSettingsDebugToolsOverdrawVisualization: - self.currentState.debugMask ^= MLNMapDebugOverdrawVisualizationMask; - break; - case MBXSettingsDebugToolsShowZoomLevel: - { - self.zoomLevelOrnamentEnabled = !self.zoomLevelOrnamentEnabled; - self.currentState.showsZoomLevelOrnament = self.zoomLevelOrnamentEnabled; - self.hudLabel.hidden = !self.zoomLevelOrnamentEnabled; - self.reuseQueueStatsEnabled = NO; - [self updateHUD]; - break; - } - case MBXSettingsDebugToolsShowFrameTimeGraph: - { - self.frameTimeGraphEnabled = !self.frameTimeGraphEnabled; - self.currentState.showsTimeFrameGraph = !self.currentState.showsTimeFrameGraph; - self.frameTimeGraphView.hidden = !self.frameTimeGraphEnabled; - [self updateHUD]; - break; - } - case MBXSettingsDebugToolsShowReuseQueueStats: - { - self.reuseQueueStatsEnabled = !self.currentState.reuseQueueStatsEnabled; - self.hudLabel.hidden = !self.currentState.reuseQueueStatsEnabled; - self.zoomLevelOrnamentEnabled = NO; - [self updateHUD]; - break; - } - case MBXSettingsDebugToolsShowRenderingStats: - { - self.renderingStatsEnabled = !self.renderingStatsEnabled; - [self.mapView enableRenderingStatsView:self.renderingStatsEnabled]; - break; - } - default: - NSAssert(NO, @"All debug tools setting rows should be implemented"); - break; - } - - self.mapView.debugMask = self.currentState.debugMask; - - break; - case MBXSettingsAnnotations: - switch (indexPath.row) - { - case MBXSettingsAnnotations100Views: - [self parseFeaturesAddingCount:100 usingViews:YES]; - break; - case MBXSettingsAnnotations1000Views: - [self parseFeaturesAddingCount:1000 usingViews:YES]; - break; - case MBXSettingsAnnotations10000Views: - [self parseFeaturesAddingCount:10000 usingViews:YES]; - break; - case MBXSettingsAnnotations100Sprites: - [self parseFeaturesAddingCount:100 usingViews:NO]; - break; - case MBXSettingsAnnotations1000Sprites: - [self parseFeaturesAddingCount:1000 usingViews:NO]; - break; - case MBXSettingsAnnotations10000Sprites: - [self parseFeaturesAddingCount:10000 usingViews:NO]; - break; - case MBXSettingsAnnotationAnimation: - [self animateAnnotationView]; - break; - case MBXSettingsAnnotationsTestShapes: - [self addTestShapes:1]; - break; - case MBXSettingsAnnotationsManyTestShapes: - [self addTestShapes:10]; - break; - case MBXSettingsAnnotationsCustomCallout: - [self addAnnotationWithCustomCallout]; - break; - case MBXSettingsAnnotationsQueryAnnotations: - [self testQueryPointAnnotations]; - break; - case MBXSettingsAnnotationsCustomUserDot: - [self toggleCustomUserDot]; - break; - case MBXSettingsAnnotationsRemoveAnnotations: - [self.mapView removeAnnotations:self.mapView.annotations]; - break; - case MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation: - [self selectAnOffscreenPointAnnotation]; - break; - case MBXSettingsAnnotationCenterSelectedAnnotation: - [self centerSelectedAnnotation]; - break; - case MBXSettingsAnnotationAddVisibleAreaPolyline: - [self addVisibleAreaPolyline]; - break; - default: - NSAssert(NO, @"All annotations setting rows should be implemented"); - break; - } - break; - case MBXSettingsRuntimeStyling: - switch (indexPath.row) - { - case MBXSettingsRuntimeStylingBuildingExtrusions: - [self styleBuildingExtrusions]; - break; - case MBXSettingsRuntimeStylingWater: - [self styleWaterLayer]; - break; - case MBXSettingsRuntimeStylingRoads: - [self styleRoadLayer]; - break; - case MBXSettingsRuntimeStylingRaster: - [self styleRasterLayer]; - break; - case MBXSettingsRuntimeStylingShape: - [self styleShapeSource]; - break; - case MBXSettingsRuntimeStylingSymbols: - [self styleSymbolLayer]; - break; - case MBXSettingsRuntimeStylingBuildings: - [self styleBuildingLayer]; - break; - case MBXSettingsRuntimeStylingFerry: - [self styleFerryLayer]; - break; - case MBXSettingsRuntimeStylingParks: - [self removeParkLayer]; - break; - case MBXSettingsRuntimeStylingFilteredFill: - [self styleFilteredFill]; - break; - case MBXSettingsRuntimeStylingFilteredLines: - [self styleFilteredLines]; - break; - case MBXSettingsRuntimeStylingNumericFilteredFill: - [self styleNumericFilteredFills]; - break; - case MBXSettingsRuntimeStylingStyleQuery: - [self styleQuery]; - break; - case MBXSettingsRuntimeStylingFeatureSource: - [self styleFeature]; - break; - case MBXSettingsRuntimeStylingPointCollection: - [self styleDynamicPointCollection]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceURL: - [self updateShapeSourceURL]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceData: - [self updateShapeSourceData]; - break; - case MBXSettingsRuntimeStylingUpdateShapeSourceFeatures: - [self updateShapeSourceFeatures]; - break; - case MBXSettingsRuntimeStylingVectorTileSource: - [self styleVectorTileSource]; - break; - case MBXSettingsRuntimeStylingRasterTileSource: - [self styleRasterTileSource]; - break; - case MBXSettingsRuntimeStylingImageSource: - [self styleImageSource]; - break; - case MBXSettingsRuntimeStylingRouteLine: - [self styleRouteLine]; - break; - case MBXSettingsRuntimeStylingAddCustomTriangleLayer: - [self styleAddCustomTriangleLayer]; - break; - case MBXSettingsRuntimeStylingDDSPolygon: - [self stylePolygonWithDDS]; - break; - case MBXSettingsRuntimeStylingCustomLatLonGrid: - [self addLatLonGrid]; - break; - case MBXSettingsRuntimeStylingLineGradient: - [self styleLineGradient]; - break; - case MBXSettingsRuntimeStylingCustomDrawableLayer: - [self addCustomDrawableLayer]; - break; - case MBXSettingsRuntimeStylingAddFoursquarePOIsPMTiles: - [self addFoursquarePOIsPMTilesLayer]; - break; - case MBXSettingsRuntimeStylingHeatmapLayer: - [self addEarthquakeHeatmap]; - break; - default: - NSAssert(NO, @"All runtime styling setting rows should be implemented"); - break; - } - break; - case MBXSettingsMiscellaneous: - switch (indexPath.row) - { - case MBXSettingsMiscellaneousLocalizeLabels: - [self toggleStyleLabelsLanguage]; - break; - case MBXSettingsMiscellaneousWorldTour: - [self startWorldTour]; - break; - case MBXSettingsMiscellaneousRandomTour: - [self randomWorldTour]; - break; - case MBXSettingsMiscellaneousScrollView: - { - UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; - MBXEmbeddedMapViewController *embeddedMapViewController = (MBXEmbeddedMapViewController *)[storyboard instantiateViewControllerWithIdentifier:@"MBXEmbeddedMapViewController"]; - [self.navigationController pushViewController:embeddedMapViewController animated:YES]; - break; - } - case MBXSettingsMiscellaneousToggleTwoMaps: - [self toggleSecondMapView]; - break; - case MBXSettingsMiscellaneousShowSnapshots: - { - [self performSegueWithIdentifier:@"ShowSnapshots" sender:nil]; - break; - } - case MBXSettingsMiscellaneousMissingIcon: - { - [self loadMissingIcon]; - break; - } - case MBXSettingsMiscellaneousShowCustomLocationManager: - { - [self performSegueWithIdentifier:@"ShowCustomLocationManger" sender:nil]; - break; - } - case MBXSettingsMiscellaneousShouldLimitCameraChanges: - { - self.shouldLimitCameraChanges = !self.shouldLimitCameraChanges; - if (self.shouldLimitCameraChanges) { - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(39.748947, -104.995882) zoomLevel:10 direction:0 animated:NO]; - } - break; - } - case MBXSettingsMiscellaneousSetContentInsets: - { - if (!_contentInsetsEnabled) { - _originalContentInsets = [self.mapView contentInset]; - } - _contentInsetsEnabled = !_contentInsetsEnabled; - _mapView.automaticallyAdjustsContentInset = !_contentInsetsEnabled; - UIEdgeInsets contentInsets = self.mapView.bounds.size.width > self.mapView.bounds.size.height - ? UIEdgeInsetsMake(_originalContentInsets.top, 0.5 * self.mapView.bounds.size.width, _originalContentInsets.bottom, 0.0) - : UIEdgeInsetsMake(0.25 * self.mapView.bounds.size.height, 0.0, _originalContentInsets.bottom, 0.25 * self.mapView.bounds.size.width); - if (_contentInsetsEnabled) { - if (!self.contentInsetsOverlays) - self.contentInsetsOverlays = [NSMutableArray array]; - if (![self.contentInsetsOverlays count]) { - UIView *view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.mapView.bounds.size.width, contentInsets.top)]; - view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; - [self.contentInsetsOverlays addObject:view]; - [self.view addSubview:view]; - view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, contentInsets.left, self.mapView.bounds.size.height)]; - view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; - [self.contentInsetsOverlays addObject:view]; - [self.view addSubview:view]; - view = [[UIView alloc]initWithFrame:CGRectMake(self.mapView.bounds.size.width - contentInsets.right, 0, contentInsets.right, self.mapView.bounds.size.height)]; - view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; - [self.contentInsetsOverlays addObject:view]; - [self.view addSubview:view]; - view = [[UIView alloc]initWithFrame:CGRectMake(0, self.mapView.bounds.size.height - contentInsets.bottom, self.mapView.bounds.size.width, self.mapView.bounds.size.height)]; - view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; - [self.contentInsetsOverlays addObject:view]; - [self.view addSubview:view]; - } - [self.view bringSubviewToFront:self.contentInsetsOverlays[0]]; - [self.view bringSubviewToFront:self.contentInsetsOverlays[1]]; - [self.view bringSubviewToFront:self.contentInsetsOverlays[2]]; - [self.view bringSubviewToFront:self.contentInsetsOverlays[3]]; - - // Denver streets parallel to cardinal directions help illustrate - // viewport center offset when edge insets are set. - MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.72707, -104.9986) - acrossDistance:100 - pitch:60 - heading:0]; - __weak MBXViewController *weakSelf = self; - [self.mapView setCamera:camera withDuration:0.3 animationTimingFunction:nil completionHandler:^{ - [weakSelf.mapView setContentInset:contentInsets animated:YES completionHandler:nil]; - }]; - } else { - [self.view sendSubviewToBack:self.contentInsetsOverlays[0]]; - [self.view sendSubviewToBack:self.contentInsetsOverlays[1]]; - [self.view sendSubviewToBack:self.contentInsetsOverlays[2]]; - [self.view sendSubviewToBack:self.contentInsetsOverlays[3]]; - [self.mapView setContentInset:_originalContentInsets animated:YES completionHandler:nil]; - } - break; - } - case MBXSettingsMiscellaneousOrnamentsPlacement: - { - MBXOrnamentsViewController *ornamentsViewController = [[MBXOrnamentsViewController alloc] init]; - [self.navigationController pushViewController:ornamentsViewController animated:YES]; - break; - } - case MBXSettingsMiscellaneousLatLngBoundsWithPadding: - [self flyToWithLatLngBoundsAndPadding]; - break; - case MBXSettingsMiscellaneousCycleTileLOD: - [self cycleTileLodMode]; - break; - default: - NSAssert(NO, @"All miscellaneous setting rows should be implemented"); - break; - } - break; + break; + case MBXSettingsAnnotations: + switch (indexPath.row) { + case MBXSettingsAnnotations100Views: + [self parseFeaturesAddingCount:100 usingViews:YES]; + break; + case MBXSettingsAnnotations1000Views: + [self parseFeaturesAddingCount:1000 usingViews:YES]; + break; + case MBXSettingsAnnotations10000Views: + [self parseFeaturesAddingCount:10000 usingViews:YES]; + break; + case MBXSettingsAnnotations100Sprites: + [self parseFeaturesAddingCount:100 usingViews:NO]; + break; + case MBXSettingsAnnotations1000Sprites: + [self parseFeaturesAddingCount:1000 usingViews:NO]; + break; + case MBXSettingsAnnotations10000Sprites: + [self parseFeaturesAddingCount:10000 usingViews:NO]; + break; + case MBXSettingsAnnotationAnimation: + [self animateAnnotationView]; + break; + case MBXSettingsAnnotationsTestShapes: + [self addTestShapes:1]; + break; + case MBXSettingsAnnotationsManyTestShapes: + [self addTestShapes:10]; + break; + case MBXSettingsAnnotationsCustomCallout: + [self addAnnotationWithCustomCallout]; + break; + case MBXSettingsAnnotationsQueryAnnotations: + [self testQueryPointAnnotations]; + break; + case MBXSettingsAnnotationsCustomUserDot: + [self toggleCustomUserDot]; + break; + case MBXSettingsAnnotationsRemoveAnnotations: + [self.mapView removeAnnotations:self.mapView.annotations]; + break; + case MBXSettingsAnnotationSelectRandomOffscreenPointAnnotation: + [self selectAnOffscreenPointAnnotation]; + break; + case MBXSettingsAnnotationCenterSelectedAnnotation: + [self centerSelectedAnnotation]; + break; + case MBXSettingsAnnotationAddVisibleAreaPolyline: + [self addVisibleAreaPolyline]; + break; default: - NSAssert(NO, @"All settings sections should be implemented"); - break; - } + NSAssert(NO, @"All annotations setting rows should be implemented"); + break; + } + break; + case MBXSettingsRuntimeStyling: + switch (indexPath.row) { + case MBXSettingsRuntimeStylingBuildingExtrusions: + [self styleBuildingExtrusions]; + break; + case MBXSettingsRuntimeStylingWater: + [self styleWaterLayer]; + break; + case MBXSettingsRuntimeStylingRoads: + [self styleRoadLayer]; + break; + case MBXSettingsRuntimeStylingRaster: + [self styleRasterLayer]; + break; + case MBXSettingsRuntimeStylingShape: + [self styleShapeSource]; + break; + case MBXSettingsRuntimeStylingSymbols: + [self styleSymbolLayer]; + break; + case MBXSettingsRuntimeStylingBuildings: + [self styleBuildingLayer]; + break; + case MBXSettingsRuntimeStylingFerry: + [self styleFerryLayer]; + break; + case MBXSettingsRuntimeStylingParks: + [self removeParkLayer]; + break; + case MBXSettingsRuntimeStylingFilteredFill: + [self styleFilteredFill]; + break; + case MBXSettingsRuntimeStylingFilteredLines: + [self styleFilteredLines]; + break; + case MBXSettingsRuntimeStylingNumericFilteredFill: + [self styleNumericFilteredFills]; + break; + case MBXSettingsRuntimeStylingStyleQuery: + [self styleQuery]; + break; + case MBXSettingsRuntimeStylingFeatureSource: + [self styleFeature]; + break; + case MBXSettingsRuntimeStylingPointCollection: + [self styleDynamicPointCollection]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceURL: + [self updateShapeSourceURL]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceData: + [self updateShapeSourceData]; + break; + case MBXSettingsRuntimeStylingUpdateShapeSourceFeatures: + [self updateShapeSourceFeatures]; + break; + case MBXSettingsRuntimeStylingVectorTileSource: + [self styleVectorTileSource]; + break; + case MBXSettingsRuntimeStylingRasterTileSource: + [self styleRasterTileSource]; + break; + case MBXSettingsRuntimeStylingImageSource: + [self styleImageSource]; + break; + case MBXSettingsRuntimeStylingRouteLine: + [self styleRouteLine]; + break; + case MBXSettingsRuntimeStylingAddCustomTriangleLayer: + [self styleAddCustomTriangleLayer]; + break; + case MBXSettingsRuntimeStylingDDSPolygon: + [self stylePolygonWithDDS]; + break; + case MBXSettingsRuntimeStylingCustomLatLonGrid: + [self addLatLonGrid]; + break; + case MBXSettingsRuntimeStylingLineGradient: + [self styleLineGradient]; + break; + case MBXSettingsRuntimeStylingCustomDrawableLayer: + [self addCustomDrawableLayer]; + break; + case MBXSettingsRuntimeStylingAddFoursquarePOIsPMTiles: + [self addFoursquarePOIsPMTilesLayer]; + break; + case MBXSettingsRuntimeStylingHeatmapLayer: + [self addEarthquakeHeatmap]; + break; + default: + NSAssert(NO, @"All runtime styling setting rows should be implemented"); + break; + } + break; + case MBXSettingsMiscellaneous: + switch (indexPath.row) { + case MBXSettingsMiscellaneousLocalizeLabels: + [self toggleStyleLabelsLanguage]; + break; + case MBXSettingsMiscellaneousWorldTour: + [self startWorldTour]; + break; + case MBXSettingsMiscellaneousRandomTour: + [self randomWorldTour]; + break; + case MBXSettingsMiscellaneousScrollView: { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; + MBXEmbeddedMapViewController *embeddedMapViewController = + (MBXEmbeddedMapViewController *)[storyboard + instantiateViewControllerWithIdentifier:@"MBXEmbeddedMapViewController"]; + [self.navigationController pushViewController:embeddedMapViewController animated:YES]; + break; + } + case MBXSettingsMiscellaneousToggleTwoMaps: + [self toggleSecondMapView]; + break; + case MBXSettingsMiscellaneousShowSnapshots: { + [self performSegueWithIdentifier:@"ShowSnapshots" sender:nil]; + break; + } + case MBXSettingsMiscellaneousMissingIcon: { + [self loadMissingIcon]; + break; + } + case MBXSettingsMiscellaneousShowCustomLocationManager: { + [self performSegueWithIdentifier:@"ShowCustomLocationManger" sender:nil]; + break; + } + case MBXSettingsMiscellaneousShouldLimitCameraChanges: { + self.shouldLimitCameraChanges = !self.shouldLimitCameraChanges; + if (self.shouldLimitCameraChanges) { + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(39.748947, -104.995882) + zoomLevel:10 + direction:0 + animated:NO]; + } + break; + } + case MBXSettingsMiscellaneousSetContentInsets: { + if (!_contentInsetsEnabled) { + _originalContentInsets = [self.mapView contentInset]; + } + _contentInsetsEnabled = !_contentInsetsEnabled; + _mapView.automaticallyAdjustsContentInset = !_contentInsetsEnabled; + UIEdgeInsets contentInsets = + self.mapView.bounds.size.width > self.mapView.bounds.size.height + ? UIEdgeInsetsMake(_originalContentInsets.top, + 0.5 * self.mapView.bounds.size.width, + _originalContentInsets.bottom, 0.0) + : UIEdgeInsetsMake(0.25 * self.mapView.bounds.size.height, 0.0, + _originalContentInsets.bottom, + 0.25 * self.mapView.bounds.size.width); + if (_contentInsetsEnabled) { + if (!self.contentInsetsOverlays) self.contentInsetsOverlays = [NSMutableArray array]; + if (![self.contentInsetsOverlays count]) { + UIView *view = + [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.mapView.bounds.size.width, + contentInsets.top)]; + view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; + [self.contentInsetsOverlays addObject:view]; + [self.view addSubview:view]; + view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, contentInsets.left, + self.mapView.bounds.size.height)]; + view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; + [self.contentInsetsOverlays addObject:view]; + [self.view addSubview:view]; + view = [[UIView alloc] + initWithFrame:CGRectMake(self.mapView.bounds.size.width - contentInsets.right, 0, + contentInsets.right, self.mapView.bounds.size.height)]; + view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; + [self.contentInsetsOverlays addObject:view]; + [self.view addSubview:view]; + view = [[UIView alloc] + initWithFrame:CGRectMake(0, + self.mapView.bounds.size.height - contentInsets.bottom, + self.mapView.bounds.size.width, + self.mapView.bounds.size.height)]; + view.backgroundColor = [UIColor colorWithRed:0.0 green:0.3 blue:0.3 alpha:0.5]; + [self.contentInsetsOverlays addObject:view]; + [self.view addSubview:view]; + } + [self.view bringSubviewToFront:self.contentInsetsOverlays[0]]; + [self.view bringSubviewToFront:self.contentInsetsOverlays[1]]; + [self.view bringSubviewToFront:self.contentInsetsOverlays[2]]; + [self.view bringSubviewToFront:self.contentInsetsOverlays[3]]; + + // Denver streets parallel to cardinal directions help illustrate + // viewport center offset when edge insets are set. + MLNMapCamera *camera = [MLNMapCamera + cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(39.72707, -104.9986) + acrossDistance:100 + pitch:60 + heading:0]; + __weak MBXViewController *weakSelf = self; + [self.mapView setCamera:camera + withDuration:0.3 + animationTimingFunction:nil + completionHandler:^{ + [weakSelf.mapView setContentInset:contentInsets + animated:YES + completionHandler:nil]; + }]; + } else { + [self.view sendSubviewToBack:self.contentInsetsOverlays[0]]; + [self.view sendSubviewToBack:self.contentInsetsOverlays[1]]; + [self.view sendSubviewToBack:self.contentInsetsOverlays[2]]; + [self.view sendSubviewToBack:self.contentInsetsOverlays[3]]; + [self.mapView setContentInset:_originalContentInsets + animated:YES + completionHandler:nil]; + } + break; + } + case MBXSettingsMiscellaneousOrnamentsPlacement: { + MBXOrnamentsViewController *ornamentsViewController = + [[MBXOrnamentsViewController alloc] init]; + [self.navigationController pushViewController:ornamentsViewController animated:YES]; + break; + } + case MBXSettingsMiscellaneousLatLngBoundsWithPadding: + [self flyToWithLatLngBoundsAndPadding]; + break; + case MBXSettingsMiscellaneousCycleTileLOD: + [self cycleTileLodMode]; + break; + default: + NSAssert(NO, @"All miscellaneous setting rows should be implemented"); + break; + } + break; + default: + NSAssert(NO, @"All settings sections should be implemented"); + break; + } } -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return [[self settingsSectionTitles] count]; +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return [[self settingsSectionTitles] count]; } -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - return [[self settingsTitlesForSection:section] count]; +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [[self settingsTitlesForSection:section] count]; } -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section -{ - return [[self settingsSectionTitles] objectAtIndex:section]; +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [[self settingsSectionTitles] objectAtIndex:section]; } -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:nil]; - cell.textLabel.text = [[self settingsTitlesForSection:indexPath.section] objectAtIndex:indexPath.row]; + cell.textLabel.text = + [[self settingsTitlesForSection:indexPath.section] objectAtIndex:indexPath.row]; - return cell; + return cell; } -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath -{ - [tableView deselectRowAtIndexPath:indexPath animated:NO]; +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; - [self dismissViewControllerAnimated:YES completion:^ - { - [self performActionForSettingAtIndexPath:indexPath]; - }]; + [self dismissViewControllerAnimated:YES + completion:^{ + [self performActionForSettingAtIndexPath:indexPath]; + }]; } // MARK: - Debugging Actions -- (NSDictionary*)loadPointFeatures -{ - [_loadLock lock]; - if (!_pointFeatures) { - NSData *featuresData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]]; - if (featuresData) { - id features = [NSJSONSerialization JSONObjectWithData:featuresData - options:0 - error:nil]; - if ([features isKindOfClass:[NSDictionary class]]) - { - _pointFeatures = (NSDictionary*)features; - } - } +- (NSDictionary *)loadPointFeatures { + [_loadLock lock]; + if (!_pointFeatures) { + NSData *featuresData = [NSData + dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"points" ofType:@"geojson"]]; + if (featuresData) { + id features = [NSJSONSerialization JSONObjectWithData:featuresData options:0 error:nil]; + if ([features isKindOfClass:[NSDictionary class]]) { + _pointFeatures = (NSDictionary *)features; + } } - [_loadLock unlock]; - return _pointFeatures; + } + [_loadLock unlock]; + return _pointFeatures; } -- (void)parseFeaturesAddingCount:(NSUInteger)featuresCount usingViews:(BOOL)useViews -{ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ - { - if (auto *features = [self loadPointFeatures]) - { - NSMutableArray *annotations = [NSMutableArray array]; - for (NSDictionary *feature in features[@"features"]) - { - CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake([feature[@"geometry"][@"coordinates"][1] doubleValue], - [feature[@"geometry"][@"coordinates"][0] doubleValue]); - NSString *title = feature[@"properties"][@"NAME"]; +- (void)parseFeaturesAddingCount:(NSUInteger)featuresCount usingViews:(BOOL)useViews { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + if (auto *features = [self loadPointFeatures]) { + NSMutableArray *annotations = [NSMutableArray array]; + for (NSDictionary *feature in features[@"features"]) { + CLLocationCoordinate2D coordinate = + CLLocationCoordinate2DMake([feature[@"geometry"][@"coordinates"][1] doubleValue], + [feature[@"geometry"][@"coordinates"][0] doubleValue]); + NSString *title = feature[@"properties"][@"NAME"]; - MLNPointAnnotation *annotation = (useViews ? [MLNPointAnnotation new] : [MBXSpriteBackedAnnotation new]); + MLNPointAnnotation *annotation = + (useViews ? [MLNPointAnnotation new] : [MBXSpriteBackedAnnotation new]); - annotation.coordinate = coordinate; - annotation.title = title; + annotation.coordinate = coordinate; + annotation.title = title; - [annotations addObject:annotation]; + [annotations addObject:annotation]; - if (annotations.count == featuresCount) break; - } + if (annotations.count == featuresCount) break; + } - dispatch_async(dispatch_get_main_queue(), ^ - { - [self.mapView removeAnnotations:self.mapView.annotations]; - [self.mapView addAnnotations:annotations]; - [self.mapView showAnnotations:annotations animated:YES]; - }); - } - }); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.mapView removeAnnotations:self.mapView.annotations]; + [self.mapView addAnnotations:annotations]; + [self.mapView showAnnotations:annotations animated:YES]; + }); + } + }); } -- (void)animateAnnotationView - { - MLNPointAnnotation *annot = [[MLNPointAnnotation alloc] init]; - annot.coordinate = self.mapView.centerCoordinate; - [self.mapView addAnnotation:annot]; +- (void)animateAnnotationView { + MLNPointAnnotation *annot = [[MLNPointAnnotation alloc] init]; + annot.coordinate = self.mapView.centerCoordinate; + [self.mapView addAnnotation:annot]; - // Move the annotation to a point that is offscreen. - CGPoint point = CGPointMake(self.view.frame.origin.x - 200, CGRectGetMidY(self.view.frame)); + // Move the annotation to a point that is offscreen. + CGPoint point = CGPointMake(self.view.frame.origin.x - 200, CGRectGetMidY(self.view.frame)); - CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.view]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [UIView animateWithDuration:10 animations:^{ - annot.coordinate = coord; - }]; - }); + CLLocationCoordinate2D coord = [self.mapView convertPoint:point toCoordinateFromView:self.view]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [UIView animateWithDuration:10 + animations:^{ + annot.coordinate = coord; + }]; + }); +}; + +- (void)addTestShapes:(NSUInteger)featuresCount { + for (int featureIndex = 0; featureIndex < featuresCount; ++featureIndex) { + double deltaLongitude = featureIndex * 0.01; + double deltaLatitude = -featureIndex * 0.01; + + // Pacific Northwest triangle + // + CLLocationCoordinate2D triangleCoordinates[3] = { + CLLocationCoordinate2DMake(44 + deltaLatitude, -122 + deltaLongitude), + CLLocationCoordinate2DMake(46 + deltaLatitude, -122 + deltaLongitude), + CLLocationCoordinate2DMake(46 + deltaLatitude, -121 + deltaLongitude)}; + + MLNPolygon *triangle = [MLNPolygon polygonWithCoordinates:triangleCoordinates count:3]; + + [self.mapView addAnnotation:triangle]; + + // West coast polyline + // + CLLocationCoordinate2D lineCoordinates[4] = { + CLLocationCoordinate2DMake(47.6025 + deltaLatitude, -122.3327 + deltaLongitude), + CLLocationCoordinate2DMake(45.5189 + deltaLatitude, -122.6726 + deltaLongitude), + CLLocationCoordinate2DMake(37.7790 + deltaLatitude, -122.4177 + deltaLongitude), + CLLocationCoordinate2DMake(34.0532 + deltaLatitude, -118.2349 + deltaLongitude)}; + MLNPolyline *line = [MLNPolyline polylineWithCoordinates:lineCoordinates count:4]; + [self.mapView addAnnotation:line]; + + // Orcas Island, WA hike polyline + // + NSDictionary *hike = [NSJSONSerialization + JSONObjectWithData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] + pathForResource:@"polyline" + ofType:@"geojson"]] + options:0 + error:nil]; + + NSArray *hikeCoordinatePairs = hike[@"features"][0][@"geometry"][@"coordinates"]; + + CLLocationCoordinate2D *polylineCoordinates = (CLLocationCoordinate2D *)malloc( + [hikeCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); + + for (NSUInteger i = 0; i < [hikeCoordinatePairs count]; i++) { + polylineCoordinates[i] = + CLLocationCoordinate2DMake([hikeCoordinatePairs[i][1] doubleValue] + deltaLatitude, + [hikeCoordinatePairs[i][0] doubleValue] + deltaLongitude); + } + + MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:polylineCoordinates + count:[hikeCoordinatePairs count]]; + + [self.mapView addAnnotation:polyline]; + + free(polylineCoordinates); + + // PA/NJ/DE polygons + // + NSDictionary *threestates = [NSJSONSerialization + JSONObjectWithData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] + pathForResource:@"threestates" + ofType:@"geojson"]] + options:0 + error:nil]; + + for (NSDictionary *feature in threestates[@"features"]) { + NSArray *stateCoordinatePairs = feature[@"geometry"][@"coordinates"]; + + while ([stateCoordinatePairs count] == 1) stateCoordinatePairs = stateCoordinatePairs[0]; + + CLLocationCoordinate2D *polygonCoordinates = (CLLocationCoordinate2D *)malloc( + [stateCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); + + for (NSUInteger i = 0; i < [stateCoordinatePairs count]; i++) { + polygonCoordinates[i] = + CLLocationCoordinate2DMake([stateCoordinatePairs[i][1] doubleValue] + deltaLatitude, + [stateCoordinatePairs[i][0] doubleValue] + deltaLongitude); + } + + MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:polygonCoordinates + count:[stateCoordinatePairs count]]; + polygon.title = feature[@"properties"][@"NAME"]; + + [self.mapView addAnnotation:polygon]; + + free(polygonCoordinates); + } + + // Null Island polygon with an interior hole + // + CLLocationCoordinate2D innerCoordinates[] = { + CLLocationCoordinate2DMake(-5 + deltaLatitude, -5 + deltaLongitude), + CLLocationCoordinate2DMake(-5 + deltaLatitude, 5 + deltaLongitude), + CLLocationCoordinate2DMake(5 + deltaLatitude, 5 + deltaLongitude), + CLLocationCoordinate2DMake(5 + deltaLatitude, -5 + deltaLongitude), }; - -- (void)addTestShapes:(NSUInteger)featuresCount -{ - for (int featureIndex = 0; featureIndex < featuresCount; ++featureIndex) { - double deltaLongitude = featureIndex * 0.01; - double deltaLatitude = -featureIndex * 0.01; - - // Pacific Northwest triangle - // - CLLocationCoordinate2D triangleCoordinates[3] = - { - CLLocationCoordinate2DMake(44 + deltaLatitude, -122 + deltaLongitude), - CLLocationCoordinate2DMake(46 + deltaLatitude, -122 + deltaLongitude), - CLLocationCoordinate2DMake(46 + deltaLatitude, -121 + deltaLongitude) - }; - - MLNPolygon *triangle = [MLNPolygon polygonWithCoordinates:triangleCoordinates count:3]; - - [self.mapView addAnnotation:triangle]; - - // West coast polyline - // - CLLocationCoordinate2D lineCoordinates[4] = { - CLLocationCoordinate2DMake(47.6025 + deltaLatitude, -122.3327 + deltaLongitude), - CLLocationCoordinate2DMake(45.5189 + deltaLatitude, -122.6726 + deltaLongitude), - CLLocationCoordinate2DMake(37.7790 + deltaLatitude, -122.4177 + deltaLongitude), - CLLocationCoordinate2DMake(34.0532 + deltaLatitude, -118.2349 + deltaLongitude) - }; - MLNPolyline *line = [MLNPolyline polylineWithCoordinates:lineCoordinates count:4]; - [self.mapView addAnnotation:line]; - - // Orcas Island, WA hike polyline - // - NSDictionary *hike = [NSJSONSerialization JSONObjectWithData: - [NSData dataWithContentsOfFile: - [[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]] - options:0 - error:nil]; - - NSArray *hikeCoordinatePairs = hike[@"features"][0][@"geometry"][@"coordinates"]; - - CLLocationCoordinate2D *polylineCoordinates = (CLLocationCoordinate2D *)malloc([hikeCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); - - for (NSUInteger i = 0; i < [hikeCoordinatePairs count]; i++) - { - polylineCoordinates[i] = CLLocationCoordinate2DMake([hikeCoordinatePairs[i][1] doubleValue] + deltaLatitude, [hikeCoordinatePairs[i][0] doubleValue] + deltaLongitude); - } - - MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:polylineCoordinates - count:[hikeCoordinatePairs count]]; - - [self.mapView addAnnotation:polyline]; - - free(polylineCoordinates); - - // PA/NJ/DE polygons - // - NSDictionary *threestates = [NSJSONSerialization JSONObjectWithData: - [NSData dataWithContentsOfFile: - [[NSBundle mainBundle] pathForResource:@"threestates" ofType:@"geojson"]] - options:0 - error:nil]; - - for (NSDictionary *feature in threestates[@"features"]) - { - NSArray *stateCoordinatePairs = feature[@"geometry"][@"coordinates"]; - - while ([stateCoordinatePairs count] == 1) stateCoordinatePairs = stateCoordinatePairs[0]; - - CLLocationCoordinate2D *polygonCoordinates = (CLLocationCoordinate2D *)malloc([stateCoordinatePairs count] * sizeof(CLLocationCoordinate2D)); - - for (NSUInteger i = 0; i < [stateCoordinatePairs count]; i++) - { - polygonCoordinates[i] = CLLocationCoordinate2DMake([stateCoordinatePairs[i][1] doubleValue] + deltaLatitude, [stateCoordinatePairs[i][0] doubleValue] + deltaLongitude); - } - - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:polygonCoordinates count:[stateCoordinatePairs count]]; - polygon.title = feature[@"properties"][@"NAME"]; - - [self.mapView addAnnotation:polygon]; - - free(polygonCoordinates); - } - - // Null Island polygon with an interior hole - // - CLLocationCoordinate2D innerCoordinates[] = { - CLLocationCoordinate2DMake(-5 + deltaLatitude, -5 + deltaLongitude), - CLLocationCoordinate2DMake(-5 + deltaLatitude, 5 + deltaLongitude), - CLLocationCoordinate2DMake(5 + deltaLatitude, 5 + deltaLongitude), - CLLocationCoordinate2DMake(5 + deltaLatitude, -5 + deltaLongitude), - }; - MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:innerCoordinates count:sizeof(innerCoordinates) / sizeof(innerCoordinates[0])]; - CLLocationCoordinate2D outerCoordinates[] = { - CLLocationCoordinate2DMake(-10 + deltaLatitude, -10 + deltaLongitude), - CLLocationCoordinate2DMake(-10 + deltaLatitude, 10 + deltaLongitude), - CLLocationCoordinate2DMake(10 + deltaLatitude, 10 + deltaLongitude), - CLLocationCoordinate2DMake(10 + deltaLatitude, -10 + deltaLongitude), - }; - MLNPolygon *outerPolygon = [MLNPolygon polygonWithCoordinates:outerCoordinates count:sizeof(outerCoordinates) / sizeof(outerCoordinates[0]) interiorPolygons:@[innerPolygon]]; - [self.mapView addAnnotation:outerPolygon]; - } + MLNPolygon *innerPolygon = + [MLNPolygon polygonWithCoordinates:innerCoordinates + count:sizeof(innerCoordinates) / sizeof(innerCoordinates[0])]; + CLLocationCoordinate2D outerCoordinates[] = { + CLLocationCoordinate2DMake(-10 + deltaLatitude, -10 + deltaLongitude), + CLLocationCoordinate2DMake(-10 + deltaLatitude, 10 + deltaLongitude), + CLLocationCoordinate2DMake(10 + deltaLatitude, 10 + deltaLongitude), + CLLocationCoordinate2DMake(10 + deltaLatitude, -10 + deltaLongitude), + }; + MLNPolygon *outerPolygon = + [MLNPolygon polygonWithCoordinates:outerCoordinates + count:sizeof(outerCoordinates) / sizeof(outerCoordinates[0]) + interiorPolygons:@[ innerPolygon ]]; + [self.mapView addAnnotation:outerPolygon]; + } } -- (void)addAnnotationWithCustomCallout -{ - [self.mapView removeAnnotations:self.mapView.annotations]; +- (void)addAnnotationWithCustomCallout { + [self.mapView removeAnnotations:self.mapView.annotations]; - MBXCustomCalloutAnnotation *firstAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; - firstAnnotation.coordinate = CLLocationCoordinate2DMake(48.8533940, 2.3775439); - firstAnnotation.title = @"Open anchored to annotation"; - firstAnnotation.anchoredToAnnotation = YES; - firstAnnotation.dismissesAutomatically = NO; + MBXCustomCalloutAnnotation *firstAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; + firstAnnotation.coordinate = CLLocationCoordinate2DMake(48.8533940, 2.3775439); + firstAnnotation.title = @"Open anchored to annotation"; + firstAnnotation.anchoredToAnnotation = YES; + firstAnnotation.dismissesAutomatically = NO; - MBXCustomCalloutAnnotation *secondAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; - secondAnnotation.coordinate = CLLocationCoordinate2DMake(48.8543940, 2.3775439); - secondAnnotation.title = @"Open not anchored to annotation"; - secondAnnotation.anchoredToAnnotation = NO; - secondAnnotation.dismissesAutomatically = NO; + MBXCustomCalloutAnnotation *secondAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; + secondAnnotation.coordinate = CLLocationCoordinate2DMake(48.8543940, 2.3775439); + secondAnnotation.title = @"Open not anchored to annotation"; + secondAnnotation.anchoredToAnnotation = NO; + secondAnnotation.dismissesAutomatically = NO; - MBXCustomCalloutAnnotation *thirdAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; - thirdAnnotation.coordinate = CLLocationCoordinate2DMake(48.8553940, 2.3775439); - thirdAnnotation.title = @"Dismisses automatically"; - thirdAnnotation.anchoredToAnnotation = YES; - thirdAnnotation.dismissesAutomatically = YES; + MBXCustomCalloutAnnotation *thirdAnnotation = [[MBXCustomCalloutAnnotation alloc] init]; + thirdAnnotation.coordinate = CLLocationCoordinate2DMake(48.8553940, 2.3775439); + thirdAnnotation.title = @"Dismisses automatically"; + thirdAnnotation.anchoredToAnnotation = YES; + thirdAnnotation.dismissesAutomatically = YES; - NSArray *annotations = @[firstAnnotation, secondAnnotation, thirdAnnotation]; - [self.mapView addAnnotations:annotations]; + NSArray *annotations = @[ firstAnnotation, secondAnnotation, thirdAnnotation ]; + [self.mapView addAnnotations:annotations]; - [self.mapView showAnnotations:annotations animated:YES]; + [self.mapView showAnnotations:annotations animated:YES]; } // MARK: - Foursquare PMTiles Implementation @@ -1098,297 +1113,289 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { Add the new method that creates the PMTiles vector source and circle layer. */ - (void)addFoursquarePOIsPMTilesLayer { - MLNVectorTileSource *foursquareSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"foursquare-10M" configurationURLString:@"pmtiles://https://oliverwipfli.ch/data/foursquare-os-places-10M-2024-11-20.pmtiles"]; + MLNVectorTileSource *foursquareSource = [[MLNVectorTileSource alloc] + initWithIdentifier:@"foursquare-10M" + configurationURLString: + @"pmtiles://https://oliverwipfli.ch/data/foursquare-os-places-10M-2024-11-20.pmtiles"]; - // Also works - // MLNVectorTileSource *foursquareSource = - // [[MLNVectorTileSource alloc] initWithIdentifier:@"foursquare-10M" - // tileURLTemplates:@[@"pmtiles://https://oliverwipfli.ch/data/foursquare-os-places-10M-2024-11-20.pmtiles"] - // options:nil]; + // Also works + // MLNVectorTileSource *foursquareSource = + // [[MLNVectorTileSource alloc] initWithIdentifier:@"foursquare-10M" + // tileURLTemplates:@[@"pmtiles://https://oliverwipfli.ch/data/foursquare-os-places-10M-2024-11-20.pmtiles"] + // options:nil]; - [self.mapView.style addSource:foursquareSource]; + [self.mapView.style addSource:foursquareSource]; - MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"foursquare-10M" source:foursquareSource]; - circleLayer.sourceLayerIdentifier = @"place"; - circleLayer.maximumZoomLevel = 11; - circleLayer.circleColor = [NSExpression expressionForConstantValue:[UIColor colorWithRed:0.8 green:0.2 blue:0.2 alpha:1.0]]; - circleLayer.circleOpacity = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", - @[@"linear"], - @[@"zoom"], - @6, @0.5, - @11, @0.5, - @14, @1.0 - ]]; - circleLayer.circleRadius = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", - @[@"linear"], - @[@"zoom"], - @6, @1, - @11, @3, - @16, @4, - @18, @8 - ]]; - [self.mapView.style addLayer:circleLayer]; + MLNCircleStyleLayer *circleLayer = + [[MLNCircleStyleLayer alloc] initWithIdentifier:@"foursquare-10M" source:foursquareSource]; + circleLayer.sourceLayerIdentifier = @"place"; + circleLayer.maximumZoomLevel = 11; + circleLayer.circleColor = [NSExpression expressionForConstantValue:[UIColor colorWithRed:0.8 + green:0.2 + blue:0.2 + alpha:1.0]]; + circleLayer.circleOpacity = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @6, @0.5, @11, @0.5, @14, @1.0 + ]]; + circleLayer.circleRadius = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @6, @1, @11, @3, @16, @4, @18, @8 + ]]; + [self.mapView.style addLayer:circleLayer]; } // Earthquake Heatmap - (void)addEarthquakeHeatmap { - NSURL *earthquakesURL = [NSURL URLWithString:@"https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson"]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"earthquakes" URL:earthquakesURL options:nil]; - [self.mapView.style addSource:source]; + NSURL *earthquakesURL = + [NSURL URLWithString:@"https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson"]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"earthquakes" + URL:earthquakesURL + options:nil]; + [self.mapView.style addSource:source]; - // Heatmap layer - visible at low zoom, fades out by zoom 9 - MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"earthquakes-heat" source:source]; - heatmapLayer.maximumZoomLevel = 9; + // Heatmap layer - visible at low zoom, fades out by zoom 9 + MLNHeatmapStyleLayer *heatmapLayer = + [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"earthquakes-heat" source:source]; + heatmapLayer.maximumZoomLevel = 9; - // Weight by earthquake magnitude - heatmapLayer.heatmapWeight = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"get", @"mag"], - @0, @0, - @6, @1 - ]]; + // Weight by earthquake magnitude + heatmapLayer.heatmapWeight = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"get", @"mag" ], @0, @0, @6, @1 + ]]; - // Increase intensity with zoom - heatmapLayer.heatmapIntensity = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"zoom"], - @0, @1, - @9, @3 - ]]; + // Increase intensity with zoom + heatmapLayer.heatmapIntensity = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @0, @1, @9, @3 + ]]; - // Color ramp from transparent blue to dark red - heatmapLayer.heatmapColor = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"heatmap-density"], - @0, @"rgba(33,102,172,0)", - @0.2, @"rgb(103,169,207)", - @0.4, @"rgb(209,229,240)", - @0.6, @"rgb(253,219,199)", - @0.8, @"rgb(239,138,98)", - @1, @"rgb(178,24,43)" - ]]; + // Color ramp from transparent blue to dark red + heatmapLayer.heatmapColor = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"heatmap-density" ], @0, @"rgba(33,102,172,0)", @0.2, + @"rgb(103,169,207)", @0.4, @"rgb(209,229,240)", @0.6, @"rgb(253,219,199)", @0.8, + @"rgb(239,138,98)", @1, @"rgb(178,24,43)" + ]]; - // Increase radius with zoom - heatmapLayer.heatmapRadius = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"zoom"], - @0, @2, - @9, @20 - ]]; + // Increase radius with zoom + heatmapLayer.heatmapRadius = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @0, @2, @9, @20 + ]]; - // Fade out heatmap at high zoom - heatmapLayer.heatmapOpacity = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"zoom"], - @7, @1, - @9, @0 - ]]; + // Fade out heatmap at high zoom + heatmapLayer.heatmapOpacity = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @7, @1, @9, @0 + ]]; - [self.mapView.style addLayer:heatmapLayer]; + [self.mapView.style addLayer:heatmapLayer]; - // Circle layer - visible at high zoom, fades in from zoom 7 - MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"earthquakes-circle" source:source]; + // Circle layer - visible at high zoom, fades in from zoom 7 + MLNCircleStyleLayer *circleLayer = + [[MLNCircleStyleLayer alloc] initWithIdentifier:@"earthquakes-circle" source:source]; - circleLayer.circleRadius = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"zoom"], - @7, @[@"interpolate", @[@"linear"], @[@"get", @"mag"], @1, @1, @6, @4], - @16, @[@"interpolate", @[@"linear"], @[@"get", @"mag"], @1, @5, @6, @50] - ]]; + circleLayer.circleRadius = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @7, + @[ @"interpolate", @[ @"linear" ], @[ @"get", @"mag" ], @1, @1, @6, @4 ], @16, + @[ @"interpolate", @[ @"linear" ], @[ @"get", @"mag" ], @1, @5, @6, @50 ] + ]]; - circleLayer.circleColor = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"get", @"mag"], - @1, @"rgba(33,102,172,0)", - @2, @"rgb(103,169,207)", - @3, @"rgb(209,229,240)", - @4, @"rgb(253,219,199)", - @5, @"rgb(239,138,98)", - @6, @"rgb(178,24,43)" - ]]; + circleLayer.circleColor = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"get", @"mag" ], @1, @"rgba(33,102,172,0)", @2, + @"rgb(103,169,207)", @3, @"rgb(209,229,240)", @4, @"rgb(253,219,199)", @5, @"rgb(239,138,98)", + @6, @"rgb(178,24,43)" + ]]; - circleLayer.circleOpacity = [NSExpression expressionWithMLNJSONObject:@[ - @"interpolate", @[@"linear"], @[@"zoom"], - @7, @0, - @8, @1 - ]]; + circleLayer.circleOpacity = [NSExpression expressionWithMLNJSONObject:@[ + @"interpolate", @[ @"linear" ], @[ @"zoom" ], @7, @0, @8, @1 + ]]; - circleLayer.circleStrokeColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; - circleLayer.circleStrokeWidth = [NSExpression expressionForConstantValue:@1]; + circleLayer.circleStrokeColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; + circleLayer.circleStrokeWidth = [NSExpression expressionForConstantValue:@1]; - [self.mapView.style insertLayer:circleLayer belowLayer:heatmapLayer]; + [self.mapView.style insertLayer:circleLayer belowLayer:heatmapLayer]; - // Move camera to show earthquake data (Central America / Pacific region) - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(15.0, -94.0) zoomLevel:5 animated:YES]; + // Move camera to show earthquake data (Central America / Pacific region) + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(15.0, -94.0) + zoomLevel:5 + animated:YES]; } -- (void)styleBuildingExtrusions -{ - MLNSource* source = [self.mapView.style sourceWithIdentifier:@"composite"]; - if (source) { +- (void)styleBuildingExtrusions { + MLNSource *source = [self.mapView.style sourceWithIdentifier:@"composite"]; + if (source) { + MLNFillExtrusionStyleLayer *layer = + [[MLNFillExtrusionStyleLayer alloc] initWithIdentifier:@"extrudedBuildings" source:source]; + layer.sourceLayerIdentifier = @"building"; + layer.predicate = + [NSPredicate predicateWithFormat:@"extrude == 'true' AND CAST(height, 'NSNumber') > 0"]; + layer.fillExtrusionBase = [NSExpression expressionForKeyPath:@"min_height"]; + layer.fillExtrusionHeight = [NSExpression expressionForKeyPath:@"height"]; - MLNFillExtrusionStyleLayer* layer = [[MLNFillExtrusionStyleLayer alloc] initWithIdentifier:@"extrudedBuildings" source:source]; - layer.sourceLayerIdentifier = @"building"; - layer.predicate = [NSPredicate predicateWithFormat:@"extrude == 'true' AND CAST(height, 'NSNumber') > 0"]; - layer.fillExtrusionBase = [NSExpression expressionForKeyPath:@"min_height"]; - layer.fillExtrusionHeight = [NSExpression expressionForKeyPath:@"height"]; + // Set the fill color to that of the existing building footprint layer, if it exists. + MLNFillStyleLayer *buildingLayer = + (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"]; + if (buildingLayer) { + if (buildingLayer.fillColor) { + layer.fillExtrusionColor = buildingLayer.fillColor; + } else { + layer.fillExtrusionColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; + } - // Set the fill color to that of the existing building footprint layer, if it exists. - MLNFillStyleLayer* buildingLayer = (MLNFillStyleLayer*)[self.mapView.style layerWithIdentifier:@"building"]; - if (buildingLayer) { - if (buildingLayer.fillColor) { - layer.fillExtrusionColor = buildingLayer.fillColor; - } else { - layer.fillExtrusionColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; - } - - layer.fillExtrusionOpacity = [NSExpression expressionForConstantValue:@0.75]; - } - - MLNStyleLayer* labelLayer = [self.mapView.style layerWithIdentifier:@"waterway-label"]; - if (labelLayer) { - [self.mapView.style insertLayer:layer belowLayer:labelLayer]; - } else { - [self.mapView.style addLayer:layer]; - } + layer.fillExtrusionOpacity = [NSExpression expressionForConstantValue:@0.75]; } -} -- (void)styleWaterLayer -{ - MLNFillStyleLayer *waterLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"water"]; - NSDictionary *waterColorStops = @{@6.0f: [UIColor yellowColor], - @8.0f: [UIColor blueColor], - @10.0f: [UIColor redColor], - @12.0f: [UIColor greenColor], - @14.0f: [UIColor blueColor]}; - NSExpression *fillColorExpression = [NSExpression mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression - withCurveType:MLNExpressionInterpolationModeLinear - parameters:nil - stops:[NSExpression expressionForConstantValue:waterColorStops]]; - waterLayer.fillColor = fillColorExpression; - - NSDictionary *fillAntialiasedStops = @{@11: @YES, - @12: @NO, - @13: @YES, - @14: @NO, - @15: @YES}; - waterLayer.fillAntialiased = [NSExpression mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression - fromExpression:[NSExpression expressionForConstantValue:@NO] - stops:[NSExpression expressionForConstantValue:fillAntialiasedStops]]; -} - -- (void)styleRoadLayer -{ - MLNLineStyleLayer *roadLayer = (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"road-primary"]; - roadLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor blackColor]]; - - NSDictionary *lineWidthStops = @{@5: @5, - @10: @15, - @15: @30}; - NSExpression *lineWidthExpression = [NSExpression expressionWithFormat: - @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", - lineWidthStops]; - roadLayer.lineWidth = lineWidthExpression; - roadLayer.lineGapWidth = lineWidthExpression; - - NSDictionary *roadLineColorStops = @{@10: [UIColor purpleColor], - @13: [UIColor yellowColor], - @16: [UIColor cyanColor]}; - roadLayer.lineColor = [NSExpression expressionWithFormat: - @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", - roadLineColorStops]; - - roadLayer.visible = YES; - roadLayer.maximumZoomLevel = 15; - roadLayer.minimumZoomLevel = 13; -} - -- (void)styleRasterLayer -{ - NSURL *rasterURL = [NSURL URLWithString:@"maptiler://sources/hybrid"]; - MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"my-raster-tile-source" configurationURL:rasterURL tileSize:512]; - [self.mapView.style addSource:rasterTileSource]; - - MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"my-raster-layer" source:rasterTileSource]; - NSDictionary *opacityStops = @{@20.0f: @1.0f, - @5.0f: @0.0f}; - rasterLayer.rasterOpacity = [NSExpression expressionWithFormat: - @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", - opacityStops]; - [self.mapView.style addLayer:rasterLayer]; -} - -- (void)styleShapeSource -{ - NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; - NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"ams" URL:geoJSONURL options:nil]; - [self.mapView.style addSource:source]; - - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"test" source:source]; - fillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]]; - [self.mapView.style addLayer:fillLayer]; - -} - -- (void)styleSymbolLayer -{ - if (auto *stateLayer = (MLNSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:@"state-label-lg"]) - { - stateLayer.textColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; - } -} - -- (void)styleBuildingLayer -{ - MLNTransition transition = { 5, 1 }; - self.mapView.style.transition = transition; - if (auto *buildingLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"]) - { - buildingLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]]; - } -} - -- (void)styleFerryLayer -{ - if (auto *ferryLineLayer = (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"ferry"]) - { - ferryLineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; - } -} - -- (void)removeParkLayer -{ - if (auto *parkLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"park"]) - { - [self.mapView.style removeLayer:parkLayer]; - } -} - -- (BOOL)loadStyleFromBundle:(NSString*)path -{ - NSString *resourcePath = [[NSBundle mainBundle] pathForResource:path ofType:@"json"]; - if (NSURL* url = resourcePath ? [NSURL fileURLWithPath:resourcePath] : nil) { - [self.mapView setStyleURL:url]; - return TRUE; + MLNStyleLayer *labelLayer = [self.mapView.style layerWithIdentifier:@"waterway-label"]; + if (labelLayer) { + [self.mapView.style insertLayer:layer belowLayer:labelLayer]; } else { - NSString *msg = [NSString stringWithFormat:@"Missing Style %@", path]; - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Style File" message:msg preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]]; - [self presentViewController:alertController animated:YES completion:nil]; - return FALSE; + [self.mapView.style addLayer:layer]; } + } } -- (void)styleFilteredFill -{ - // set style and focus on Texas - if (![self loadStyleFromBundle:@"fill_filter_style"]) { - return; - } - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(31, -100) zoomLevel:3 animated:NO]; +- (void)styleWaterLayer { + MLNFillStyleLayer *waterLayer = + (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"water"]; + NSDictionary *waterColorStops = @{ + @6.0f : [UIColor yellowColor], + @8.0f : [UIColor blueColor], + @10.0f : [UIColor redColor], + @12.0f : [UIColor greenColor], + @14.0f : [UIColor blueColor] + }; + NSExpression *fillColorExpression = [NSExpression + mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression + withCurveType:MLNExpressionInterpolationModeLinear + parameters:nil + stops:[NSExpression + expressionForConstantValue:waterColorStops]]; + waterLayer.fillColor = fillColorExpression; - // after slight delay, fill in Texas (atypical use; we want to clearly see the change for test purposes) - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ - { - MLNFillStyleLayer *statesLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"states"]; + NSDictionary *fillAntialiasedStops = @{@11 : @YES, @12 : @NO, @13 : @YES, @14 : @NO, @15 : @YES}; + waterLayer.fillAntialiased = [NSExpression + mgl_expressionForSteppingExpression:NSExpression.zoomLevelVariableExpression + fromExpression:[NSExpression expressionForConstantValue:@NO] + stops:[NSExpression + expressionForConstantValue:fillAntialiasedStops]]; +} + +- (void)styleRoadLayer { + MLNLineStyleLayer *roadLayer = + (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"road-primary"]; + roadLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor blackColor]]; + + NSDictionary *lineWidthStops = @{@5 : @5, @10 : @15, @15 : @30}; + NSExpression *lineWidthExpression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + lineWidthStops]; + roadLayer.lineWidth = lineWidthExpression; + roadLayer.lineGapWidth = lineWidthExpression; + + NSDictionary *roadLineColorStops = + @{@10 : [UIColor purpleColor], @13 : [UIColor yellowColor], @16 : [UIColor cyanColor]}; + roadLayer.lineColor = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + roadLineColorStops]; + + roadLayer.visible = YES; + roadLayer.maximumZoomLevel = 15; + roadLayer.minimumZoomLevel = 13; +} + +- (void)styleRasterLayer { + NSURL *rasterURL = [NSURL URLWithString:@"maptiler://sources/hybrid"]; + MLNRasterTileSource *rasterTileSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"my-raster-tile-source" + configurationURL:rasterURL + tileSize:512]; + [self.mapView.style addSource:rasterTileSource]; + + MLNRasterStyleLayer *rasterLayer = + [[MLNRasterStyleLayer alloc] initWithIdentifier:@"my-raster-layer" source:rasterTileSource]; + NSDictionary *opacityStops = @{@20.0f : @1.0f, @5.0f : @0.0f}; + rasterLayer.rasterOpacity = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", + opacityStops]; + [self.mapView.style addLayer:rasterLayer]; +} + +- (void)styleShapeSource { + NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" + ofType:@"geojson"]; + NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"ams" + URL:geoJSONURL + options:nil]; + [self.mapView.style addSource:source]; + + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"test" + source:source]; + fillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]]; + [self.mapView.style addLayer:fillLayer]; +} + +- (void)styleSymbolLayer { + if (auto *stateLayer = + (MLNSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:@"state-label-lg"]) { + stateLayer.textColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; + } +} + +- (void)styleBuildingLayer { + MLNTransition transition = {5, 1}; + self.mapView.style.transition = transition; + if (auto *buildingLayer = + (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"building"]) { + buildingLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor purpleColor]]; + } +} + +- (void)styleFerryLayer { + if (auto *ferryLineLayer = + (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"ferry"]) { + ferryLineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; + } +} + +- (void)removeParkLayer { + if (auto *parkLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"park"]) { + [self.mapView.style removeLayer:parkLayer]; + } +} + +- (BOOL)loadStyleFromBundle:(NSString *)path { + NSString *resourcePath = [[NSBundle mainBundle] pathForResource:path ofType:@"json"]; + if (NSURL *url = resourcePath ? [NSURL fileURLWithPath:resourcePath] : nil) { + [self.mapView setStyleURL:url]; + return TRUE; + } else { + NSString *msg = [NSString stringWithFormat:@"Missing Style %@", path]; + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:@"Style File" + message:msg + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" + style:UIAlertActionStyleCancel + handler:nil]]; + [self presentViewController:alertController animated:YES completion:nil]; + return FALSE; + } +} + +- (void)styleFilteredFill { + // set style and focus on Texas + if (![self loadStyleFromBundle:@"fill_filter_style"]) { + return; + } + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(31, -100) zoomLevel:3 animated:NO]; + + // after slight delay, fill in Texas (atypical use; we want to clearly see the change for test + // purposes) + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + MLNFillStyleLayer *statesLayer = + (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"states"]; // filter statesLayer.predicate = [NSPredicate predicateWithFormat:@"name == 'Texas'"]; @@ -1396,21 +1403,22 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { // paint properties statesLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; statesLayer.fillOpacity = [NSExpression expressionForConstantValue:@0.25]; - }); + }); } -- (void)styleFilteredLines -{ - // set style and focus on lower 48 - if (![self loadStyleFromBundle:@"line_filter_style"]) { - return; - } - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; +- (void)styleFilteredLines { + // set style and focus on lower 48 + if (![self loadStyleFromBundle:@"line_filter_style"]) { + return; + } + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; - // after slight delay, change styling for all Washington-named counties (atypical use; we want to clearly see the change for test purposes) - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ - { - MLNLineStyleLayer *countiesLayer = (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"counties"]; + // after slight delay, change styling for all Washington-named counties (atypical use; we want to + // clearly see the change for test purposes) + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + MLNLineStyleLayer *countiesLayer = + (MLNLineStyleLayer *)[self.mapView.style layerWithIdentifier:@"counties"]; // filter countiesLayer.predicate = [NSPredicate predicateWithFormat:@"NAME10 == 'Washington'"]; @@ -1419,1137 +1427,1201 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { countiesLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; countiesLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.75]; countiesLayer.lineWidth = [NSExpression expressionForConstantValue:@5]; - }); + }); } -- (void)styleNumericFilteredFills -{ - // set style and focus on lower 48 - if (![self loadStyleFromBundle:@"numeric_filter_style"]) { - return; - } - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; +- (void)styleNumericFilteredFills { + // set style and focus on lower 48 + if (![self loadStyleFromBundle:@"numeric_filter_style"]) { + return; + } + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40, -97) zoomLevel:5 animated:NO]; - // after slight delay, change styling for regions 200-299 (atypical use; we want to clearly see the change for test purposes) - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^ - { - MLNFillStyleLayer *regionsLayer = (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"regions"]; + // after slight delay, change styling for regions 200-299 (atypical use; we want to clearly see + // the change for test purposes) + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + MLNFillStyleLayer *regionsLayer = + (MLNFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"regions"]; // filter (testing both inline and format strings) - regionsLayer.predicate = [NSPredicate predicateWithFormat:@"CAST(HRRNUM, 'NSNumber') >= %@ AND CAST(HRRNUM, 'NSNumber') < 300", @(200)]; + regionsLayer.predicate = [NSPredicate + predicateWithFormat: + @"CAST(HRRNUM, 'NSNumber') >= %@ AND CAST(HRRNUM, 'NSNumber') < 300", @(200)]; // paint properties regionsLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor blueColor]]; regionsLayer.fillOpacity = [NSExpression expressionForConstantValue:@0.5]; - }); + }); } -- (void)styleQuery -{ - CGRect queryRect = CGRectInset(self.mapView.bounds, 100, 200); - NSArray *visibleFeatures = [self.mapView visibleFeaturesInRect:queryRect]; +- (void)styleQuery { + CGRect queryRect = CGRectInset(self.mapView.bounds, 100, 200); + NSArray *visibleFeatures = [self.mapView visibleFeaturesInRect:queryRect]; - NSString *querySourceID = @"query-source-id"; - NSString *queryLayerID = @"query-layer-id"; + NSString *querySourceID = @"query-source-id"; + NSString *queryLayerID = @"query-layer-id"; - // RTE if you don't remove the layer first - // RTE if you pass a nill layer to remove layer - MLNStyleLayer *layer = [self.mapView.style layerWithIdentifier:queryLayerID]; - if (layer) { - [self.mapView.style removeLayer:layer]; - } + // RTE if you don't remove the layer first + // RTE if you pass a nill layer to remove layer + MLNStyleLayer *layer = [self.mapView.style layerWithIdentifier:queryLayerID]; + if (layer) { + [self.mapView.style removeLayer:layer]; + } - // RTE if you pass a nill source to remove source - MLNSource *source = [self.mapView.style sourceWithIdentifier:querySourceID]; - if (source) { - [self.mapView.style removeSource:source]; - } + // RTE if you pass a nill source to remove source + MLNSource *source = [self.mapView.style sourceWithIdentifier:querySourceID]; + if (source) { + [self.mapView.style removeSource:source]; + } - dispatch_async(dispatch_get_main_queue(), ^{ - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:querySourceID features:visibleFeatures options:nil]; - [self.mapView.style addSource:shapeSource]; + dispatch_async(dispatch_get_main_queue(), ^{ + MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:querySourceID + features:visibleFeatures + options:nil]; + [self.mapView.style addSource:shapeSource]; - MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:queryLayerID source:shapeSource]; - fillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor blueColor]]; - fillLayer.fillOpacity = [NSExpression expressionForConstantValue:@0.5]; - [self.mapView.style addLayer:fillLayer]; - }); + MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:queryLayerID + source:shapeSource]; + fillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor blueColor]]; + fillLayer.fillOpacity = [NSExpression expressionForConstantValue:@0.5]; + [self.mapView.style addLayer:fillLayer]; + }); } -- (void)styleFeature -{ - self.mapView.zoomLevel = 10; - self.mapView.centerCoordinate = CLLocationCoordinate2DMake(51.068585180672635, -114.06074523925781); +- (void)styleFeature { + self.mapView.zoomLevel = 10; + self.mapView.centerCoordinate = + CLLocationCoordinate2DMake(51.068585180672635, -114.06074523925781); - CLLocationCoordinate2D leafCoords[] = { - {50.9683733218221,-114.07035827636719}, - {51.02325750523972,-114.06967163085938}, - {51.009434536947786,-114.14245605468749}, - {51.030599281184124,-114.12597656249999}, - {51.060386316691016,-114.21043395996094}, - {51.063838646941576,-114.17816162109375}, - {51.08152779888779,-114.19876098632812}, - {51.08066507029602,-114.16854858398438}, - {51.09662294502995,-114.17472839355469}, - {51.07764539352731,-114.114990234375}, - {51.13670896949613,-114.12391662597656}, - {51.13369295212583,-114.09576416015624}, - {51.17546878815025,-114.07585144042969}, - {51.140155605265896,-114.04632568359375}, - {51.15049396880196,-114.01542663574219}, - {51.088860342359965,-114.00924682617186}, - {51.12205789681453,-113.94813537597656}, - {51.106539930027225,-113.94882202148438}, - {51.117747873223344,-113.92616271972656}, - {51.10093493903458,-113.92616271972656}, - {51.10697105503078,-113.90625}, - {51.09144802136697,-113.9117431640625}, - {51.04916446529361,-113.97010803222655}, - {51.045279344649146,-113.9398956298828}, - {51.022825599852496,-114.06211853027344}, - {51.045279344649146,-113.9398956298828}, - {51.022825599852496,-114.06211853027344}, - {51.022825599852496,-114.06280517578125}, - {50.968805734317804,-114.06280517578125}, - {50.9683733218221,-114.07035827636719}, - }; - NSUInteger coordsCount = sizeof(leafCoords) / sizeof(leafCoords[0]); + CLLocationCoordinate2D leafCoords[] = { + {50.9683733218221, -114.07035827636719}, {51.02325750523972, -114.06967163085938}, + {51.009434536947786, -114.14245605468749}, {51.030599281184124, -114.12597656249999}, + {51.060386316691016, -114.21043395996094}, {51.063838646941576, -114.17816162109375}, + {51.08152779888779, -114.19876098632812}, {51.08066507029602, -114.16854858398438}, + {51.09662294502995, -114.17472839355469}, {51.07764539352731, -114.114990234375}, + {51.13670896949613, -114.12391662597656}, {51.13369295212583, -114.09576416015624}, + {51.17546878815025, -114.07585144042969}, {51.140155605265896, -114.04632568359375}, + {51.15049396880196, -114.01542663574219}, {51.088860342359965, -114.00924682617186}, + {51.12205789681453, -113.94813537597656}, {51.106539930027225, -113.94882202148438}, + {51.117747873223344, -113.92616271972656}, {51.10093493903458, -113.92616271972656}, + {51.10697105503078, -113.90625}, {51.09144802136697, -113.9117431640625}, + {51.04916446529361, -113.97010803222655}, {51.045279344649146, -113.9398956298828}, + {51.022825599852496, -114.06211853027344}, {51.045279344649146, -113.9398956298828}, + {51.022825599852496, -114.06211853027344}, {51.022825599852496, -114.06280517578125}, + {50.968805734317804, -114.06280517578125}, {50.9683733218221, -114.07035827636719}, + }; + NSUInteger coordsCount = sizeof(leafCoords) / sizeof(leafCoords[0]); - MLNPolygonFeature *feature = [MLNPolygonFeature polygonWithCoordinates:leafCoords count:coordsCount]; - feature.identifier = @"leaf-feature"; - feature.attributes = @{@"color": @"red"}; + MLNPolygonFeature *feature = [MLNPolygonFeature polygonWithCoordinates:leafCoords + count:coordsCount]; + feature.identifier = @"leaf-feature"; + feature.attributes = @{@"color" : @"red"}; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"leaf-source" shape:feature options:nil]; - [self.mapView.style addSource:source]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"leaf-source" + shape:feature + options:nil]; + [self.mapView.style addSource:source]; - MLNFillStyleLayer *layer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"leaf-fill-layer" source:source]; - layer.predicate = [NSPredicate predicateWithFormat:@"color = 'red'"]; - layer.fillColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; - [self.mapView.style addLayer:layer]; + MLNFillStyleLayer *layer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"leaf-fill-layer" + source:source]; + layer.predicate = [NSPredicate predicateWithFormat:@"color = 'red'"]; + layer.fillColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; + [self.mapView.style addLayer:layer]; - NSString *geoJSON = @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}"; + NSString *geoJSON = + @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": " + @"\"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}"; - NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; - MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; - MLNShapeSource *pointSource = [[MLNShapeSource alloc] initWithIdentifier:@"leaf-point-source" shape:shape options:nil]; - [self.mapView.style addSource:pointSource]; + NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; + MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; + MLNShapeSource *pointSource = [[MLNShapeSource alloc] initWithIdentifier:@"leaf-point-source" + shape:shape + options:nil]; + [self.mapView.style addSource:pointSource]; - MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"leaf-circle-layer" source:pointSource]; - circleLayer.circleColor = [NSExpression expressionForConstantValue:[UIColor greenColor]]; - circleLayer.predicate = [NSPredicate predicateWithFormat:@"color = 'green'"]; - [self.mapView.style addLayer:circleLayer]; + MLNCircleStyleLayer *circleLayer = + [[MLNCircleStyleLayer alloc] initWithIdentifier:@"leaf-circle-layer" source:pointSource]; + circleLayer.circleColor = [NSExpression expressionForConstantValue:[UIColor greenColor]]; + circleLayer.predicate = [NSPredicate predicateWithFormat:@"color = 'green'"]; + [self.mapView.style addLayer:circleLayer]; + CLLocationCoordinate2D squareCoords[] = { + {51.056070541830934, -114.0274429321289}, {51.07937094724242, -114.0274429321289}, + {51.07937094724242, -113.98761749267578}, {51.05607054183093, -113.98761749267578}, + {51.056070541830934, -114.0274429321289}, + }; + MLNPolygon *polygon = + [MLNPolygon polygonWithCoordinates:squareCoords + count:sizeof(squareCoords) / sizeof(squareCoords[0])]; + MLNShapeSource *plainShapeSource = + [[MLNShapeSource alloc] initWithIdentifier:@"leaf-plain-shape-source" + shape:polygon + options:nil]; + [self.mapView.style addSource:plainShapeSource]; - CLLocationCoordinate2D squareCoords[] = { - {51.056070541830934, -114.0274429321289}, - {51.07937094724242, -114.0274429321289}, - {51.07937094724242, -113.98761749267578}, - {51.05607054183093, -113.98761749267578}, - {51.056070541830934, -114.0274429321289}, - }; - MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:squareCoords count:sizeof(squareCoords)/sizeof(squareCoords[0])]; - MLNShapeSource *plainShapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"leaf-plain-shape-source" shape:polygon options:nil]; - [self.mapView.style addSource:plainShapeSource]; - - MLNFillStyleLayer *plainFillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"leaf-plain-fill-layer" source:plainShapeSource]; - plainFillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor yellowColor]]; - [self.mapView.style addLayer:plainFillLayer]; + MLNFillStyleLayer *plainFillLayer = + [[MLNFillStyleLayer alloc] initWithIdentifier:@"leaf-plain-fill-layer" + source:plainShapeSource]; + plainFillLayer.fillColor = [NSExpression expressionForConstantValue:[UIColor yellowColor]]; + [self.mapView.style addLayer:plainFillLayer]; } -- (void)updateShapeSourceData -{ - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.329795743702064, -107.75390625) zoomLevel:11 animated:NO]; +- (void)updateShapeSourceData { + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.329795743702064, -107.75390625) + zoomLevel:11 + animated:NO]; - NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}"; + 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]; - MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-id" shape:shape options:nil]; - [self.mapView.style addSource:source]; + NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; + MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:NULL]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-id" + shape:shape + options:nil]; + [self.mapView.style addSource:source]; - MLNLineStyleLayer *layer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-id" source:source]; - [self.mapView.style addLayer:layer]; + MLNLineStyleLayer *layer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-id" + source:source]; + [self.mapView.style addLayer:layer]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - NSString *updatedGeoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-109.34814453125,37.64903402157866]]}}]}"; + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + NSString *updatedGeoJSON = + @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": " + @"\"Feature\",\"properties\": {},\"geometry\": {\"type\": " + @"\"LineString\",\"coordinates\": " + @"[[-107.75390625,40.329795743702064],[-109.34814453125,37.64903402157866]]}}]}"; NSData *updatedData = [updatedGeoJSON dataUsingEncoding:NSUTF8StringEncoding]; - MLNShape *updatedShape = [MLNShape shapeWithData:updatedData encoding:NSUTF8StringEncoding error:NULL]; + MLNShape *updatedShape = [MLNShape shapeWithData:updatedData + encoding:NSUTF8StringEncoding + error:NULL]; source.shape = updatedShape; - }); + }); } -- (void)updateShapeSourceURL -{ - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(48.668731, -122.857151) zoomLevel:11 animated:NO]; +- (void)updateShapeSourceURL { + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(48.668731, -122.857151) + zoomLevel:11 + animated:NO]; - NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"polyline" ofType:@"geojson"]; - NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-url-id" URL:geoJSONURL options:nil]; - [self.mapView.style addSource:source]; + NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"polyline" + ofType:@"geojson"]; + NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-url-id" + URL:geoJSONURL + options:nil]; + [self.mapView.style addSource:source]; - MLNLineStyleLayer *layer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-url-id" source:source]; - [self.mapView.style addLayer:layer]; + MLNLineStyleLayer *layer = + [[MLNLineStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-url-id" source:source]; + [self.mapView.style addLayer:layer]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(41.563986787078704, -75.04843935793578) zoomLevel:8 animated:NO]; + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self.mapView + setCenterCoordinate:CLLocationCoordinate2DMake(41.563986787078704, -75.04843935793578) + zoomLevel:8 + animated:NO]; - NSString *threeStatesFilePath = [[NSBundle bundleForClass:self.class] pathForResource:@"threestates" ofType:@"geojson"]; + NSString *threeStatesFilePath = + [[NSBundle bundleForClass:self.class] pathForResource:@"threestates" ofType:@"geojson"]; NSURL *updatedGeoJSONURL = [NSURL fileURLWithPath:threeStatesFilePath]; source.URL = updatedGeoJSONURL; - }); + }); } -- (void)updateShapeSourceFeatures -{ - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-41.1520, 288.6592) zoomLevel:10 animated:NO]; +- (void)updateShapeSourceFeatures { + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-41.1520, 288.6592) + zoomLevel:10 + animated:NO]; - CLLocationCoordinate2D smallBox[] = { - {-41.14763798539186, 288.68019104003906}, - {-41.140915920129665, 288.68019104003906}, - {-41.140915920129665, 288.6887741088867}, - {-41.14763798539186, 288.6887741088867}, - {-41.14763798539186, 288.68019104003906} - }; + CLLocationCoordinate2D smallBox[] = {{-41.14763798539186, 288.68019104003906}, + {-41.140915920129665, 288.68019104003906}, + {-41.140915920129665, 288.6887741088867}, + {-41.14763798539186, 288.6887741088867}, + {-41.14763798539186, 288.68019104003906}}; - CLLocationCoordinate2D largeBox[] = { - {-41.17710352162799, 288.67298126220703}, - {-41.13962313627545, 288.67298126220703}, - {-41.13962313627545, 288.7261962890625}, - {-41.17710352162799, 288.7261962890625}, - {-41.17710352162799, 288.67298126220703} - }; + CLLocationCoordinate2D largeBox[] = {{-41.17710352162799, 288.67298126220703}, + {-41.13962313627545, 288.67298126220703}, + {-41.13962313627545, 288.7261962890625}, + {-41.17710352162799, 288.7261962890625}, + {-41.17710352162799, 288.67298126220703}}; - MLNPolygonFeature *smallBoxFeature = [MLNPolygonFeature polygonWithCoordinates:smallBox count:sizeof(smallBox)/sizeof(smallBox[0])]; - MLNPolygonFeature *largeBoxFeature = [MLNPolygonFeature polygonWithCoordinates:largeBox count:sizeof(largeBox)/sizeof(largeBox[0])]; + MLNPolygonFeature *smallBoxFeature = + [MLNPolygonFeature polygonWithCoordinates:smallBox + count:sizeof(smallBox) / sizeof(smallBox[0])]; + MLNPolygonFeature *largeBoxFeature = + [MLNPolygonFeature polygonWithCoordinates:largeBox + count:sizeof(largeBox) / sizeof(largeBox[0])]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-features-id" - shape:smallBoxFeature - options:nil]; - [self.mapView.style addSource:source]; + MLNShapeSource *source = + [[MLNShapeSource alloc] initWithIdentifier:@"mutable-data-source-features-id" + shape:smallBoxFeature + options:nil]; + [self.mapView.style addSource:source]; - MLNFillStyleLayer *layer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-features-id" source:source]; - layer.fillColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; + MLNFillStyleLayer *layer = + [[MLNFillStyleLayer alloc] initWithIdentifier:@"mutable-data-layer-features-id" + source:source]; + layer.fillColor = [NSExpression expressionForConstantValue:[UIColor redColor]]; + [self.mapView.style addLayer:layer]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + source.shape = largeBoxFeature; + }); +} + +- (void)styleDynamicPointCollection { + [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) + zoomLevel:14 + animated:NO]; + + CLLocationCoordinate2D coordinates[] = { + {37.00145594210082, -109.04960632324219}, + {37.00173012609867, -109.0404224395752}, + {36.99453246847359, -109.04960632324219}, + {36.99508088541243, -109.04007911682129}, + }; + MLNPointCollectionFeature *feature = + [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:4]; + MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"wiggle-source" + shape:feature + options:nil]; + [self.mapView.style addSource:source]; + + MLNCircleStyleLayer *layer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"wiggle-layer" + source:source]; + [self.mapView.style addLayer:layer]; +} + +- (void)styleVectorTileSource { + NSURL *url = [[NSURL alloc] initWithString:@"maptiler://source/hillshade"]; + MLNVectorTileSource *vectorTileSource = + [[MLNVectorTileSource alloc] initWithIdentifier:@"style-vector-tile-source-id" + configurationURL:url]; + [self.mapView.style addSource:vectorTileSource]; + + MLNBackgroundStyleLayer *backgroundLayer = + [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"style-vector-background-layer-id"]; + backgroundLayer.backgroundColor = [NSExpression expressionForConstantValue:[UIColor blackColor]]; + [self.mapView.style addLayer:backgroundLayer]; + + MLNLineStyleLayer *lineLayer = + [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-vector-line-layer-id" + source:vectorTileSource]; + lineLayer.sourceLayerIdentifier = @"contour"; + lineLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; + lineLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; + lineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor greenColor]]; + + [self.mapView.style addLayer:lineLayer]; +} + +- (void)styleRasterTileSource { + NSString *tileURL = + [NSString stringWithFormat: + @"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}%@.jpg", + UIScreen.mainScreen.nativeScale > 1 ? @"@2x" : @""]; + MLNRasterTileSource *rasterTileSource = + [[MLNRasterTileSource alloc] initWithIdentifier:@"style-raster-tile-source-id" + tileURLTemplates:@[ tileURL ] + options:@{ + MLNTileSourceOptionTileSize : @256, + }]; + [self.mapView.style addSource:rasterTileSource]; + + MLNRasterStyleLayer *rasterLayer = + [[MLNRasterStyleLayer alloc] initWithIdentifier:@"style-raster-layer-id" + source:rasterTileSource]; + [self.mapView.style addLayer:rasterLayer]; +} + +- (NSURL *)radarImageURL:(int)index { + return [NSURL + URLWithString: + [NSString stringWithFormat:@"https://maplibre.org/maplibre-gl-js/docs/assets/radar%d.gif", + index]]; +} + +- (void)styleImageSource { + MLNCoordinateQuad coordinateQuad = { + {46.437, -80.425}, {37.936, -80.425}, {37.936, -71.516}, {46.437, -71.516}}; + + MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"style-image-source-id" + coordinateQuad:coordinateQuad + URL:[self radarImageURL:0]]; + + [self.mapView.style addSource:imageSource]; + + MLNRasterStyleLayer *rasterLayer = + [[MLNRasterStyleLayer alloc] initWithIdentifier:@"style-raster-image-layer-id" + source:imageSource]; + [self.mapView.style addLayer:rasterLayer]; + + [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(updateAnimatedImageSource:) + userInfo:imageSource + repeats:YES]; + + const CGFloat maximumPadding = 50; + const CGSize frameSize = self.mapView.frame.size; + const CGFloat yPadding = + (frameSize.height / 5 <= maximumPadding) ? (frameSize.height / 5) : maximumPadding; + const CGFloat xPadding = + (frameSize.width / 5 <= maximumPadding) ? (frameSize.width / 5) : maximumPadding; + [self.mapView setVisibleCoordinateBounds:MLNCoordinateBoundsMake(coordinateQuad.bottomLeft, + coordinateQuad.topRight) + edgePadding:UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding) + animated:YES + completionHandler:nil]; +} + +- (void)updateAnimatedImageSource:(NSTimer *)timer { + MLNImageSource *imageSource = (MLNImageSource *)timer.userInfo; + if (![self.mapView.style sourceWithIdentifier:imageSource.identifier]) { + // the source has been removed, probably by reloading the style, if we try to update + // it now, we will crash with 'This source got invalidated after the style change' + [timer invalidate]; + return; + } + + static int radarSuffix = 0; + [imageSource setValue:[self radarImageURL:radarSuffix] forKey:@"URL"]; + radarSuffix = (radarSuffix + 1) % 5; +} + +- (void)toggleStyleLabelsLanguage { + _localizingLabels = !_localizingLabels; + [self.mapView.style localizeLabelsIntoLocale:_localizingLabels + ? [NSLocale localeWithLocaleIdentifier:@"mul"] + : nil]; +} + +- (void)styleLineGradient { + CLLocationCoordinate2D coords[] = { + {43.84455590478528, 10.504238605499268}, {43.84385562343126, 10.504125952720642}, + {43.84388657526694, 10.503299832344055}, {43.84332557075269, 10.503235459327698}, + {43.843441641085036, 10.502264499664307}, {43.84396395478592, 10.50242006778717}, + {43.84406067904351, 10.501744151115416}, {43.84422317544319, 10.501792430877686}}; + NSInteger count = sizeof(coords) / sizeof(coords[0]); + + [self.mapView setCenterCoordinate:coords[0] zoomLevel:16 animated:YES]; + + MLNPolylineFeature *routeLine = [MLNPolylineFeature polylineWithCoordinates:coords count:count]; + + NSDictionary *sourceOptions = @{MLNShapeSourceOptionLineDistanceMetrics : @YES}; + + MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" + shape:routeLine + options:sourceOptions]; + + MLNLineStyleLayer *baseRouteLayer = + [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource]; + baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]]; + baseRouteLayer.lineWidth = [NSExpression expressionForConstantValue:@20]; + baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.95]; + baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; + baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; + + MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" + source:routeSource]; + routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; + routeLayer.lineWidth = [NSExpression expressionForConstantValue:@15]; + routeLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.8]; + routeLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; + routeLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; + // Create stops dictionary + NSDictionary *stops = @{ + @0 : [UIColor blueColor], + @0.1 : [UIColor colorWithRed:25 / 255.0 green:41 / 255.0 blue:88 / 255.0 alpha:1.0], + @0.3 : [UIColor cyanColor], + @0.5 : [UIColor greenColor], + @0.7 : [UIColor yellowColor], + @1 : [UIColor redColor], + }; + // Create an expression that will interpolate the color of the line + // (mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@)) + NSExpression *lineGradientExpression = [NSExpression + expressionWithFormat: + @"mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@)", + stops]; + routeLayer.lineGradient = lineGradientExpression; + + [self removeLayer:baseRouteLayer.identifier]; + [self removeLayer:routeLayer.identifier]; + [self removeSource:routeSource.identifier]; + [self.mapView.style addSource:routeSource]; + [self.mapView.style addLayer:baseRouteLayer]; + [self.mapView.style addLayer:routeLayer]; +} + +- (void)addCustomDrawableLayer { + // Create a CustomLayer that uses the Drawable/Builder toolkit to generate and render geometry + ExampleCustomDrawableStyleLayer *layer = + [[ExampleCustomDrawableStyleLayer alloc] initWithIdentifier:@"custom-drawable-layer"]; + + if (layer) { [self.mapView.style addLayer:layer]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - source.shape = largeBoxFeature; - }); + } } -- (void)styleDynamicPointCollection -{ - [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO]; +- (void)removeSource:(NSString *)ident { + if (MLNSource *source = [self.mapView.style sourceWithIdentifier:ident]) { + [self.mapView.style removeSource:source]; + } +} - CLLocationCoordinate2D coordinates[] = { - {37.00145594210082, -109.04960632324219}, - {37.00173012609867, -109.0404224395752}, - {36.99453246847359, -109.04960632324219}, - {36.99508088541243, -109.04007911682129}, - }; - MLNPointCollectionFeature *feature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:4]; - MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"wiggle-source" shape:feature options:nil]; - [self.mapView.style addSource:source]; +- (void)removeLayer:(NSString *)ident { + if (MLNStyleLayer *layer = [self.mapView.style layerWithIdentifier:ident]) { + [self.mapView.style removeLayer:layer]; + } +} - MLNCircleStyleLayer *layer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"wiggle-layer" source:source]; +- (void)styleRouteLine { + CLLocationCoordinate2D coords[] = { + {43.84455590478528, 10.504238605499268}, {43.84385562343126, 10.504125952720642}, + {43.84388657526694, 10.503299832344055}, {43.84332557075269, 10.503235459327698}, + {43.843441641085036, 10.502264499664307}, {43.84396395478592, 10.50242006778717}, + {43.84406067904351, 10.501744151115416}, {43.84422317544319, 10.501792430877686}}; + NSInteger count = sizeof(coords) / sizeof(coords[0]); + + [self.mapView setCenterCoordinate:coords[0] zoomLevel:16 animated:YES]; + + MLNPolylineFeature *routeLine = [MLNPolylineFeature polylineWithCoordinates:coords count:count]; + + MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" + shape:routeLine + options:nil]; + + MLNLineStyleLayer *baseRouteLayer = + [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource]; + baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]]; + baseRouteLayer.lineWidth = [NSExpression expressionForConstantValue:@20]; + baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.5]; + baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; + baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; + + MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" + source:routeSource]; + routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; + routeLayer.lineWidth = [NSExpression expressionForConstantValue:@15]; + routeLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.8]; + routeLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; + routeLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; + + [self removeLayer:baseRouteLayer.identifier]; + [self removeLayer:routeLayer.identifier]; + [self removeSource:routeSource.identifier]; + [self.mapView.style addSource:routeSource]; + [self.mapView.style addLayer:baseRouteLayer]; + [self.mapView.style addLayer:routeLayer]; +} + +- (void)styleAddCustomTriangleLayer { + if (CustomStyleLayerExample *layer = + [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]) { [self.mapView.style addLayer:layer]; + } } -- (void)styleVectorTileSource -{ - NSURL *url = [[NSURL alloc] initWithString:@"maptiler://source/hillshade"]; - MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"style-vector-tile-source-id" configurationURL:url]; - [self.mapView.style addSource:vectorTileSource]; +- (void)stylePolygonWithDDS { + CLLocationCoordinate2D leftCoords[] = { + {37.73081027834234, -122.49412536621094}, {37.7566013348511, -122.49412536621094}, + {37.7566013348511, -122.46253967285156}, {37.73081027834234, -122.46253967285156}, + {37.73081027834234, -122.49412536621094}, + }; + CLLocationCoordinate2D rightCoords[] = { + {37.73135334055843, -122.44640350341795}, {37.75741564287944, -122.44640350341795}, + {37.75741564287944, -122.41310119628906}, {37.73135334055843, -122.41310119628906}, + {37.73135334055843, -122.44640350341795}, + }; + MLNPolygonFeature *leftFeature = [MLNPolygonFeature polygonWithCoordinates:leftCoords count:5]; + leftFeature.attributes = @{@"fill" : @(YES)}; - MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"style-vector-background-layer-id"]; - backgroundLayer.backgroundColor = [NSExpression expressionForConstantValue:[UIColor blackColor]]; - [self.mapView.style addLayer:backgroundLayer]; + MLNPolygonFeature *rightFeature = [MLNPolygonFeature polygonWithCoordinates:rightCoords count:5]; + rightFeature.attributes = @{@"opacity" : @(0.5)}; - MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-vector-line-layer-id" source:vectorTileSource]; - lineLayer.sourceLayerIdentifier = @"contour"; - lineLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; - lineLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - lineLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor greenColor]]; + MLNShapeSource *shapeSource = + [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" + features:@[ leftFeature, rightFeature ] + options:nil]; + [self.mapView.style addSource:shapeSource]; - [self.mapView.style addLayer:lineLayer]; + // source, categorical function that sets any feature with a "fill" attribute value of true to red + // color and anything without to green + MLNFillStyleLayer *fillStyleLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" + source:shapeSource]; + fillStyleLayer.fillColor = [NSExpression + mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"fill == YES"] + trueExpression:[NSExpression expressionForConstantValue:[UIColor greenColor]] + falseExpresssion:[NSExpression expressionForConstantValue:[UIColor redColor]]]; + + // source, identity function that sets any feature with an "opacity" attribute to use that value + // and anything without to 1.0 + fillStyleLayer.fillOpacity = + [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"opacity != nil"] + trueExpression:[NSExpression expressionForKeyPath:@"opacity"] + falseExpresssion:[NSExpression expressionForConstantValue:@1.0]]; + [self.mapView.style addLayer:fillStyleLayer]; } -- (void)styleRasterTileSource -{ - NSString *tileURL = [NSString stringWithFormat:@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}%@.jpg", UIScreen.mainScreen.nativeScale > 1 ? @"@2x" : @""]; - MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"style-raster-tile-source-id" tileURLTemplates:@[tileURL] options:@{ - MLNTileSourceOptionTileSize: @256, - }]; - [self.mapView.style addSource:rasterTileSource]; - - MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"style-raster-layer-id" source:rasterTileSource]; - [self.mapView.style addLayer:rasterLayer]; +- (void)addLatLonGrid { + MLNComputedShapeSource *source = [[MLNComputedShapeSource alloc] + initWithIdentifier:@"latlon" + options:@{MLNShapeSourceOptionMaximumZoomLevel : @14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"latlonlines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MLNSymbolStyleLayer *labelLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"latlonlabels" + source:source]; + labelLayer.text = [NSExpression expressionForKeyPath:@"value"]; + [self.mapView.style addLayer:labelLayer]; } -- (NSURL*)radarImageURL:(int)index -{ - return [NSURL URLWithString: - [NSString stringWithFormat:@"https://maplibre.org/maplibre-gl-js/docs/assets/radar%d.gif", index]]; -} +- (NSString *)bestLanguageForUser { + // https://www.mapbox.com/vector-tiles/mapbox-streets-v8/#name-text--name_lang-code-text + NSArray *supportedLanguages = @[ + @"ar", @"de", @"en", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"zh", @"zh-Hans", @"zh-Hant" + ]; + NSArray *preferredLanguages = + [NSBundle preferredLocalizationsFromArray:supportedLanguages + forPreferences:[NSLocale preferredLanguages]]; + NSString *mostSpecificLanguage; -- (void)styleImageSource -{ - MLNCoordinateQuad coordinateQuad = { - { 46.437, -80.425 }, - { 37.936, -80.425 }, - { 37.936, -71.516 }, - { 46.437, -71.516 } }; - - MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"style-image-source-id" - coordinateQuad:coordinateQuad - URL:[self radarImageURL:0]]; - - [self.mapView.style addSource:imageSource]; - - MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"style-raster-image-layer-id" source:imageSource]; - [self.mapView.style addLayer:rasterLayer]; - - [NSTimer scheduledTimerWithTimeInterval:1.0 - target:self - selector:@selector(updateAnimatedImageSource:) - userInfo:imageSource - repeats:YES]; - - const CGFloat maximumPadding = 50; - const CGSize frameSize = self.mapView.frame.size; - const CGFloat yPadding = (frameSize.height / 5 <= maximumPadding) ? (frameSize.height / 5) : maximumPadding; - const CGFloat xPadding = (frameSize.width / 5 <= maximumPadding) ? (frameSize.width / 5) : maximumPadding; - [self.mapView setVisibleCoordinateBounds:MLNCoordinateBoundsMake(coordinateQuad.bottomLeft, coordinateQuad.topRight) - edgePadding:UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding) - animated:YES - completionHandler:nil]; -} - - -- (void)updateAnimatedImageSource:(NSTimer *)timer -{ - MLNImageSource *imageSource = (MLNImageSource *)timer.userInfo; - if (![self.mapView.style sourceWithIdentifier:imageSource.identifier]) { - // the source has been removed, probably by reloading the style, if we try to update - // it now, we will crash with 'This source got invalidated after the style change' - [timer invalidate]; - return; + for (NSString *language in preferredLanguages) { + if (language.length > mostSpecificLanguage.length) { + mostSpecificLanguage = language; } + } - static int radarSuffix = 0; - [imageSource setValue:[self radarImageURL:radarSuffix] forKey:@"URL"]; - radarSuffix = (radarSuffix + 1) % 5; + return mostSpecificLanguage ?: @"en"; } --(void)toggleStyleLabelsLanguage -{ - _localizingLabels = !_localizingLabels; - [self.mapView.style localizeLabelsIntoLocale:_localizingLabels ? [NSLocale localeWithLocaleIdentifier:@"mul"] : nil]; +- (IBAction)startWorldTour { + _isTouringWorld = YES; + + [self.mapView removeAnnotations:self.mapView.annotations]; + NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]); + NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations]; + for (NSUInteger i = 0; i < numberOfAnnotations; i++) { + MBXDroppedPinAnnotation *annotation = [[MBXDroppedPinAnnotation alloc] init]; + annotation.coordinate = WorldTourDestinations[i]; + [annotations addObject:annotation]; + } + [self.mapView addAnnotations:annotations]; + [self continueWorldTourWithRemainingAnnotations:annotations]; } -- (void)styleLineGradient -{ - CLLocationCoordinate2D coords[] = { - { 43.84455590478528, 10.504238605499268 }, - { 43.84385562343126, 10.504125952720642 }, - { 43.84388657526694, 10.503299832344055 }, - { 43.84332557075269, 10.503235459327698 }, - { 43.843441641085036, 10.502264499664307 }, - { 43.84396395478592, 10.50242006778717 }, - { 43.84406067904351, 10.501744151115416 }, - { 43.84422317544319, 10.501792430877686 } - }; - NSInteger count = sizeof(coords) / sizeof(coords[0]); +- (void)continueWorldTourWithRemainingAnnotations: + (NSMutableArray *)annotations { + MLNPointAnnotation *nextAnnotation = annotations.firstObject; + if (!nextAnnotation || !_isTouringWorld) { + _isTouringWorld = NO; + return; + } - [self.mapView setCenterCoordinate:coords[0] zoomLevel:16 animated:YES]; - - MLNPolylineFeature *routeLine = [MLNPolylineFeature polylineWithCoordinates:coords count:count]; - - NSDictionary *sourceOptions = @{ MLNShapeSourceOptionLineDistanceMetrics: @YES }; - - MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" shape:routeLine options:sourceOptions]; - - MLNLineStyleLayer *baseRouteLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource]; - baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]]; - baseRouteLayer.lineWidth = [NSExpression expressionForConstantValue:@20]; - baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.95]; - baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; - - MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" source:routeSource]; - routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; - routeLayer.lineWidth = [NSExpression expressionForConstantValue:@15]; - routeLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.8]; - routeLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - routeLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; - // Create stops dictionary - NSDictionary *stops = @{ - @0: [UIColor blueColor], - @0.1: [UIColor colorWithRed:25 / 255.0 green:41 /255.0 blue:88 / 255.0 alpha:1.0], - @0.3: [UIColor cyanColor], - @0.5: [UIColor greenColor], - @0.7: [UIColor yellowColor], - @1: [UIColor redColor], - }; - // Create an expression that will interpolate the color of the line - // (mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@)) - NSExpression *lineGradientExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($lineProgress, 'linear', nil, %@)", stops]; - routeLayer.lineGradient = lineGradientExpression; - - [self removeLayer:baseRouteLayer.identifier]; - [self removeLayer:routeLayer.identifier]; - [self removeSource:routeSource.identifier]; - [self.mapView.style addSource:routeSource]; - [self.mapView.style addLayer:baseRouteLayer]; - [self.mapView.style addLayer:routeLayer]; + [annotations removeObjectAtIndex:0]; + MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate + acrossDistance:10 + pitch:arc4random_uniform(60) + heading:arc4random_uniform(360)]; + __weak MBXViewController *weakSelf = self; + [self.mapView flyToCamera:camera + completionHandler:^{ + MBXViewController *strongSelf = weakSelf; + [strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:) + withObject:annotations + afterDelay:2]; + }]; } -- (void)addCustomDrawableLayer -{ - // Create a CustomLayer that uses the Drawable/Builder toolkit to generate and render geometry - ExampleCustomDrawableStyleLayer* layer = [[ExampleCustomDrawableStyleLayer alloc] initWithIdentifier:@"custom-drawable-layer"]; - - if (layer) { - [self.mapView.style addLayer:layer]; - } -} - -- (void)removeSource:(NSString*)ident -{ - if (MLNSource *source = [self.mapView.style sourceWithIdentifier:ident]) - { - [self.mapView.style removeSource:source]; - } -} - -- (void)removeLayer:(NSString*)ident -{ - if (MLNStyleLayer* layer = [self.mapView.style layerWithIdentifier:ident]) - { - [self.mapView.style removeLayer:layer]; - } -} - -- (void)styleRouteLine -{ - CLLocationCoordinate2D coords[] = { - { 43.84455590478528, 10.504238605499268 }, - { 43.84385562343126, 10.504125952720642 }, - { 43.84388657526694, 10.503299832344055 }, - { 43.84332557075269, 10.503235459327698 }, - { 43.843441641085036, 10.502264499664307 }, - { 43.84396395478592, 10.50242006778717 }, - { 43.84406067904351, 10.501744151115416 }, - { 43.84422317544319, 10.501792430877686 } - }; - NSInteger count = sizeof(coords) / sizeof(coords[0]); - - [self.mapView setCenterCoordinate:coords[0] zoomLevel:16 animated:YES]; - - MLNPolylineFeature *routeLine = [MLNPolylineFeature polylineWithCoordinates:coords count:count]; - - MLNShapeSource *routeSource = [[MLNShapeSource alloc] initWithIdentifier:@"style-route-source" shape:routeLine options:nil]; - - MLNLineStyleLayer *baseRouteLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-base-route-layer" source:routeSource]; - baseRouteLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor orangeColor]]; - baseRouteLayer.lineWidth = [NSExpression expressionForConstantValue:@20]; - baseRouteLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.5]; - baseRouteLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - baseRouteLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; - - MLNLineStyleLayer *routeLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"style-route-layer" source:routeSource]; - routeLayer.lineColor = [NSExpression expressionForConstantValue:[UIColor whiteColor]]; - routeLayer.lineWidth = [NSExpression expressionForConstantValue:@15]; - routeLayer.lineOpacity = [NSExpression expressionForConstantValue:@0.8]; - routeLayer.lineCap = [NSExpression expressionForConstantValue:@"round"]; - routeLayer.lineJoin = [NSExpression expressionForConstantValue:@"round"]; - - [self removeLayer:baseRouteLayer.identifier]; - [self removeLayer:routeLayer.identifier]; - [self removeSource:routeSource.identifier]; - [self.mapView.style addSource:routeSource]; - [self.mapView.style addLayer:baseRouteLayer]; - [self.mapView.style addLayer:routeLayer]; -} - -- (void)styleAddCustomTriangleLayer -{ - if (CustomStyleLayerExample *layer = [[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]) { - [self.mapView.style addLayer:layer]; - } -} - -- (void)stylePolygonWithDDS -{ - CLLocationCoordinate2D leftCoords[] = { - {37.73081027834234, -122.49412536621094}, - {37.7566013348511, -122.49412536621094}, - {37.7566013348511, -122.46253967285156}, - {37.73081027834234, -122.46253967285156}, - {37.73081027834234, -122.49412536621094}, - }; - CLLocationCoordinate2D rightCoords[] = { - {37.73135334055843, -122.44640350341795}, - {37.75741564287944, -122.44640350341795}, - {37.75741564287944, -122.41310119628906}, - {37.73135334055843, -122.41310119628906}, - {37.73135334055843, -122.44640350341795}, - }; - MLNPolygonFeature *leftFeature = [MLNPolygonFeature polygonWithCoordinates:leftCoords count:5]; - leftFeature.attributes = @{@"fill": @(YES)}; - - MLNPolygonFeature *rightFeature = [MLNPolygonFeature polygonWithCoordinates:rightCoords count:5]; - rightFeature.attributes = @{@"opacity": @(0.5)}; - - MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" features:@[leftFeature, rightFeature] options:nil]; - [self.mapView.style addSource:shapeSource]; - - // source, categorical function that sets any feature with a "fill" attribute value of true to red color and anything without to green - MLNFillStyleLayer *fillStyleLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:shapeSource]; - fillStyleLayer.fillColor = [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"fill == YES"] - trueExpression:[NSExpression expressionForConstantValue:[UIColor greenColor]] - falseExpresssion:[NSExpression expressionForConstantValue:[UIColor redColor]]]; - - - - // source, identity function that sets any feature with an "opacity" attribute to use that value and anything without to 1.0 - fillStyleLayer.fillOpacity = [NSExpression mgl_expressionForConditional:[NSPredicate predicateWithFormat:@"opacity != nil"] - trueExpression:[NSExpression expressionForKeyPath:@"opacity"] - falseExpresssion:[NSExpression expressionForConstantValue:@1.0]]; - [self.mapView.style addLayer:fillStyleLayer]; -} - -- (void)addLatLonGrid -{ - MLNComputedShapeSource *source = [[MLNComputedShapeSource alloc] initWithIdentifier:@"latlon" - options:@{MLNShapeSourceOptionMaximumZoomLevel:@14}]; - source.dataSource = self; - [self.mapView.style addSource:source]; - MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"latlonlines" - source:source]; - [self.mapView.style addLayer:lineLayer]; - MLNSymbolStyleLayer *labelLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"latlonlabels" - source:source]; - labelLayer.text = [NSExpression expressionForKeyPath:@"value"]; - [self.mapView.style addLayer:labelLayer]; -} - -- (NSString *)bestLanguageForUser -{ - // https://www.mapbox.com/vector-tiles/mapbox-streets-v8/#name-text--name_lang-code-text - NSArray *supportedLanguages = @[ @"ar", @"de", @"en", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"zh", @"zh-Hans", @"zh-Hant" ]; - NSArray *preferredLanguages = [NSBundle preferredLocalizationsFromArray:supportedLanguages forPreferences:[NSLocale preferredLanguages]]; - NSString *mostSpecificLanguage; - - for (NSString *language in preferredLanguages) - { - if (language.length > mostSpecificLanguage.length) - { - mostSpecificLanguage = language; - } - } - - return mostSpecificLanguage ?: @"en"; -} - -- (IBAction)startWorldTour -{ - _isTouringWorld = YES; - - [self.mapView removeAnnotations:self.mapView.annotations]; - NSUInteger numberOfAnnotations = sizeof(WorldTourDestinations) / sizeof(WorldTourDestinations[0]); - NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:numberOfAnnotations]; - for (NSUInteger i = 0; i < numberOfAnnotations; i++) - { - MBXDroppedPinAnnotation *annotation = [[MBXDroppedPinAnnotation alloc] init]; - annotation.coordinate = WorldTourDestinations[i]; - [annotations addObject:annotation]; - } - [self.mapView addAnnotations:annotations]; - [self continueWorldTourWithRemainingAnnotations:annotations]; -} - -- (void)continueWorldTourWithRemainingAnnotations:(NSMutableArray *)annotations -{ - MLNPointAnnotation *nextAnnotation = annotations.firstObject; - if (!nextAnnotation || !_isTouringWorld) - { - _isTouringWorld = NO; - return; - } - - [annotations removeObjectAtIndex:0]; - MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:nextAnnotation.coordinate - acrossDistance:10 - pitch:arc4random_uniform(60) - heading:arc4random_uniform(360)]; - __weak MBXViewController *weakSelf = self; - [self.mapView flyToCamera:camera completionHandler:^{ - MBXViewController *strongSelf = weakSelf; - [strongSelf performSelector:@selector(continueWorldTourWithRemainingAnnotations:) - withObject:annotations - afterDelay:2]; - }]; -} - -- (void)toggleCustomUserDot -{ - _customUserLocationAnnnotationEnabled = !_customUserLocationAnnnotationEnabled; - self.mapView.showsUserLocation = NO; - self.mapView.userTrackingMode = MLNUserTrackingModeFollow; +- (void)toggleCustomUserDot { + _customUserLocationAnnnotationEnabled = !_customUserLocationAnnnotationEnabled; + self.mapView.showsUserLocation = NO; + self.mapView.userTrackingMode = MLNUserTrackingModeFollow; } - (void)testQueryPointAnnotations { - NSNumber *visibleAnnotationCount = @(self.mapView.visibleAnnotations.count); - NSString *message; - if ([visibleAnnotationCount integerValue] == 1) { - message = [NSString stringWithFormat:@"There is %@ visible annotation.", visibleAnnotationCount]; - } else { - message = [NSString stringWithFormat:@"There are %@ visible annotations.", visibleAnnotationCount]; - } + NSNumber *visibleAnnotationCount = @(self.mapView.visibleAnnotations.count); + NSString *message; + if ([visibleAnnotationCount integerValue] == 1) { + message = + [NSString stringWithFormat:@"There is %@ visible annotation.", visibleAnnotationCount]; + } else { + message = + [NSString stringWithFormat:@"There are %@ visible annotations.", visibleAnnotationCount]; + } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Visible Annotations" message:message preferredStyle:UIAlertControllerStyleAlert]; - [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleCancel handler:nil]]; - [self presentViewController:alertController animated:YES completion:nil]; + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:@"Visible Annotations" + message:message + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" + style:UIAlertActionStyleCancel + handler:nil]]; + [self presentViewController:alertController animated:YES completion:nil]; } - (id)randomOffscreenPointAnnotation { - - NSPredicate *pointAnnotationPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + NSPredicate *pointAnnotationPredicate = + [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, + NSDictionary *_Nullable bindings) { return [evaluatedObject isKindOfClass:[MLNPointAnnotation class]]; - }]; + }]; - NSArray *annotations = [self.mapView.annotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + NSArray *annotations = + [self.mapView.annotations filteredArrayUsingPredicate:pointAnnotationPredicate]; - if (annotations.count == 0) { - return nil; - } + if (annotations.count == 0) { + return nil; + } - NSArray *visibleAnnotations = [self.mapView.visibleAnnotations filteredArrayUsingPredicate:pointAnnotationPredicate]; + NSArray *visibleAnnotations = + [self.mapView.visibleAnnotations filteredArrayUsingPredicate:pointAnnotationPredicate]; - if (visibleAnnotations.count == annotations.count) { - return nil; - } + if (visibleAnnotations.count == annotations.count) { + return nil; + } - NSMutableArray *invisibleAnnotations = [annotations mutableCopy]; + NSMutableArray *invisibleAnnotations = [annotations mutableCopy]; - if (visibleAnnotations.count > 0) { - [invisibleAnnotations removeObjectsInArray:visibleAnnotations]; - } + if (visibleAnnotations.count > 0) { + [invisibleAnnotations removeObjectsInArray:visibleAnnotations]; + } - // Now pick a random offscreen annotation. - uint32_t index = arc4random_uniform((uint32_t)invisibleAnnotations.count); - return invisibleAnnotations[index]; + // Now pick a random offscreen annotation. + uint32_t index = arc4random_uniform((uint32_t)invisibleAnnotations.count); + return invisibleAnnotations[index]; } - (void)selectAnOffscreenPointAnnotation { - id annotation = [self randomOffscreenPointAnnotation]; - if (annotation) { - [self.mapView selectAnnotation:annotation animated:YES completionHandler:nil]; + id annotation = [self randomOffscreenPointAnnotation]; + if (annotation) { + [self.mapView selectAnnotation:annotation animated:YES completionHandler:nil]; - NSAssert(self.mapView.selectedAnnotations.firstObject, @"The annotation was not selected"); - } + NSAssert(self.mapView.selectedAnnotations.firstObject, @"The annotation was not selected"); + } } - (void)centerSelectedAnnotation { - id annotation = self.mapView.selectedAnnotations.firstObject; + id annotation = self.mapView.selectedAnnotations.firstObject; - if (!annotation) - return; + if (!annotation) return; - CGPoint point = [self.mapView convertCoordinate:annotation.coordinate toPointToView:self.mapView]; + CGPoint point = [self.mapView convertCoordinate:annotation.coordinate toPointToView:self.mapView]; - // Animate, so that point becomes the the center - CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; - [self.mapView setCenterCoordinate:center animated:YES]; + // Animate, so that point becomes the the center + CLLocationCoordinate2D center = [self.mapView convertPoint:point + toCoordinateFromView:self.mapView]; + [self.mapView setCenterCoordinate:center animated:YES]; } - (void)addVisibleAreaPolyline { - CGRect constrainedRect = UIEdgeInsetsInsetRect(self.mapView.bounds, self.mapView.contentInset); + CGRect constrainedRect = UIEdgeInsetsInsetRect(self.mapView.bounds, self.mapView.contentInset); - CLLocationCoordinate2D lineCoords[5]; + CLLocationCoordinate2D lineCoords[5]; - lineCoords[0] = [self.mapView convertPoint: CGPointMake(CGRectGetMinX(constrainedRect), CGRectGetMinY(constrainedRect)) toCoordinateFromView:self.mapView]; - lineCoords[1] = [self.mapView convertPoint: CGPointMake(CGRectGetMaxX(constrainedRect), CGRectGetMinY(constrainedRect)) toCoordinateFromView:self.mapView]; - lineCoords[2] = [self.mapView convertPoint: CGPointMake(CGRectGetMaxX(constrainedRect), CGRectGetMaxY(constrainedRect)) toCoordinateFromView:self.mapView]; - lineCoords[3] = [self.mapView convertPoint: CGPointMake(CGRectGetMinX(constrainedRect), CGRectGetMaxY(constrainedRect)) toCoordinateFromView:self.mapView]; - lineCoords[4] = lineCoords[0]; + lineCoords[0] = [self.mapView convertPoint:CGPointMake(CGRectGetMinX(constrainedRect), + CGRectGetMinY(constrainedRect)) + toCoordinateFromView:self.mapView]; + lineCoords[1] = [self.mapView convertPoint:CGPointMake(CGRectGetMaxX(constrainedRect), + CGRectGetMinY(constrainedRect)) + toCoordinateFromView:self.mapView]; + lineCoords[2] = [self.mapView convertPoint:CGPointMake(CGRectGetMaxX(constrainedRect), + CGRectGetMaxY(constrainedRect)) + toCoordinateFromView:self.mapView]; + lineCoords[3] = [self.mapView convertPoint:CGPointMake(CGRectGetMinX(constrainedRect), + CGRectGetMaxY(constrainedRect)) + toCoordinateFromView:self.mapView]; + lineCoords[4] = lineCoords[0]; - MLNPolyline *line = [MLNPolyline polylineWithCoordinates:lineCoords - count:sizeof(lineCoords)/sizeof(lineCoords[0])]; - [self.mapView addAnnotation:line]; + MLNPolyline *line = + [MLNPolyline polylineWithCoordinates:lineCoords + count:sizeof(lineCoords) / sizeof(lineCoords[0])]; + [self.mapView addAnnotation:line]; } -- (void)loadMissingIcon -{ - self.mapView.centerCoordinate = CLLocationCoordinate2DMake(0, 0); - self.mapView.zoomLevel = 1; - NSURL *customStyleJSON = [[NSBundle mainBundle] URLForResource:@"missing_icon" withExtension:@"json"]; - [self.mapView setStyleURL:customStyleJSON]; +- (void)loadMissingIcon { + self.mapView.centerCoordinate = CLLocationCoordinate2DMake(0, 0); + self.mapView.zoomLevel = 1; + NSURL *customStyleJSON = [[NSBundle mainBundle] URLForResource:@"missing_icon" + withExtension:@"json"]; + [self.mapView setStyleURL:customStyleJSON]; } - (UIImage *)mapView:(MLNMapView *)mapView didFailToLoadImage:(NSString *)imageName { - UIImage *backupImage = [UIImage imageNamed:@"MissingImage"]; - return backupImage; + UIImage *backupImage = [UIImage imageNamed:@"MissingImage"]; + return backupImage; } -- (void)flyToWithLatLngBoundsAndPadding -{ - [self.mapView removeAnnotations:self.mapView.annotations]; - CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(48, 11); - CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(50, 12); +- (void)flyToWithLatLngBoundsAndPadding { + [self.mapView removeAnnotations:self.mapView.annotations]; + CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(48, 11); + CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(50, 12); - UIEdgeInsets padding = UIEdgeInsetsMake(200, 200, 0, 0); - MLNMapCamera *cameraWithoutPadding = [self.mapView cameraThatFitsCoordinateBounds:MLNCoordinateBoundsMake(sw, ne) - edgePadding:padding]; + UIEdgeInsets padding = UIEdgeInsetsMake(200, 200, 0, 0); + MLNMapCamera *cameraWithoutPadding = + [self.mapView cameraThatFitsCoordinateBounds:MLNCoordinateBoundsMake(sw, ne) + edgePadding:padding]; + MLNPointAnnotation *annotation = [MLNPointAnnotation new]; + annotation.coordinate = cameraWithoutPadding.centerCoordinate; + annotation.title = @"Bounds center"; + [self.mapView addAnnotation:annotation]; - MLNPointAnnotation *annotation = [MLNPointAnnotation new]; - annotation.coordinate = cameraWithoutPadding.centerCoordinate; - annotation.title = @"Bounds center"; - [self.mapView addAnnotation: annotation]; + __weak MBXViewController *weakSelf = self; + [self.mapView flyToCamera:cameraWithoutPadding + edgePadding:padding + withDuration:5 + completionHandler:^{ + [weakSelf.mapView flyToCamera:cameraWithoutPadding + edgePadding:UIEdgeInsetsZero + withDuration:5 + completionHandler:^{ - __weak MBXViewController *weakSelf = self; - [self.mapView flyToCamera:cameraWithoutPadding edgePadding:padding withDuration:5 completionHandler:^{ - [weakSelf.mapView flyToCamera:cameraWithoutPadding edgePadding:UIEdgeInsetsZero withDuration:5 completionHandler:^{ - - }]; - }]; + }]; + }]; } --(void)cycleTileLodMode -{ - // TileLodMode::Default parameters - static const double defaultRadius = self.mapView.tileLodMinRadius; - static const double defaultScale = self.mapView.tileLodScale; - static const double defaultPitchThreshold = self.mapView.tileLodPitchThreshold; +- (void)cycleTileLodMode { + // TileLodMode::Default parameters + static const double defaultRadius = self.mapView.tileLodMinRadius; + static const double defaultScale = self.mapView.tileLodScale; + static const double defaultPitchThreshold = self.mapView.tileLodPitchThreshold; - _tileLodMode = static_cast((static_cast(_tileLodMode) + 1) % static_cast(MBXTileLodModeCount)); + _tileLodMode = static_cast((static_cast(_tileLodMode) + 1) % + static_cast(MBXTileLodModeCount)); - switch (_tileLodMode) { - case MBXTileLodModeDefault: - self.mapView.tileLodMinRadius = defaultRadius; - self.mapView.tileLodScale = defaultScale; - self.mapView.tileLodPitchThreshold = defaultPitchThreshold; - break; - case MBXTileLodModeNoLod: - // When LOD is off we set a maximum PitchThreshold - self.mapView.tileLodPitchThreshold = M_PI; - break; - case MBXTileLodModeReduced: - self.mapView.tileLodMinRadius = 2; - self.mapView.tileLodScale = 1.5; - self.mapView.tileLodPitchThreshold = M_PI / 4; - break; - case MBXTileLodModeAggressive: - self.mapView.tileLodMinRadius = 1; - self.mapView.tileLodScale = 2; - self.mapView.tileLodPitchThreshold = 0; - break; - default: - break; - } + switch (_tileLodMode) { + case MBXTileLodModeDefault: + self.mapView.tileLodMinRadius = defaultRadius; + self.mapView.tileLodScale = defaultScale; + self.mapView.tileLodPitchThreshold = defaultPitchThreshold; + break; + case MBXTileLodModeNoLod: + // When LOD is off we set a maximum PitchThreshold + self.mapView.tileLodPitchThreshold = M_PI; + break; + case MBXTileLodModeReduced: + self.mapView.tileLodMinRadius = 2; + self.mapView.tileLodScale = 1.5; + self.mapView.tileLodPitchThreshold = M_PI / 4; + break; + case MBXTileLodModeAggressive: + self.mapView.tileLodMinRadius = 1; + self.mapView.tileLodScale = 2; + self.mapView.tileLodPitchThreshold = 0; + break; + default: + break; + } - // update UI - static UISlider* zoomSlider = nil; - if (zoomSlider && _tileLodMode == MBXTileLodModeNoLod) { - [zoomSlider removeFromSuperview]; - zoomSlider = nil; - } + // update UI + static UISlider *zoomSlider = nil; + if (zoomSlider && _tileLodMode == MBXTileLodModeNoLod) { + [zoomSlider removeFromSuperview]; + zoomSlider = nil; + } - if (!zoomSlider && _tileLodMode != MBXTileLodModeNoLod) { - const float zoomShiftRange = 5.0f; - zoomSlider = [[UISlider alloc] init]; - zoomSlider.minimumValue = -zoomShiftRange / 2.0f; - zoomSlider.maximumValue = zoomShiftRange / 2.0f; - zoomSlider.value = self.mapView.tileLodZoomShift; - zoomSlider.continuous = NO; + if (!zoomSlider && _tileLodMode != MBXTileLodModeNoLod) { + const float zoomShiftRange = 5.0f; + zoomSlider = [[UISlider alloc] init]; + zoomSlider.minimumValue = -zoomShiftRange / 2.0f; + zoomSlider.maximumValue = zoomShiftRange / 2.0f; + zoomSlider.value = self.mapView.tileLodZoomShift; + zoomSlider.continuous = NO; - [zoomSlider addTarget:self action:@selector(updateTileLodZoom:) forControlEvents:UIControlEventValueChanged]; - [self.view addSubview:zoomSlider]; + [zoomSlider addTarget:self + action:@selector(updateTileLodZoom:) + forControlEvents:UIControlEventValueChanged]; + [self.view addSubview:zoomSlider]; - zoomSlider.translatesAutoresizingMaskIntoConstraints = NO; - zoomSlider.frame = CGRectMake(0, 0, 100, 100); + zoomSlider.translatesAutoresizingMaskIntoConstraints = NO; + zoomSlider.frame = CGRectMake(0, 0, 100, 100); - NSArray* layout = @[ - [NSLayoutConstraint constraintWithItem:zoomSlider - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterX - multiplier:1 - constant:0], - [NSLayoutConstraint constraintWithItem:zoomSlider - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeWidth - multiplier:0.5 - constant:0], - [NSLayoutConstraint constraintWithItem:zoomSlider - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterY - multiplier:1 - constant:0], - [zoomSlider.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor]]; + NSArray *layout = @[ + [NSLayoutConstraint constraintWithItem:zoomSlider + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0], + [NSLayoutConstraint constraintWithItem:zoomSlider + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeWidth + multiplier:0.5 + constant:0], + [NSLayoutConstraint constraintWithItem:zoomSlider + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1 + constant:0], + [zoomSlider.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor] + ]; - [self.view addConstraints:layout]; - } + [self.view addConstraints:layout]; + } - [self.mapView triggerRepaint]; + [self.mapView triggerRepaint]; } --(void)updateTileLodZoom:(id)sender { - UISlider* slider = (UISlider*)sender; +- (void)updateTileLodZoom:(id)sender { + UISlider *slider = (UISlider *)sender; - self.mapView.tileLodZoomShift = slider.value; - [self.mapView triggerRepaint]; + self.mapView.tileLodZoomShift = slider.value; + [self.mapView triggerRepaint]; - NSLog(@"Tile LOD zoom shift: %f", self.mapView.tileLodZoomShift); + NSLog(@"Tile LOD zoom shift: %f", self.mapView.tileLodZoomShift); } --(NSString*)getTileLodModeName { - switch (_tileLodMode) { - case MBXTileLodModeDefault: - return @"Default"; - case MBXTileLodModeNoLod: - return @"Disabled"; - case MBXTileLodModeReduced: - return @"Reduced"; - case MBXTileLodModeAggressive: - return @"Aggressive"; - default: - return @""; - } +- (NSString *)getTileLodModeName { + switch (_tileLodMode) { + case MBXTileLodModeDefault: + return @"Default"; + case MBXTileLodModeNoLod: + return @"Disabled"; + case MBXTileLodModeReduced: + return @"Reduced"; + case MBXTileLodModeAggressive: + return @"Aggressive"; + default: + return @""; + } } // MARK: - Random World Tour -- (void)addAnnotations:(NSInteger)numAnnotations aroundCoordinate:(CLLocationCoordinate2D)coordinate radius:(CLLocationDistance)radius { - NSMutableArray *annotations = [[NSMutableArray alloc] initWithCapacity:numAnnotations]; - for (NSInteger i = 0; i feature in features) { - if (!title) { - title = [feature attributeForKey:@"name_en"] ?: [feature attributeForKey:@"name"]; - } - } - - MBXDroppedPinAnnotation *pin = [[MBXDroppedPinAnnotation alloc] init]; - pin.coordinate = [self.mapView convertPoint:point - toCoordinateFromView:self.mapView]; - pin.title = title ?: @"Dropped Pin"; - pin.subtitle = [[[MLNCoordinateFormatter alloc] init] stringFromCoordinate:pin.coordinate]; - - - // Calling `addAnnotation:` on mapView is required here (since `selectAnnotation:animated` has - // the side effect of adding the annotation if required, but returning an incorrect callout - // positioning rect) - - [self.mapView addAnnotation:pin]; - [self.mapView selectAnnotation:pin animated:YES completionHandler:nil]; +- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)longPress { + if (longPress.state == UIGestureRecognizerStateBegan) { + CGPoint point = [longPress locationInView:longPress.view]; + NSArray *features = [self.mapView visibleFeaturesAtPoint:point]; + NSString *title; + for (id feature in features) { + if (!title) { + title = [feature attributeForKey:@"name_en"] ?: [feature attributeForKey:@"name"]; + } } + + MBXDroppedPinAnnotation *pin = [[MBXDroppedPinAnnotation alloc] init]; + pin.coordinate = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; + pin.title = title ?: @"Dropped Pin"; + pin.subtitle = [[[MLNCoordinateFormatter alloc] init] stringFromCoordinate:pin.coordinate]; + + // Calling `addAnnotation:` on mapView is required here (since `selectAnnotation:animated` has + // the side effect of adding the annotation if required, but returning an incorrect callout + // positioning rect) + + [self.mapView addAnnotation:pin]; + [self.mapView selectAnnotation:pin animated:YES completionHandler:nil]; + } } // MARK: - User defined Styles -- (void)setStyles -{ - self.styleNames = [NSMutableArray array]; - self.styleURLs = [NSMutableArray array]; +- (void)setStyles { + self.styleNames = [NSMutableArray array]; + self.styleURLs = [NSMutableArray array]; + /// Style that does not require an `apiKey` nor any further configuration + [self.styleNames addObject:@"MapLibre Basic"]; + [self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]]; + /// This is hte same style as above but copied locally and the three instances of the metal + /// plug-in layer added to the style Look for "type": "plugin-layer-metal-rendering" in the + /// PluginLayerTestStyle.json for an example of how the layer is defined + NSURL *url = [[NSBundle mainBundle] URLForResource:@"PluginLayerTestStyle.json" + withExtension:nil]; + if (url) { + [self.styleNames addObject:@"MapLibre Basic - Local With Plugin"]; + [self.styleURLs addObject:url]; + } + /// Add MapLibre Styles if an `apiKey` exists + NSString *apiKey = [MLNSettings apiKey]; + if (apiKey.length) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + for (MLNDefaultStyle *predefinedStyle in [MLNStyle predefinedStyles]) { + [self.styleNames addObject:predefinedStyle.name]; + [self.styleURLs addObject:predefinedStyle.url]; + } + }); + } - /// Style that does not require an `apiKey` nor any further configuration - [self.styleNames addObject:@"MapLibre Basic"]; - [self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]]; - - /// This is hte same style as above but copied locally and the three instances of the metal plug-in layer added to the style - /// Look for "type": "plugin-layer-metal-rendering" in the PluginLayerTestStyle.json for an example of how the layer is defined - NSURL *url = [[NSBundle mainBundle] URLForResource:@"PluginLayerTestStyle.json" withExtension:nil]; - if (url) { - [self.styleNames addObject:@"MapLibre Basic - Local With Plugin"]; - [self.styleURLs addObject:url]; - } - - /// Add MapLibre Styles if an `apiKey` exists - NSString* apiKey = [MLNSettings apiKey]; - if (apiKey.length) - { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - - for (MLNDefaultStyle* predefinedStyle in [MLNStyle predefinedStyles]){ - [self.styleNames addObject:predefinedStyle.name]; - [self.styleURLs addObject:predefinedStyle.url]; - } - }); - } - - NSAssert(self.styleNames.count == self.styleURLs.count, @"Style names and URLs don’t match."); + NSAssert(self.styleNames.count == self.styleURLs.count, @"Style names and URLs don’t match."); } -- (IBAction)cycleStyles:(__unused id)sender -{ - self.styleIndex = (self.styleIndex + 1) % self.styleNames.count; +- (IBAction)cycleStyles:(__unused id)sender { + self.styleIndex = (self.styleIndex + 1) % self.styleNames.count; - self.mapView.styleURL = self.styleURLs[self.styleIndex]; + self.mapView.styleURL = self.styleURLs[self.styleIndex]; - UIButton *titleButton = (UIButton *)self.navigationItem.titleView; - [titleButton setTitle:self.styleNames[self.styleIndex] forState:UIControlStateNormal]; + UIButton *titleButton = (UIButton *)self.navigationItem.titleView; + [titleButton setTitle:self.styleNames[self.styleIndex] forState:UIControlStateNormal]; } -- (IBAction)locateUser:(id)sender -{ - [self nextTrackingMode:sender]; +- (IBAction)locateUser:(id)sender { + [self nextTrackingMode:sender]; } - -- (void)nextTrackingMode:(id)sender -{ - MLNUserTrackingMode nextMode; - NSString *nextAccessibilityValue; - switch (self.mapView.userTrackingMode) { - case MLNUserTrackingModeNone: - nextMode = MLNUserTrackingModeFollow; - nextAccessibilityValue = @"Follow location"; - break; - case MLNUserTrackingModeFollow: - nextMode = MLNUserTrackingModeFollowWithHeading; - nextAccessibilityValue = @"Follow location and heading"; - break; - case MLNUserTrackingModeFollowWithHeading: - nextMode = MLNUserTrackingModeFollowWithCourse; - nextAccessibilityValue = @"Follow course"; - break; - case MLNUserTrackingModeFollowWithCourse: - nextMode = MLNUserTrackingModeNone; - nextAccessibilityValue = @"Off"; - break; - } - self.mapView.userTrackingMode = nextMode; - [sender setAccessibilityValue:nextAccessibilityValue]; +- (void)nextTrackingMode:(id)sender { + MLNUserTrackingMode nextMode; + NSString *nextAccessibilityValue; + switch (self.mapView.userTrackingMode) { + case MLNUserTrackingModeNone: + nextMode = MLNUserTrackingModeFollow; + nextAccessibilityValue = @"Follow location"; + break; + case MLNUserTrackingModeFollow: + nextMode = MLNUserTrackingModeFollowWithHeading; + nextAccessibilityValue = @"Follow location and heading"; + break; + case MLNUserTrackingModeFollowWithHeading: + nextMode = MLNUserTrackingModeFollowWithCourse; + nextAccessibilityValue = @"Follow course"; + break; + case MLNUserTrackingModeFollowWithCourse: + nextMode = MLNUserTrackingModeNone; + nextAccessibilityValue = @"Off"; + break; + } + self.mapView.userTrackingMode = nextMode; + [sender setAccessibilityValue:nextAccessibilityValue]; } // MARK: - UIViewDelegate -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator -{ - [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - if (_contentInsetsEnabled) - { - _contentInsetsEnabled = NO; - _mapView.automaticallyAdjustsContentInset = YES; - [self.mapView setContentInset:UIEdgeInsetsZero]; - } - while (self.contentInsetsOverlays && [self.contentInsetsOverlays count]) { - [[self.contentInsetsOverlays lastObject] removeFromSuperview]; - [self.contentInsetsOverlays removeLastObject]; - } +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + if (_contentInsetsEnabled) { + _contentInsetsEnabled = NO; + _mapView.automaticallyAdjustsContentInset = YES; + [self.mapView setContentInset:UIEdgeInsetsZero]; + } + while (self.contentInsetsOverlays && [self.contentInsetsOverlays count]) { + [[self.contentInsetsOverlays lastObject] removeFromSuperview]; + [self.contentInsetsOverlays removeLastObject]; + } } // MARK: - MLNMapViewDelegate -- (void)mapView:(MLNMapView *)mapView sourceDidChange:(MLNSource *)source -{ - NSLog(@"A source was updated: %@", source.identifier); +- (void)mapView:(MLNMapView *)mapView sourceDidChange:(MLNSource *)source { + NSLog(@"A source was updated: %@", source.identifier); } - (void)mapView:(MLNMapView *)mapView shaderWillCompile:(NSInteger)id backend:(NSInteger)backend - defines:(NSString *)defines -{ - NSLog(@"A new shader is being compiled - shaderID:%ld, backend type:%ld, program configuration:%@", id, backend, defines); + defines:(NSString *)defines { + NSLog( + @"A new shader is being compiled - shaderID:%ld, backend type:%ld, program configuration:%@", + id, backend, defines); } - (void)mapView:(MLNMapView *)mapView shaderDidCompile:(NSInteger)id backend:(NSInteger)backend - defines:(NSString *)defines -{ - NSLog(@"A shader has been compiled - shaderID:%ld, backend type:%ld, program configuration:%@", id, backend, defines); + defines:(NSString *)defines { + NSLog(@"A shader has been compiled - shaderID:%ld, backend type:%ld, program configuration:%@", + id, backend, defines); } - (void)mapView:(MLNMapView *)mapView glyphsWillLoad:(NSArray *)fontStack - range:(NSRange)range -{ - NSLog(@"Glyphs are being requested for the font stack %@, ranging from %ld to %ld", fontStack, range.location, range.location + range.length); + range:(NSRange)range { + NSLog(@"Glyphs are being requested for the font stack %@, ranging from %ld to %ld", fontStack, + range.location, range.location + range.length); } - (void)mapView:(MLNMapView *)mapView glyphsDidLoad:(NSArray *)fontStack - range:(NSRange)range -{ - NSLog(@"Glyphs have been loaded for the font stack %@, ranging from %ld to %ld", fontStack, range.location, range.location + range.length); + range:(NSRange)range { + NSLog(@"Glyphs have been loaded for the font stack %@, ranging from %ld to %ld", fontStack, + range.location, range.location + range.length); } - (void)mapView:(MLNMapView *)mapView @@ -2559,476 +2631,492 @@ CLLocationCoordinate2D randomWorldCoordinate(void) { z:(NSInteger)z wrap:(NSInteger)wrap overscaledZ:(NSInteger)overscaledZ - sourceID:(NSString *)sourceID -{ - NSString* tileStr = [NSString stringWithFormat:@"(x: %ld, y: %ld, z: %ld, wrap: %ld, overscaledZ: %ld, sourceID: %@)", - x, y, z, wrap, overscaledZ, sourceID]; + sourceID:(NSString *)sourceID { + NSString *tileStr = [NSString + stringWithFormat:@"(x: %ld, y: %ld, z: %ld, wrap: %ld, overscaledZ: %ld, sourceID: %@)", x, y, + z, wrap, overscaledZ, sourceID]; - switch (operation) { - case MLNTileOperationRequestedFromCache: - NSLog(@"Requesting tile %@ from cache", tileStr); - break; + switch (operation) { + case MLNTileOperationRequestedFromCache: + NSLog(@"Requesting tile %@ from cache", tileStr); + break; - case MLNTileOperationRequestedFromNetwork: - NSLog(@"Requesting tile %@ from network", tileStr); - break; + case MLNTileOperationRequestedFromNetwork: + NSLog(@"Requesting tile %@ from network", tileStr); + break; - case MLNTileOperationLoadFromCache: - NSLog(@"Loading tile %@, requested from the cache", tileStr); - break; + case MLNTileOperationLoadFromCache: + NSLog(@"Loading tile %@, requested from the cache", tileStr); + break; - case MLNTileOperationLoadFromNetwork: - NSLog(@"Loading tile %@, requested from the network", tileStr); - break; + case MLNTileOperationLoadFromNetwork: + NSLog(@"Loading tile %@, requested from the network", tileStr); + break; - case MLNTileOperationStartParse: - NSLog(@"Parsing tile %@", tileStr); - break; + case MLNTileOperationStartParse: + NSLog(@"Parsing tile %@", tileStr); + break; - case MLNTileOperationEndParse: - NSLog(@"Completed parsing tile %@", tileStr); - break; + case MLNTileOperationEndParse: + NSLog(@"Completed parsing tile %@", tileStr); + break; - case MLNTileOperationError: - NSLog(@"An error occured during proccessing for tile %@", tileStr); - break; + case MLNTileOperationError: + NSLog(@"An error occured during proccessing for tile %@", tileStr); + break; - case MLNTileOperationCancelled: - NSLog(@"Pending work cancelled on tile %@", tileStr); - break; + case MLNTileOperationCancelled: + NSLog(@"Pending work cancelled on tile %@", tileStr); + break; - case MLNTileOperationNullOp: - NSLog(@"An unknown tile operation was emitted for tile %@", tileStr); - break; - } + case MLNTileOperationNullOp: + NSLog(@"An unknown tile operation was emitted for tile %@", tileStr); + break; + } } -- (void)mapView:(MLNMapView *)mapView spriteWillLoad:(NSString *)id url:(NSString *)url -{ - NSLog(@"The sprite %@ has been requested from %@", id, url); +- (void)mapView:(MLNMapView *)mapView spriteWillLoad:(NSString *)id url:(NSString *)url { + NSLog(@"The sprite %@ has been requested from %@", id, url); } -- (void)mapView:(MLNMapView *)mapView spriteDidLoad:(NSString *)id url:(NSString *)url -{ - NSLog(@"The sprite %@ has been loaded from %@", id, url); +- (void)mapView:(MLNMapView *)mapView spriteDidLoad:(NSString *)id url:(NSString *)url { + NSLog(@"The sprite %@ has been loaded from %@", id, url); } -- (MLNAnnotationView *)mapView:(MLNMapView *)mapView viewForAnnotation:(id)annotation -{ - if (annotation == mapView.userLocation) - { - if (_customUserLocationAnnnotationEnabled) - { - MBXUserLocationAnnotationView *annotationView = [[MBXUserLocationAnnotationView alloc] initWithFrame:CGRectZero]; - annotationView.frame = CGRectMake(0, 0, annotationView.intrinsicContentSize.width, annotationView.intrinsicContentSize.height); - return annotationView; - } - - return nil; - } - // Use GL backed pins for dropped pin annotations - if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || [annotation isKindOfClass:[MBXSpriteBackedAnnotation class]]) - { - return nil; +- (MLNAnnotationView *)mapView:(MLNMapView *)mapView + viewForAnnotation:(id)annotation { + if (annotation == mapView.userLocation) { + if (_customUserLocationAnnnotationEnabled) { + MBXUserLocationAnnotationView *annotationView = + [[MBXUserLocationAnnotationView alloc] initWithFrame:CGRectZero]; + annotationView.frame = CGRectMake(0, 0, annotationView.intrinsicContentSize.width, + annotationView.intrinsicContentSize.height); + return annotationView; } - MBXAnnotationView *annotationView = (MBXAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; - if (!annotationView) - { - annotationView = [[MBXAnnotationView alloc] initWithReuseIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; - annotationView.frame = CGRectMake(0, 0, 10, 10); - annotationView.backgroundColor = [UIColor whiteColor]; - - // Note that having two long press gesture recognizers on overlapping - // views (`self.view` & `annotationView`) will cause weird behavior. - // Comment out the pin dropping functionality in the handleLongPress: - // method in this class to make draggable annotation views play nice. - annotationView.draggable = YES; - } else { - // orange indicates that the annotation view was reused - annotationView.backgroundColor = [UIColor orangeColor]; - } - return annotationView; -} - -- (MLNAnnotationImage *)mapView:(MLNMapView * __nonnull)mapView imageForAnnotation:(id __nonnull)annotation -{ - if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) - { - return nil; // use default marker - } - - NSAssert([annotation isKindOfClass:[MBXSpriteBackedAnnotation class]], @"Annotations should be sprite-backed."); - - NSString *title = [(MLNPointAnnotation *)annotation title]; - if (!title.length) return nil; - NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; - - MLNAnnotationImage *annotationImage = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; - - if ( ! annotationImage) - { - UIColor *color; - - // make every tenth annotation blue - if ([lastTwoCharacters hasSuffix:@"0"]) { - color = [UIColor blueColor]; - } else { - color = [UIColor redColor]; - } - - UIImage *image = [self imageWithText:lastTwoCharacters backgroundColor:color]; - annotationImage = [MLNAnnotationImage annotationImageWithImage:image reuseIdentifier:lastTwoCharacters]; - - // don't allow touches on blue annotations - if ([color isEqual:[UIColor blueColor]]) annotationImage.enabled = NO; - } - - return annotationImage; -} - - -- (UIImage *)imageWithText:(NSString *)text backgroundColor:(UIColor *)color -{ - CGRect rect = CGRectMake(0, 0, 20, 15); - - UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); - - CGContextRef ctx = UIGraphicsGetCurrentContext(); - - CGContextSetFillColorWithColor(ctx, [[color colorWithAlphaComponent:0.75] CGColor]); - CGContextFillRect(ctx, rect); - - CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); - CGContextStrokeRectWithWidth(ctx, rect, 2); - - NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:text attributes:@{ - NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12], - NSForegroundColorAttributeName: [UIColor whiteColor], - }]; - CGSize stringSize = drawString.size; - CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2, - (rect.size.height - stringSize.height) / 2, - stringSize.width, - stringSize.height); - [drawString drawInRect:stringRect]; - - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; -} - -- (BOOL)mapView:(__unused MLNMapView *)mapView annotationCanShowCallout:(__unused id )annotation -{ - return YES; -} - -- (CGFloat)mapView:(__unused MLNMapView *)mapView alphaForShapeAnnotation:(MLNShape *)annotation -{ - return ([annotation isKindOfClass:[MLNPolygon class]] ? 0.5 : 1.0); -} - -- (UIColor *)mapView:(__unused MLNMapView *)mapView strokeColorForShapeAnnotation:(MLNShape *)annotation -{ - UIColor *color = [annotation isKindOfClass:[MLNPolyline class]] ? [UIColor greenColor] : [UIColor blackColor]; - return [color colorWithAlphaComponent:0.9]; -} - -- (UIColor *)mapView:(__unused MLNMapView *)mapView fillColorForPolygonAnnotation:(__unused MLNPolygon *)annotation -{ - UIColor *color = annotation.pointCount > 3 ? [UIColor greenColor] : [UIColor redColor]; - return [color colorWithAlphaComponent:0.5]; -} - -- (void)mapView:(__unused MLNMapView *)mapView didChangeUserTrackingMode:(MLNUserTrackingMode)mode animated:(__unused BOOL)animated -{ - UIImage *newButtonImage; - NSString *newButtonTitle; - - switch (mode) { - case MLNUserTrackingModeNone: - newButtonImage = [UIImage imageNamed:@"TrackingLocationOffMask.png"]; - break; - - case MLNUserTrackingModeFollow: - newButtonImage = [UIImage imageNamed:@"TrackingLocationMask.png"]; - break; - - case MLNUserTrackingModeFollowWithHeading: - newButtonImage = [UIImage imageNamed:@"TrackingHeadingMask.png"]; - break; - case MLNUserTrackingModeFollowWithCourse: - newButtonImage = nil; - newButtonTitle = @"Course"; - break; - } - - self.navigationItem.rightBarButtonItem.title = newButtonTitle; - [UIView animateWithDuration:0.25 animations:^{ - self.navigationItem.rightBarButtonItem.image = newButtonImage; - }]; -} - -- (nullable id )mapView:(__unused MLNMapView *)mapView calloutViewForAnnotation:(id)annotation -{ - if ([annotation respondsToSelector:@selector(title)] - && [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) - { - MBXCustomCalloutAnnotation *customAnnotation = (MBXCustomCalloutAnnotation *)annotation; - MBXCustomCalloutView *calloutView = [[MBXCustomCalloutView alloc] init]; - calloutView.representedObject = annotation; - calloutView.anchoredToAnnotation = customAnnotation.anchoredToAnnotation; - calloutView.dismissesAutomatically = customAnnotation.dismissesAutomatically; - return calloutView; - } return nil; + } + // Use GL backed pins for dropped pin annotations + if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || + [annotation isKindOfClass:[MBXSpriteBackedAnnotation class]]) { + return nil; + } + + MBXAnnotationView *annotationView = (MBXAnnotationView *)[mapView + dequeueReusableAnnotationViewWithIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; + if (!annotationView) { + annotationView = [[MBXAnnotationView alloc] + initWithReuseIdentifier:MBXViewControllerAnnotationViewReuseIdentifer]; + annotationView.frame = CGRectMake(0, 0, 10, 10); + annotationView.backgroundColor = [UIColor whiteColor]; + + // Note that having two long press gesture recognizers on overlapping + // views (`self.view` & `annotationView`) will cause weird behavior. + // Comment out the pin dropping functionality in the handleLongPress: + // method in this class to make draggable annotation views play nice. + annotationView.draggable = YES; + } else { + // orange indicates that the annotation view was reused + annotationView.backgroundColor = [UIColor orangeColor]; + } + return annotationView; } -- (UIView *)mapView:(__unused MLNMapView *)mapView leftCalloutAccessoryViewForAnnotation:(__unused id)annotation -{ - UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; - button.frame = CGRectZero; - [button setTitle:@"Left" forState:UIControlStateNormal]; - [button sizeToFit]; - return button; -} +- (MLNAnnotationImage *)mapView:(MLNMapView *__nonnull)mapView + imageForAnnotation:(id __nonnull)annotation { + if ([annotation isKindOfClass:[MBXDroppedPinAnnotation class]] || + [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) { + return nil; // use default marker + } -- (UIView *)mapView:(__unused MLNMapView *)mapView rightCalloutAccessoryViewForAnnotation:(__unused id)annotation -{ - UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; - button.frame = CGRectZero; - [button setTitle:@"Right" forState:UIControlStateNormal]; - [button sizeToFit]; - return button; -} + NSAssert([annotation isKindOfClass:[MBXSpriteBackedAnnotation class]], + @"Annotations should be sprite-backed."); -- (void)mapView:(MLNMapView *)mapView tapOnCalloutForAnnotation:(id )annotation -{ - if ( ! [annotation isKindOfClass:[MLNPointAnnotation class]]) - { - return; - } + NSString *title = [(MLNPointAnnotation *)annotation title]; + if (!title.length) return nil; + NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2]; - MLNPointAnnotation *point = (MLNPointAnnotation *)annotation; - point.coordinate = [self.mapView convertPoint:self.mapView.center toCoordinateFromView:self.mapView]; -} + MLNAnnotationImage *annotationImage = + [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters]; -- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style -{ - // Default MapLibre styles use {name_en} as their label language, which means - // that a device with an English-language locale is already effectively - // using locale-based country labels. - _localizingLabels = [[self bestLanguageForUser] isEqualToString:@"en"]; -} + if (!annotationImage) { + UIColor *color; -- (BOOL)mapView:(MLNMapView *)mapView shouldChangeFromCamera:(MLNMapCamera *)oldCamera toCamera:(MLNMapCamera *)newCamera { - if (_shouldLimitCameraChanges) { - // Get the current camera to restore it after. - MLNMapCamera *currentCamera = mapView.camera; - - // From the new camera obtain the center to test if it’s inside the boundaries. - CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate; - - // Set the map’s visible bounds to newCamera. - mapView.camera = newCamera; - MLNCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds; - - // Revert the camera. - mapView.camera = currentCamera; - - // Test if the newCameraCenter and newVisibleCoordinates are inside Colorado. - BOOL inside = MLNCoordinateInCoordinateBounds(newCameraCenter, colorado); - BOOL intersects = MLNCoordinateInCoordinateBounds(newVisibleCoordinates.ne, colorado) && MLNCoordinateInCoordinateBounds(newVisibleCoordinates.sw, colorado); - - return inside && intersects; + // make every tenth annotation blue + if ([lastTwoCharacters hasSuffix:@"0"]) { + color = [UIColor blueColor]; } else { - return YES; - } -} - -- (void)mapViewRegionIsChanging:(MLNMapView *)mapView -{ - [self updateHUD]; - [self updateHelperMapViews]; -} - -- (void)mapView:(MLNMapView *)mapView regionDidChangeWithReason:(MLNCameraChangeReason)reason animated:(BOOL)animated -{ - if (reason != MLNCameraChangeReasonProgrammatic) { - self.randomWalk = NO; + color = [UIColor redColor]; } - [self updateHUD]; - [self updateHelperMapViews]; + UIImage *image = [self imageWithText:lastTwoCharacters backgroundColor:color]; + annotationImage = [MLNAnnotationImage annotationImageWithImage:image + reuseIdentifier:lastTwoCharacters]; + + // don't allow touches on blue annotations + if ([color isEqual:[UIColor blueColor]]) annotationImage.enabled = NO; + } + + return annotationImage; +} + +- (UIImage *)imageWithText:(NSString *)text backgroundColor:(UIColor *)color { + CGRect rect = CGRectMake(0, 0, 20, 15); + + UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]); + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + + CGContextSetFillColorWithColor(ctx, [[color colorWithAlphaComponent:0.75] CGColor]); + CGContextFillRect(ctx, rect); + + CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]); + CGContextStrokeRectWithWidth(ctx, rect, 2); + + NSAttributedString *drawString = [[NSAttributedString alloc] + initWithString:text + attributes:@{ + NSFontAttributeName : [UIFont fontWithName:@"Arial-BoldMT" size:12], + NSForegroundColorAttributeName : [UIColor whiteColor], + }]; + CGSize stringSize = drawString.size; + CGRect stringRect = + CGRectMake((rect.size.width - stringSize.width) / 2, + (rect.size.height - stringSize.height) / 2, stringSize.width, stringSize.height); + [drawString drawInRect:stringRect]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +- (BOOL)mapView:(__unused MLNMapView *)mapView + annotationCanShowCallout:(__unused id)annotation { + return YES; +} + +- (CGFloat)mapView:(__unused MLNMapView *)mapView alphaForShapeAnnotation:(MLNShape *)annotation { + return ([annotation isKindOfClass:[MLNPolygon class]] ? 0.5 : 1.0); +} + +- (UIColor *)mapView:(__unused MLNMapView *)mapView + strokeColorForShapeAnnotation:(MLNShape *)annotation { + UIColor *color = + [annotation isKindOfClass:[MLNPolyline class]] ? [UIColor greenColor] : [UIColor blackColor]; + return [color colorWithAlphaComponent:0.9]; +} + +- (UIColor *)mapView:(__unused MLNMapView *)mapView + fillColorForPolygonAnnotation:(__unused MLNPolygon *)annotation { + UIColor *color = annotation.pointCount > 3 ? [UIColor greenColor] : [UIColor redColor]; + return [color colorWithAlphaComponent:0.5]; +} + +- (void)mapView:(__unused MLNMapView *)mapView + didChangeUserTrackingMode:(MLNUserTrackingMode)mode + animated:(__unused BOOL)animated { + UIImage *newButtonImage; + NSString *newButtonTitle; + + switch (mode) { + case MLNUserTrackingModeNone: + newButtonImage = [UIImage imageNamed:@"TrackingLocationOffMask.png"]; + break; + + case MLNUserTrackingModeFollow: + newButtonImage = [UIImage imageNamed:@"TrackingLocationMask.png"]; + break; + + case MLNUserTrackingModeFollowWithHeading: + newButtonImage = [UIImage imageNamed:@"TrackingHeadingMask.png"]; + break; + case MLNUserTrackingModeFollowWithCourse: + newButtonImage = nil; + newButtonTitle = @"Course"; + break; + } + + self.navigationItem.rightBarButtonItem.title = newButtonTitle; + [UIView animateWithDuration:0.25 + animations:^{ + self.navigationItem.rightBarButtonItem.image = newButtonImage; + }]; +} + +- (nullable id)mapView:(__unused MLNMapView *)mapView + calloutViewForAnnotation:(id)annotation { + if ([annotation respondsToSelector:@selector(title)] && + [annotation isKindOfClass:[MBXCustomCalloutAnnotation class]]) { + MBXCustomCalloutAnnotation *customAnnotation = (MBXCustomCalloutAnnotation *)annotation; + MBXCustomCalloutView *calloutView = [[MBXCustomCalloutView alloc] init]; + calloutView.representedObject = annotation; + calloutView.anchoredToAnnotation = customAnnotation.anchoredToAnnotation; + calloutView.dismissesAutomatically = customAnnotation.dismissesAutomatically; + return calloutView; + } + return nil; +} + +- (UIView *)mapView:(__unused MLNMapView *)mapView + leftCalloutAccessoryViewForAnnotation:(__unused id)annotation { + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + button.frame = CGRectZero; + [button setTitle:@"Left" forState:UIControlStateNormal]; + [button sizeToFit]; + return button; +} + +- (UIView *)mapView:(__unused MLNMapView *)mapView + rightCalloutAccessoryViewForAnnotation:(__unused id)annotation { + UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; + button.frame = CGRectZero; + [button setTitle:@"Right" forState:UIControlStateNormal]; + [button sizeToFit]; + return button; +} + +- (void)mapView:(MLNMapView *)mapView tapOnCalloutForAnnotation:(id)annotation { + if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { + return; + } + + MLNPointAnnotation *point = (MLNPointAnnotation *)annotation; + point.coordinate = [self.mapView convertPoint:self.mapView.center + toCoordinateFromView:self.mapView]; +} + +- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style { + // Default MapLibre styles use {name_en} as their label language, which means + // that a device with an English-language locale is already effectively + // using locale-based country labels. + _localizingLabels = [[self bestLanguageForUser] isEqualToString:@"en"]; +} + +- (BOOL)mapView:(MLNMapView *)mapView + shouldChangeFromCamera:(MLNMapCamera *)oldCamera + toCamera:(MLNMapCamera *)newCamera { + if (_shouldLimitCameraChanges) { + // Get the current camera to restore it after. + MLNMapCamera *currentCamera = mapView.camera; + + // From the new camera obtain the center to test if it’s inside the boundaries. + CLLocationCoordinate2D newCameraCenter = newCamera.centerCoordinate; + + // Set the map’s visible bounds to newCamera. + mapView.camera = newCamera; + MLNCoordinateBounds newVisibleCoordinates = mapView.visibleCoordinateBounds; + + // Revert the camera. + mapView.camera = currentCamera; + + // Test if the newCameraCenter and newVisibleCoordinates are inside Colorado. + BOOL inside = MLNCoordinateInCoordinateBounds(newCameraCenter, colorado); + BOOL intersects = MLNCoordinateInCoordinateBounds(newVisibleCoordinates.ne, colorado) && + MLNCoordinateInCoordinateBounds(newVisibleCoordinates.sw, colorado); + + return inside && intersects; + } else { + return YES; + } +} + +- (void)mapViewRegionIsChanging:(MLNMapView *)mapView { + [self updateHUD]; + [self updateHelperMapViews]; +} + +- (void)mapView:(MLNMapView *)mapView + regionDidChangeWithReason:(MLNCameraChangeReason)reason + animated:(BOOL)animated { + if (reason != MLNCameraChangeReasonProgrammatic) { + self.randomWalk = NO; + } + + [self updateHUD]; + [self updateHelperMapViews]; } - (void)mapView:(MLNMapView *)mapView didUpdateUserLocation:(MLNUserLocation *)userLocation { - [self updateHUD]; + [self updateHUD]; } - (void)updateHelperMapViews { - for (UIWindow *window in self.helperWindows) { - MLNMapView *mapView = (MLNMapView *)window.rootViewController.view; - mapView.camera = self.mapView.camera; - } + for (UIWindow *window in self.helperWindows) { + MLNMapView *mapView = (MLNMapView *)window.rootViewController.view; + mapView.camera = self.mapView.camera; + } } - (void)updateHUD { + if (self.reuseQueueStatsEnabled == NO && self.zoomLevelOrnamentEnabled == NO) { + return; + } - if (self.reuseQueueStatsEnabled == NO && self.zoomLevelOrnamentEnabled == NO) { - return; + NSString *hudString; + + if (self.reuseQueueStatsEnabled) { + NSUInteger queuedAnnotations = 0; + for (NSArray *queue in self.mapView.annotationViewReuseQueueByIdentifier.allValues) { + queuedAnnotations += queue.count; } + hudString = [NSString stringWithFormat:@"Visible: %ld Queued: %ld", + (unsigned long)self.mapView.visibleAnnotations.count, + (unsigned long)queuedAnnotations]; + } else if (self.zoomLevelOrnamentEnabled) { + hudString = [NSString stringWithFormat:@"%.f FPS (%.1fms) ∕ %.2f ∕ ↕\U0000FE0E%.f° ∕ %.f°", + roundf(self.mapView.averageFrameRate), + self.mapView.averageFrameTime, self.mapView.zoomLevel, + self.mapView.camera.pitch, self.mapView.direction]; + } - NSString *hudString; - - if (self.reuseQueueStatsEnabled) { - NSUInteger queuedAnnotations = 0; - for (NSArray *queue in self.mapView.annotationViewReuseQueueByIdentifier.allValues) { - queuedAnnotations += queue.count; - } - hudString = [NSString stringWithFormat:@"Visible: %ld Queued: %ld", (unsigned long)self.mapView.visibleAnnotations.count, (unsigned long)queuedAnnotations]; - } else if (self.zoomLevelOrnamentEnabled) { - hudString = [NSString stringWithFormat:@"%.f FPS (%.1fms) ∕ %.2f ∕ ↕\U0000FE0E%.f° ∕ %.f°", - roundf(self.mapView.averageFrameRate), self.mapView.averageFrameTime, - self.mapView.zoomLevel, self.mapView.camera.pitch, self.mapView.direction]; - } - - [self.hudLabel setTitle:hudString forState:UIControlStateNormal]; + [self.hudLabel setTitle:hudString forState:UIControlStateNormal]; } // MARK: - MLNComputedShapeSourceDataSource -- (NSArray>*)featuresInCoordinateBounds:(MLNCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { - double gridSpacing; - if(zoom >= 13) { - gridSpacing = 0.01; - } else if(zoom >= 11) { - gridSpacing = 0.05; - } else if(zoom == 10) { - gridSpacing = .1; - } else if(zoom == 9) { - gridSpacing = 0.25; - } else if(zoom == 8) { - gridSpacing = 0.5; - } else if (zoom >= 6) { - gridSpacing = 1; - } else if(zoom == 5) { - gridSpacing = 2; - } else if(zoom >= 4) { - gridSpacing = 5; - } else if(zoom == 2) { - gridSpacing = 10; - } else { - gridSpacing = 20; - } +- (NSArray> *)featuresInCoordinateBounds:(MLNCoordinateBounds)bounds + zoomLevel:(NSUInteger)zoom { + double gridSpacing; + if (zoom >= 13) { + gridSpacing = 0.01; + } else if (zoom >= 11) { + gridSpacing = 0.05; + } else if (zoom == 10) { + gridSpacing = .1; + } else if (zoom == 9) { + gridSpacing = 0.25; + } else if (zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if (zoom == 5) { + gridSpacing = 2; + } else if (zoom >= 4) { + gridSpacing = 5; + } else if (zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } - NSMutableArray > * features = [NSMutableArray array]; - CLLocationCoordinate2D coords[2]; + NSMutableArray> *features = [NSMutableArray array]; + CLLocationCoordinate2D coords[2]; - for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { - coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); - coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); - MLNPolylineFeature *feature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; - feature.attributes = @{@"value": @(y)}; - [features addObject:feature]; - } + for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; + y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); + coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); + MLNPolylineFeature *feature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value" : @(y)}; + [features addObject:feature]; + } - for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { - coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); - coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); - MLNPolylineFeature *feature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; - feature.attributes = @{@"value": @(x)}; - [features addObject:feature]; - } + for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; + x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); + coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); + MLNPolylineFeature *feature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value" : @(x)}; + [features addObject:feature]; + } - return features; + return features; } - (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered renderingStats:(nonnull MLNRenderingStats *)renderingStats { - if (self.frameTimeGraphEnabled) { - [self.frameTimeGraphView updatePathWithFrameDuration:renderingStats.encodingTime]; - } + if (self.frameTimeGraphEnabled) { + [self.frameTimeGraphView updatePathWithFrameDuration:renderingStats.encodingTime]; + } } -- (void)mapView:(nonnull MLNMapView *)mapView didChangeLocationManagerAuthorization:(nonnull id)manager { - if (@available(iOS 14, *)) { +- (void)mapView:(nonnull MLNMapView *)mapView + didChangeLocationManagerAuthorization:(nonnull id)manager { + if (@available(iOS 14, *)) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 - if (manager.authorizationStatus == kCLAuthorizationStatusDenied || manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) { - [self alertAccuracyChanges]; - } -#endif + if (manager.authorizationStatus == kCLAuthorizationStatusDenied || + manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) { + [self alertAccuracyChanges]; } +#endif + } } - (void)alertAccuracyChanges { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"MapLibre works best with your precise location." - message:@"You'll get turn-by-turn directions." + UIAlertController *alert = + [UIAlertController alertControllerWithTitle:@"MapLibre works best with your precise location." + message:@"You'll get turn-by-turn directions." preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"Turn On in Settings" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) { - if (success) { - NSLog(@"Opened url"); - } - }]; - }]; + UIAlertAction *settingsAction = + [UIAlertAction actionWithTitle:@"Turn On in Settings" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + [[UIApplication sharedApplication] + openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] + options:@{} + completionHandler:^(BOOL success) { + if (success) { + NSLog(@"Opened url"); + } + }]; + }]; - UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"Keep Precise Location Off" style:UIAlertActionStyleDefault - handler:nil]; + UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:@"Keep Precise Location Off" + style:UIAlertActionStyleDefault + handler:nil]; - [alert addAction:settingsAction]; - [alert addAction:defaultAction]; - [self presentViewController:alert animated:YES completion:nil]; + [alert addAction:settingsAction]; + [alert addAction:defaultAction]; + [self presentViewController:alert animated:YES completion:nil]; } - (BOOL)isUITesting { - NSString* value = NSProcessInfo.processInfo.environment[@"UITesting"]; - return value && [value isEqual: @"YES"]; + NSString *value = NSProcessInfo.processInfo.environment[@"UITesting"]; + return value && [value isEqual:@"YES"]; } - (void)saveCurrentMapState:(__unused NSNotification *)notification { + // saved changes to the settings can break UI tests, so always start from the defaults when + // testing + if ([self isUITesting]) { + return; + } - // saved changes to the settings can break UI tests, so always start from the defaults when testing - if ([self isUITesting]) { - return; - } + // The following properties can change after the view loads so we need to save their + // state before exiting the view controller. + self.currentState.camera = self.mapView.camera; + self.currentState.showsUserLocation = self.mapView.showsUserLocation; + self.currentState.userTrackingMode = self.mapView.userTrackingMode; + self.currentState.showsUserHeadingIndicator = self.mapView.showsUserHeadingIndicator; + self.currentState.showsMapScale = self.mapView.showsScale; + self.currentState.showsZoomLevelOrnament = self.zoomLevelOrnamentEnabled; + self.currentState.showsTimeFrameGraph = self.frameTimeGraphEnabled; + self.currentState.debugMask = self.mapView.debugMask; + self.currentState.reuseQueueStatsEnabled = self.reuseQueueStatsEnabled; - // The following properties can change after the view loads so we need to save their - // state before exiting the view controller. - self.currentState.camera = self.mapView.camera; - self.currentState.showsUserLocation = self.mapView.showsUserLocation; - self.currentState.userTrackingMode = self.mapView.userTrackingMode; - self.currentState.showsUserHeadingIndicator = self.mapView.showsUserHeadingIndicator; - self.currentState.showsMapScale = self.mapView.showsScale; - self.currentState.showsZoomLevelOrnament = self.zoomLevelOrnamentEnabled; - self.currentState.showsTimeFrameGraph = self.frameTimeGraphEnabled; - self.currentState.debugMask = self.mapView.debugMask; - self.currentState.reuseQueueStatsEnabled = self.reuseQueueStatsEnabled; - - [[MBXStateManager sharedManager] saveState:self.currentState]; + [[MBXStateManager sharedManager] saveState:self.currentState]; } - (void)restoreMapState:(__unused NSNotification *)notification { + if ([self isUITesting]) { + return; + } - if ([self isUITesting]) { - return; - } + MBXState *currentState = [MBXStateManager sharedManager].currentState; - MBXState *currentState = [MBXStateManager sharedManager].currentState; + self.mapView.camera = currentState.camera; + self.mapView.showsUserLocation = currentState.showsUserLocation; + self.mapView.userTrackingMode = currentState.userTrackingMode; + self.mapView.showsUserHeadingIndicator = currentState.showsUserHeadingIndicator; + self.mapView.showsScale = currentState.showsMapScale; + self.zoomLevelOrnamentEnabled = currentState.showsZoomLevelOrnament; + self.frameTimeGraphEnabled = currentState.showsTimeFrameGraph; + self.mapView.debugMask = currentState.debugMask; + self.reuseQueueStatsEnabled = currentState.reuseQueueStatsEnabled; - self.mapView.camera = currentState.camera; - self.mapView.showsUserLocation = currentState.showsUserLocation; - self.mapView.userTrackingMode = currentState.userTrackingMode; - self.mapView.showsUserHeadingIndicator = currentState.showsUserHeadingIndicator; - self.mapView.showsScale = currentState.showsMapScale; - self.zoomLevelOrnamentEnabled = currentState.showsZoomLevelOrnament; - self.frameTimeGraphEnabled = currentState.showsTimeFrameGraph; - self.mapView.debugMask = currentState.debugMask; - self.reuseQueueStatsEnabled = currentState.reuseQueueStatsEnabled; - - self.currentState = currentState; + self.currentState = currentState; } @end diff --git a/platform/ios/benchmark/MBXBenchAppDelegate.mm b/platform/ios/benchmark/MBXBenchAppDelegate.mm index 2eeec3217..74324ab59 100644 --- a/platform/ios/benchmark/MBXBenchAppDelegate.mm +++ b/platform/ios/benchmark/MBXBenchAppDelegate.mm @@ -7,18 +7,17 @@ - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - #ifndef MLN_LOGGING_DISABLED - [MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault; + [MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault; #endif - [MLNSettings useWellKnownTileServer:MLNMapTiler]; + [MLNSettings useWellKnownTileServer:MLNMapTiler]; - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; - self.window.rootViewController = [MBXBenchViewController new]; - [self.window makeKeyAndVisible]; + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.rootViewController = [MBXBenchViewController new]; + [self.window makeKeyAndVisible]; - return YES; + return YES; } @end diff --git a/platform/ios/benchmark/MBXBenchViewController.mm b/platform/ios/benchmark/MBXBenchViewController.mm index 21451a5e6..26eb6c71d 100644 --- a/platform/ios/benchmark/MBXBenchViewController.mm +++ b/platform/ios/benchmark/MBXBenchViewController.mm @@ -19,31 +19,30 @@ frameRenderingTime:(double)frameRenderingTime; @end - class BenchMapObserver : public mbgl::MapObserver { public: - BenchMapObserver() = delete; - BenchMapObserver(id mapDelegate_) : mapDelegate(mapDelegate_) {} - virtual ~BenchMapObserver() = default; + BenchMapObserver() = delete; + BenchMapObserver(id mapDelegate_) : mapDelegate(mapDelegate_) {} + virtual ~BenchMapObserver() = default; - void onDidFinishRenderingFrame(const RenderFrameStatus& status) override final { - //NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3); - //NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3); + void onDidFinishRenderingFrame(const RenderFrameStatus &status) override final { + // NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3); + // NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3); - bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered - frameEncodingTime:status.renderingStats.encodingTime - frameRenderingTime:status.renderingStats.renderingTime]; - } + bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; + [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered + frameEncodingTime:status.renderingStats.encodingTime + frameRenderingTime:status.renderingStats.renderingTime]; + } protected: - __weak id mapDelegate = nullptr; + __weak id mapDelegate = nullptr; }; @interface MBXBenchViewController () { - std::unique_ptr observer; - std::unique_ptr frontend; - std::unique_ptr map; + std::unique_ptr observer; + std::unique_ptr frontend; + std::unique_ptr map; } @property (nonatomic) UIImageView *imageView; @@ -54,125 +53,129 @@ protected: // MARK: - Setup -+ (void)initialize -{ - if (self == [MBXBenchViewController class]) - { - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ - @"MBXUserTrackingMode": @(MLNUserTrackingModeNone), - @"MBXShowsUserLocation": @NO, - @"MBXDebug": @NO, - }]; - } ++ (void)initialize { + if (self == [MBXBenchViewController class]) { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"MBXUserTrackingMode" : @(MLNUserTrackingModeNone), + @"MBXShowsUserLocation" : @NO, + @"MBXDebug" : @NO, + }]; + } } -- (void)viewDidLoad -{ - [super viewDidLoad]; +- (void)viewDidLoad { + [super viewDidLoad]; } -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; - #ifdef LOG_TO_DOCUMENTS_DIR - [self setAndRedirectLogFileToDocuments]; - #endif +#ifdef LOG_TO_DOCUMENTS_DIR + [self setAndRedirectLogFileToDocuments]; +#endif - // Use a local style and local assets if they’ve been downloaded. - NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" withExtension:@"pbf" subdirectory:@"tiles/tiles/v3/5/7"]; - NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles" withExtension:@"json" subdirectory:@"tiles"]; - const std::vector styles = { - "maptiler://maps/streets", - }; - constexpr auto styleIndex = 0; - NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]]; - NSLog(@"Using style URL: \"%@\"", [url absoluteString]); + // Use a local style and local assets if they’ve been downloaded. + NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" + withExtension:@"pbf" + subdirectory:@"tiles/tiles/v3/5/7"]; + NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles" + withExtension:@"json" + subdirectory:@"tiles"]; + const std::vector styles = { + "maptiler://maps/streets", + }; + constexpr auto styleIndex = 0; + NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" + : [NSString stringWithCString:styles[styleIndex].c_str() + encoding:NSUTF8StringEncoding]]; + NSLog(@"Using style URL: \"%@\"", [url absoluteString]); - self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; - self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - [self.view addSubview:self.imageView]; + self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; + self.imageView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:self.imageView]; - mbgl::Size viewSize = { static_cast(self.view.bounds.size.width), - static_cast(self.view.bounds.size.height) }; - auto pixelRatio = [[UIScreen mainScreen] scale]; + mbgl::Size viewSize = {static_cast(self.view.bounds.size.width), + static_cast(self.view.bounds.size.height)}; + auto pixelRatio = [[UIScreen mainScreen] scale]; - observer = std::make_unique(self); - frontend = std::make_unique( - viewSize, - pixelRatio, - mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush, - mbgl::gfx::ContextMode::Unique, - /* localFontFamily */ std::nullopt, - /* invalidateOnUpdate */ false - ); + observer = std::make_unique(self); + frontend = std::make_unique( + viewSize, pixelRatio, mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush, + mbgl::gfx::ContextMode::Unique, + /* localFontFamily */ std::nullopt, + /* invalidateOnUpdate */ false); - mbgl::MapOptions mapOptions; - mapOptions.withMapMode(mbgl::MapMode::Continuous) - .withSize(viewSize) - .withPixelRatio(pixelRatio) - .withConstrainMode(mbgl::ConstrainMode::None) - .withViewportMode(mbgl::ViewportMode::Default) - .withCrossSourceCollisions(true); + mbgl::MapOptions mapOptions; + mapOptions.withMapMode(mbgl::MapMode::Continuous) + .withSize(viewSize) + .withPixelRatio(pixelRatio) + .withConstrainMode(mbgl::ConstrainMode::None) + .withViewportMode(mbgl::ViewportMode::Default) + .withCrossSourceCollisions(true); - mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - mbgl::ResourceOptions resourceOptions; - resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) - .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) - .withTileServerOptions(*tileServerOptions); - mbgl::ClientOptions clientOptions; + mbgl::TileServerOptions *tileServerOptions = + [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) + .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) + .withTileServerOptions(*tileServerOptions); + mbgl::ClientOptions clientOptions; - auto apiKey = [[MLNSettings sharedSettings] apiKey]; - if (apiKey) { - resourceOptions.withApiKey([apiKey UTF8String]); - } + auto apiKey = [[MLNSettings sharedSettings] apiKey]; + if (apiKey) { + resourceOptions.withApiKey([apiKey UTF8String]); + } - map = std::make_unique(*frontend, *observer, mapOptions, resourceOptions, clientOptions); - map->setSize(viewSize); - map->setDebug(mbgl::MapDebugOptions::NoDebug); - map->getStyle().loadURL([url.absoluteString UTF8String]); + map = + std::make_unique(*frontend, *observer, mapOptions, resourceOptions, clientOptions); + map->setSize(viewSize); + map->setDebug(mbgl::MapDebugOptions::NoDebug); + map->getStyle().loadURL([url.absoluteString UTF8String]); - [self startBenchmarkIteration]; + [self startBenchmarkIteration]; } // For setting the filename -NSDate* const currentDate = [NSDate date]; +NSDate *const currentDate = [NSDate date]; /*! \description - Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings > Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR` + Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the + Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings + > Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR` */ -- (void)setAndRedirectLogFileToDocuments -{ - NSString* const name = UIDevice.currentDevice.name; - #if TARGET_IPHONE_SIMULATOR - NSString* const DeviceMode = @"Simulator"; - #else - NSString* const DeviceMode = @"Device"; - #endif +- (void)setAndRedirectLogFileToDocuments { + NSString *const name = UIDevice.currentDevice.name; +#if TARGET_IPHONE_SIMULATOR + NSString *const DeviceMode = @"Simulator"; +#else + NSString *const DeviceMode = @"Device"; +#endif - // Set log file name date - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setDateFormat:@"yyyy-MM-dd-HHmm"]; - NSString *dateString = [formatter stringFromDate:currentDate]; + // Set log file name date + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd-HHmm"]; + NSString *dateString = [formatter stringFromDate:currentDate]; - // Set the log file name - NSString* filename = [NSString stringWithFormat: @"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString]; + // Set the log file name + NSString *filename = + [NSString stringWithFormat:@"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString]; - // Set log file path - NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [allPaths objectAtIndex:0]; - NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent: filename]; - NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command"); - NSLog(@" open \"%@\"", pathForLog); + // Set log file path + NSArray *allPaths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [allPaths objectAtIndex:0]; + NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:filename]; + NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command"); + NSLog(@" open \"%@\"", pathForLog); - [self redirectLogToDocuments :pathForLog]; + [self redirectLogToDocuments:pathForLog]; } -- (void)redirectLogToDocuments: (NSString*) fileName -{ - // write to file in append "a+" mode - freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr); +- (void)redirectLogToDocuments:(NSString *)fileName { + // write to file in append "a+" mode + freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr); } size_t idx = 0; @@ -181,163 +184,153 @@ int frames = 0; double totalFrameEncodingTime = 0; double totalFrameRenderingTime = 0; std::chrono::steady_clock::time_point started; -std::vector> > result; +std::vector>> result; -static const int benchmarkDuration = 5; // seconds +static const int benchmarkDuration = 5; // seconds -namespace mbgl { - extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; +namespace mbgl { +extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; } -- (void)renderFrame -{ - mbgl::gfx::BackendScope guard{*(frontend->getBackend())}; +- (void)renderFrame { + mbgl::gfx::BackendScope guard{*(frontend->getBackend())}; - frontend->renderFrame(); + frontend->renderFrame(); - auto image = frontend->readStillImage(); + auto image = frontend->readStillImage(); - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef bitmapContext = CGBitmapContextCreate( - image.data.get(), - image.size.width, - image.size.height, - 8, - 4 * image.size.width, - colorSpace, - kCGImageAlphaPremultipliedLast - ); - CFRelease(colorSpace); - CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext); - CGContextRelease(bitmapContext); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef bitmapContext = + CGBitmapContextCreate(image.data.get(), image.size.width, image.size.height, 8, + 4 * image.size.width, colorSpace, kCGImageAlphaPremultipliedLast); + CFRelease(colorSpace); + CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext); + CGContextRelease(bitmapContext); - self.imageView.image = [UIImage imageWithCGImage:cgImage]; - CGImageRelease(cgImage); + self.imageView.image = [UIImage imageWithCGImage:cgImage]; + CGImageRelease(cgImage); } -- (void)startBenchmarkIteration -{ - if (mbgl::bench::locations.size() > idx) { - const auto& location = mbgl::bench::locations[idx]; +- (void)startBenchmarkIteration { + if (mbgl::bench::locations.size() > idx) { + const auto &location = mbgl::bench::locations[idx]; - mbgl::CameraOptions cameraOptions; - cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude); - cameraOptions.zoom = location.zoom; - cameraOptions.bearing = location.bearing; - mbgl::AnimationOptions animationOptions; - animationOptions.duration.emplace(std::chrono::duration_cast(std::chrono::duration(benchmarkDuration))); - map->easeTo(cameraOptions, animationOptions); + mbgl::CameraOptions cameraOptions; + cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude); + cameraOptions.zoom = location.zoom; + cameraOptions.bearing = location.bearing; + mbgl::AnimationOptions animationOptions; + animationOptions.duration.emplace(std::chrono::duration_cast( + std::chrono::duration(benchmarkDuration))); + map->easeTo(cameraOptions, animationOptions); - state = State::WaitingForAssets; - NSLog(@"Benchmarking \"%s\"", location.name.c_str()); - NSLog(@"- Loading assets..."); + state = State::WaitingForAssets; + NSLog(@"Benchmarking \"%s\"", location.name.c_str()); + NSLog(@"- Loading assets..."); - dispatch_async(dispatch_get_main_queue(), ^{ - [self renderFrame]; - }); - } else { - // Do nothing. The benchmark is completed. - NSLog(@"Benchmark completed."); + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); + } else { + // Do nothing. The benchmark is completed. + NSLog(@"Benchmark completed."); - NSLog(@"Result:"); - size_t colWidth = 0; - for (const auto& row : result) { - colWidth = std::max(row.first.size(), colWidth); - } + NSLog(@"Result:"); + size_t colWidth = 0; + for (const auto &row : result) { + colWidth = std::max(row.first.size(), colWidth); + } - double totalFrameEncodingTime = 0; - double totalFrameRenderingTime = 0; - for (const auto& row : result) { - NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(), 1e3 * row.second.first, 1e3 * row.second.second); - totalFrameEncodingTime += row.second.first; - totalFrameRenderingTime += row.second.second; - } + double totalFrameEncodingTime = 0; + double totalFrameRenderingTime = 0; + for (const auto &row : result) { + NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(), + 1e3 * row.second.first, 1e3 * row.second.second); + totalFrameEncodingTime += row.second.first; + totalFrameRenderingTime += row.second.second; + } - NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size()); - NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size()); + NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size()); + NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size()); - // NSLog(@"Total uploads: %zu", mbgl::uploadCount); - // NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty); - // NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments); - // NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount); + // NSLog(@"Total uploads: %zu", mbgl::uploadCount); + // NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty); + // NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments); + // NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount); #if !defined(NDEBUG) - // Clean up and show rendering stats, as in `destroyCoreObjects` from tests. - // TODO: This doesn't clean up everything, what are we missing? - map.reset(); - observer.reset(); - frontend.reset(); -#endif // !defined(NDEBUG) + // Clean up and show rendering stats, as in `destroyCoreObjects` from tests. + // TODO: This doesn't clean up everything, what are we missing? + map.reset(); + observer.reset(); + frontend.reset(); +#endif // !defined(NDEBUG) - // this does not shut the application down correctly, - // and results in an assertion failure in thread-local code - //exit(0); + // this does not shut the application down correctly, + // and results in an assertion failure in thread-local code + // exit(0); - // Use the UIApplication lifecycle instead. - // Terminating an app programmatically is strongly discouraged by Apple. - // Combined with the plist setting "Application does not run in background" suspend allows - // the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait. - UIApplication *app = [UIApplication sharedApplication]; - if ([app respondsToSelector:@selector(suspend)]) - { - [app performSelector:@selector(suspend)]; - } + // Use the UIApplication lifecycle instead. + // Terminating an app programmatically is strongly discouraged by Apple. + // Combined with the plist setting "Application does not run in background" suspend allows + // the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait. + UIApplication *app = [UIApplication sharedApplication]; + if ([app respondsToSelector:@selector(suspend)]) { + [app performSelector:@selector(suspend)]; } + } } - (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered frameEncodingTime:(double)frameEncodingTime - frameRenderingTime:(double)frameRenderingTime -{ - if (state == State::Benchmarking) - { - frames++; - totalFrameEncodingTime += frameEncodingTime; - totalFrameRenderingTime += frameRenderingTime; + frameRenderingTime:(double)frameRenderingTime { + if (state == State::Benchmarking) { + frames++; + totalFrameEncodingTime += frameEncodingTime; + totalFrameRenderingTime += frameRenderingTime; - const auto duration = std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count(); - if (duration >= benchmarkDuration * 1e6) - { - state = State::None; + const auto duration = std::chrono::duration_cast( + std::chrono::steady_clock::now() - started) + .count(); + if (duration >= benchmarkDuration * 1e6) { + state = State::None; - // Report FPS - const auto frameEncodingTime = static_cast(totalFrameEncodingTime) / frames; - const auto frameRenderingTime = static_cast(totalFrameRenderingTime) / frames; - result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime)); - NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames); + // Report FPS + const auto frameEncodingTime = static_cast(totalFrameEncodingTime) / frames; + const auto frameRenderingTime = static_cast(totalFrameRenderingTime) / frames; + result.emplace_back(mbgl::bench::locations[idx].name, + std::make_pair(frameEncodingTime, frameRenderingTime)); + NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", + frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames); - // Start benchmarking the next location. - idx++; - [self startBenchmarkIteration]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self renderFrame]; - }); - } - return; + // Start benchmarking the next location. + idx++; + [self startBenchmarkIteration]; + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); } - else if (state == State::WaitingForAssets) - { - if (map->isFullyLoaded()) - { - // Start the benchmarking timer. - frames = 0; - totalFrameEncodingTime = 0; - totalFrameRenderingTime = 0; - state = State::Benchmarking; - started = std::chrono::steady_clock::now(); - NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration); - } - dispatch_async(dispatch_get_main_queue(), ^{ - [self renderFrame]; - }); - return; + return; + } else if (state == State::WaitingForAssets) { + if (map->isFullyLoaded()) { + // Start the benchmarking timer. + frames = 0; + totalFrameEncodingTime = 0; + totalFrameRenderingTime = 0; + state = State::Benchmarking; + started = std::chrono::steady_clock::now(); + NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration); } + dispatch_async(dispatch_get_main_queue(), ^{ + [self renderFrame]; + }); + return; + } } -- (NSUInteger)supportedInterfaceOrientations -{ - return UIInterfaceOrientationMaskLandscape; +- (NSUInteger)supportedInterfaceOrientations { + return UIInterfaceOrientationMaskLandscape; } @end diff --git a/platform/ios/benchmark/main.mm b/platform/ios/benchmark/main.mm index 52bb44555..6fb43d6bf 100644 --- a/platform/ios/benchmark/main.mm +++ b/platform/ios/benchmark/main.mm @@ -2,9 +2,8 @@ #import "MBXBenchAppDelegate.h" -int main(int argc, char * argv[]) -{ - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXBenchAppDelegate class])); - } +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXBenchAppDelegate class])); + } } diff --git a/platform/ios/src/MLNAnnotationView.mm b/platform/ios/src/MLNAnnotationView.mm index 6718f10c0..1def8c82e 100644 --- a/platform/ios/src/MLNAnnotationView.mm +++ b/platform/ios/src/MLNAnnotationView.mm @@ -1,10 +1,10 @@ #import "MLNAnnotationView.h" -#import "MLNAnnotationView_Private.h" -#import "MLNMapView_Private.h" -#import "MLNCalloutView.h" #import "MLNAnnotation.h" -#import "MLNPointAnnotation.h" +#import "MLNAnnotationView_Private.h" +#import "MLNCalloutView.h" #import "MLNLoggingConfiguration_Private.h" +#import "MLNMapView_Private.h" +#import "MLNPointAnnotation.h" #import "NSBundle+MLNAdditions.h" #import "NSValue+MLNAdditions.h" @@ -27,361 +27,350 @@ @implementation MLNAnnotationView + (BOOL)supportsSecureCoding { - return YES; + return YES; } - (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier { - MLNLogDebug(@"Initializing with identifier: %@", reuseIdentifier); - self = [super initWithFrame:CGRectZero]; - if (self) { - [self commonInitWithAnnotation:nil reuseIdentifier:reuseIdentifier]; - } - return self; + MLNLogDebug(@"Initializing with identifier: %@", reuseIdentifier); + self = [super initWithFrame:CGRectZero]; + if (self) { + [self commonInitWithAnnotation:nil reuseIdentifier:reuseIdentifier]; + } + return self; } -- (instancetype)initWithAnnotation:(nullable id)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier { - MLNLogDebug(@"Initializing with annotation: %@ reuseIdentifier: %@", annotation, reuseIdentifier); - self = [super initWithFrame:CGRectZero]; - if (self) { - [self commonInitWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; - } - return self; +- (instancetype)initWithAnnotation:(nullable id)annotation + reuseIdentifier:(nullable NSString *)reuseIdentifier { + MLNLogDebug(@"Initializing with annotation: %@ reuseIdentifier: %@", annotation, reuseIdentifier); + self = [super initWithFrame:CGRectZero]; + if (self) { + [self commonInitWithAnnotation:annotation reuseIdentifier:reuseIdentifier]; + } + return self; } -- (void)commonInitWithAnnotation:(nullable id)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier { - _lastAppliedScaleTransform = CATransform3DIdentity; - _lastAppliedRotationTransform = CATransform3DIdentity; - _annotation = annotation; - _reuseIdentifier = [reuseIdentifier copy]; - _enabled = YES; +- (void)commonInitWithAnnotation:(nullable id)annotation + reuseIdentifier:(nullable NSString *)reuseIdentifier { + _lastAppliedScaleTransform = CATransform3DIdentity; + _lastAppliedRotationTransform = CATransform3DIdentity; + _annotation = annotation; + _reuseIdentifier = [reuseIdentifier copy]; + _enabled = YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { - MLNLogInfo(@"Initializing with coder."); - if (self = [super initWithCoder:decoder]) { - _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; - _annotation = [decoder decodeObjectOfClass:[NSObject class] forKey:@"annotation"]; - _centerOffset = [decoder decodeCGVectorForKey:@"centerOffset"]; - _scalesWithViewingDistance = [decoder decodeBoolForKey:@"scalesWithViewingDistance"]; - _rotatesToMatchCamera = [decoder decodeBoolForKey:@"rotatesToMatchCamera"]; - _selected = [decoder decodeBoolForKey:@"selected"]; - _enabled = [decoder decodeBoolForKey:@"enabled"]; - self.draggable = [decoder decodeBoolForKey:@"draggable"]; - } - return self; + MLNLogInfo(@"Initializing with coder."); + if (self = [super initWithCoder:decoder]) { + _reuseIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"reuseIdentifier"]; + _annotation = [decoder decodeObjectOfClass:[NSObject class] forKey:@"annotation"]; + _centerOffset = [decoder decodeCGVectorForKey:@"centerOffset"]; + _scalesWithViewingDistance = [decoder decodeBoolForKey:@"scalesWithViewingDistance"]; + _rotatesToMatchCamera = [decoder decodeBoolForKey:@"rotatesToMatchCamera"]; + _selected = [decoder decodeBoolForKey:@"selected"]; + _enabled = [decoder decodeBoolForKey:@"enabled"]; + self.draggable = [decoder decodeBoolForKey:@"draggable"]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; - [coder encodeObject:_annotation forKey:@"annotation"]; - [coder encodeCGVector:_centerOffset forKey:@"centerOffset"]; - [coder encodeBool:_scalesWithViewingDistance forKey:@"scalesWithViewingDistance"]; - [coder encodeBool:_rotatesToMatchCamera forKey:@"rotatesToMatchCamera"]; - [coder encodeBool:_selected forKey:@"selected"]; - [coder encodeBool:_enabled forKey:@"enabled"]; - [coder encodeBool:_draggable forKey:@"draggable"]; + [super encodeWithCoder:coder]; + [coder encodeObject:_reuseIdentifier forKey:@"reuseIdentifier"]; + [coder encodeObject:_annotation forKey:@"annotation"]; + [coder encodeCGVector:_centerOffset forKey:@"centerOffset"]; + [coder encodeBool:_scalesWithViewingDistance forKey:@"scalesWithViewingDistance"]; + [coder encodeBool:_rotatesToMatchCamera forKey:@"rotatesToMatchCamera"]; + [coder encodeBool:_selected forKey:@"selected"]; + [coder encodeBool:_enabled forKey:@"enabled"]; + [coder encodeBool:_draggable forKey:@"draggable"]; } -- (void)prepareForReuse -{ - // Intentionally left blank. The default implementation of this method does nothing. +- (void)prepareForReuse { + // Intentionally left blank. The default implementation of this method does nothing. } -- (void)setCenterOffset:(CGVector)centerOffset -{ - MLNLogDebug(@"Setting centerOffset: %@", NSStringFromCGVector(centerOffset)); - _centerOffset = centerOffset; - self.center = self.center; +- (void)setCenterOffset:(CGVector)centerOffset { + MLNLogDebug(@"Setting centerOffset: %@", NSStringFromCGVector(centerOffset)); + _centerOffset = centerOffset; + self.center = self.center; } -- (void)setSelected:(BOOL)selected -{ - MLNLogDebug(@"Setting selected: %@", MLNStringFromBOOL(selected)); - [self setSelected:selected animated:NO]; +- (void)setSelected:(BOOL)selected { + MLNLogDebug(@"Setting selected: %@", MLNStringFromBOOL(selected)); + [self setSelected:selected animated:NO]; } -- (void)setSelected:(BOOL)selected animated:(BOOL)animated -{ - MLNLogDebug(@"Setting selected: %@ animated: %@", MLNStringFromBOOL(selected), MLNStringFromBOOL(animated)); - [self willChangeValueForKey:@"selected"]; - _selected = selected; - [self didChangeValueForKey:@"selected"]; +- (void)setSelected:(BOOL)selected animated:(BOOL)animated { + MLNLogDebug(@"Setting selected: %@ animated: %@", MLNStringFromBOOL(selected), + MLNStringFromBOOL(animated)); + [self willChangeValueForKey:@"selected"]; + _selected = selected; + [self didChangeValueForKey:@"selected"]; } -- (CGPoint)center -{ - CGPoint center = super.center; - center.x -= _centerOffset.dx; - center.y -= _centerOffset.dy; - return center; +- (CGPoint)center { + CGPoint center = super.center; + center.x -= _centerOffset.dx; + center.y -= _centerOffset.dy; + return center; } -- (void)setCenter:(CGPoint)center -{ - MLNLogDebug(@"Setting center: %@", NSStringFromCGPoint(center)); - center.x += _centerOffset.dx; - center.y += _centerOffset.dy; +- (void)setCenter:(CGPoint)center { + MLNLogDebug(@"Setting center: %@", NSStringFromCGPoint(center)); + center.x += _centerOffset.dx; + center.y += _centerOffset.dy; - super.center = center; + super.center = center; + [self updateScaleTransformForViewingDistance]; + [self updateRotateTransform]; +} + +- (void)setScalesWithViewingDistance:(BOOL)scalesWithViewingDistance { + MLNLogDebug(@"Setting scaleWithViewingDistance: %@", + MLNStringFromBOOL(scalesWithViewingDistance)); + if (_scalesWithViewingDistance != scalesWithViewingDistance) { + _scalesWithViewingDistance = scalesWithViewingDistance; [self updateScaleTransformForViewingDistance]; - [self updateRotateTransform]; + } } -- (void)setScalesWithViewingDistance:(BOOL)scalesWithViewingDistance -{ - MLNLogDebug(@"Setting scaleWithViewingDistance: %@", MLNStringFromBOOL(scalesWithViewingDistance)); - if (_scalesWithViewingDistance != scalesWithViewingDistance) - { - _scalesWithViewingDistance = scalesWithViewingDistance; - [self updateScaleTransformForViewingDistance]; - } -} +- (void)updateScaleTransformForViewingDistance { + if (self.scalesWithViewingDistance == NO || self.dragState == MLNAnnotationViewDragStateDragging) + return; -- (void)updateScaleTransformForViewingDistance -{ - if (self.scalesWithViewingDistance == NO || self.dragState == MLNAnnotationViewDragStateDragging) return; + CGFloat superviewHeight = CGRectGetHeight(self.superview.frame); + if (superviewHeight > 0.0) { + // Find the maximum amount of scale reduction to apply as the view's center moves from the top + // of the superview to the bottom. For example, if this view's center has moved 25% of the way + // from the top of the superview towards the bottom then the maximum scale reduction is 1 - .25 + // or 75%. The range goes from a maximum of 100% to 0% as the view moves from the top to the + // bottom along the y axis of its superview. + CGFloat maxScaleReduction = 1.0 - self.center.y / superviewHeight; - CGFloat superviewHeight = CGRectGetHeight(self.superview.frame); - if (superviewHeight > 0.0) { - // Find the maximum amount of scale reduction to apply as the view's center moves from the top - // of the superview to the bottom. For example, if this view's center has moved 25% of the way - // from the top of the superview towards the bottom then the maximum scale reduction is 1 - .25 - // or 75%. The range goes from a maximum of 100% to 0% as the view moves from the top to the bottom - // along the y axis of its superview. - CGFloat maxScaleReduction = 1.0 - self.center.y / superviewHeight; + // Since it is possible for the map view to report a pitch less than 0 due to the nature of + // how the gesture information is captured, the value is guarded with MAX. + CGFloat pitch = MAX(self.mapView.camera.pitch, 0); - // Since it is possible for the map view to report a pitch less than 0 due to the nature of - // how the gesture information is captured, the value is guarded with MAX. - CGFloat pitch = MAX(self.mapView.camera.pitch, 0); + // Return early if the map view currently has no pitch and was not previously pitched. + if (!pitch && !_lastPitch) return; + _lastPitch = pitch; - // Return early if the map view currently has no pitch and was not previously pitched. - if (!pitch && !_lastPitch) return; - _lastPitch = pitch; + // The pitch intensity represents how much the map view is actually pitched compared to + // what is possible. The value will range from 0% (not pitched at all) to 100% (pitched as much + // as the map view will allow). The map view's maximum pitch is defined in + // `mbgl::util::PITCH_MAX`. + CGFloat pitchIntensity = pitch / MLNDegreesFromRadians(mbgl::util::PITCH_MAX); - // The pitch intensity represents how much the map view is actually pitched compared to - // what is possible. The value will range from 0% (not pitched at all) to 100% (pitched as much - // as the map view will allow). The map view's maximum pitch is defined in `mbgl::util::PITCH_MAX`. - CGFloat pitchIntensity = pitch / MLNDegreesFromRadians(mbgl::util::PITCH_MAX); + // The pitch adjusted scale is the inverse proportion of the maximum possible scale reduction + // multiplied by the pitch intensity. For example, if the maximum scale reduction is 75% and the + // map view is 50% pitched then the annotation view should be reduced by 37.5% (.75 * .5). The + // reduction is then normalized for a scale of 1.0. + CGFloat pitchAdjustedScale = 1.0 - maxScaleReduction * pitchIntensity; - // The pitch adjusted scale is the inverse proportion of the maximum possible scale reduction - // multiplied by the pitch intensity. For example, if the maximum scale reduction is 75% and the - // map view is 50% pitched then the annotation view should be reduced by 37.5% (.75 * .5). The - // reduction is then normalized for a scale of 1.0. - CGFloat pitchAdjustedScale = 1.0 - maxScaleReduction * pitchIntensity; - - // We keep track of each viewing distance scale transform that we apply. Each iteration, - // we can account for it so that we don't get cumulative scaling every time we move. - // We also avoid clobbering any existing transform passed in by the client or this SDK. - CATransform3D undoOfLastScaleTransform = CATransform3DInvert(_lastAppliedScaleTransform); - CATransform3D newScaleTransform = CATransform3DMakeScale(pitchAdjustedScale, pitchAdjustedScale, 1); - CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastScaleTransform, newScaleTransform); - self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform); - _lastAppliedScaleTransform = newScaleTransform; - } -} - -- (void)setRotatesToMatchCamera:(BOOL)rotatesToMatchCamera -{ - MLNLogDebug(@"Setting rotatesToMatchCamera: %@", MLNStringFromBOOL(rotatesToMatchCamera)); - if (_rotatesToMatchCamera != rotatesToMatchCamera) - { - _rotatesToMatchCamera = rotatesToMatchCamera; - [self updateRotateTransform]; - } -} - -- (void)updateRotateTransform -{ - if (self.rotatesToMatchCamera == NO) return; - - CGFloat direction = -MLNRadiansFromDegrees(self.mapView.direction); - - // Return early if the map view has the same rotation as the already-applied transform. - if (direction == _lastDirection) return; - _lastDirection = direction; - - // We keep track of each rotation transform that we apply. Each iteration, - // we can account for it so that we don't get cumulative rotation every time we move. + // We keep track of each viewing distance scale transform that we apply. Each iteration, + // we can account for it so that we don't get cumulative scaling every time we move. // We also avoid clobbering any existing transform passed in by the client or this SDK. - CATransform3D undoOfLastRotationTransform = CATransform3DInvert(_lastAppliedRotationTransform); - CATransform3D newRotationTransform = CATransform3DMakeRotation(direction, 0, 0, 1); - CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastRotationTransform, newRotationTransform); + CATransform3D undoOfLastScaleTransform = CATransform3DInvert(_lastAppliedScaleTransform); + CATransform3D newScaleTransform = + CATransform3DMakeScale(pitchAdjustedScale, pitchAdjustedScale, 1); + CATransform3D effectiveTransform = + CATransform3DConcat(undoOfLastScaleTransform, newScaleTransform); self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform); - _lastAppliedRotationTransform = newRotationTransform; + _lastAppliedScaleTransform = newScaleTransform; + } +} + +- (void)setRotatesToMatchCamera:(BOOL)rotatesToMatchCamera { + MLNLogDebug(@"Setting rotatesToMatchCamera: %@", MLNStringFromBOOL(rotatesToMatchCamera)); + if (_rotatesToMatchCamera != rotatesToMatchCamera) { + _rotatesToMatchCamera = rotatesToMatchCamera; + [self updateRotateTransform]; + } +} + +- (void)updateRotateTransform { + if (self.rotatesToMatchCamera == NO) return; + + CGFloat direction = -MLNRadiansFromDegrees(self.mapView.direction); + + // Return early if the map view has the same rotation as the already-applied transform. + if (direction == _lastDirection) return; + _lastDirection = direction; + + // We keep track of each rotation transform that we apply. Each iteration, + // we can account for it so that we don't get cumulative rotation every time we move. + // We also avoid clobbering any existing transform passed in by the client or this SDK. + CATransform3D undoOfLastRotationTransform = CATransform3DInvert(_lastAppliedRotationTransform); + CATransform3D newRotationTransform = CATransform3DMakeRotation(direction, 0, 0, 1); + CATransform3D effectiveTransform = + CATransform3DConcat(undoOfLastRotationTransform, newRotationTransform); + self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform); + _lastAppliedRotationTransform = newRotationTransform; } // MARK: - Draggable -- (void)setDraggable:(BOOL)draggable -{ - MLNLogDebug(@"Setting draggable: %@", MLNStringFromBOOL(draggable)); - [self willChangeValueForKey:@"draggable"]; - _draggable = draggable; - [self didChangeValueForKey:@"draggable"]; +- (void)setDraggable:(BOOL)draggable { + MLNLogDebug(@"Setting draggable: %@", MLNStringFromBOOL(draggable)); + [self willChangeValueForKey:@"draggable"]; + _draggable = draggable; + [self didChangeValueForKey:@"draggable"]; - if (draggable) - { - [self enableDrag]; - } - else - { - [self disableDrag]; - } + if (draggable) { + [self enableDrag]; + } else { + [self disableDrag]; + } } -- (void)enableDrag -{ - if (!_longPressRecognizer) - { - UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; - recognizer.delegate = self; - [self addGestureRecognizer:recognizer]; - _longPressRecognizer = recognizer; - } +- (void)enableDrag { + if (!_longPressRecognizer) { + UILongPressGestureRecognizer *recognizer = + [[UILongPressGestureRecognizer alloc] initWithTarget:self + action:@selector(handleLongPress:)]; + recognizer.delegate = self; + [self addGestureRecognizer:recognizer]; + _longPressRecognizer = recognizer; + } - if (!_panGestureRecognizer) - { - UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; - recognizer.delegate = self; - [self addGestureRecognizer:recognizer]; - _panGestureRecognizer = recognizer; - } + if (!_panGestureRecognizer) { + UIPanGestureRecognizer *recognizer = + [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; + recognizer.delegate = self; + [self addGestureRecognizer:recognizer]; + _panGestureRecognizer = recognizer; + } } -- (void)disableDrag -{ - [self removeGestureRecognizer:_longPressRecognizer]; - [self removeGestureRecognizer:_panGestureRecognizer]; +- (void)disableDrag { + [self removeGestureRecognizer:_longPressRecognizer]; + [self removeGestureRecognizer:_panGestureRecognizer]; } -- (void)handleLongPress:(UILongPressGestureRecognizer *)sender -{ - switch (sender.state) { - case UIGestureRecognizerStateBegan: - self.dragState = MLNAnnotationViewDragStateStarting; - break; - case UIGestureRecognizerStateChanged: - self.dragState = MLNAnnotationViewDragStateDragging; - break; - case UIGestureRecognizerStateCancelled: - self.dragState = MLNAnnotationViewDragStateCanceling; - break; - case UIGestureRecognizerStateEnded: - self.dragState = MLNAnnotationViewDragStateEnding; - break; - case UIGestureRecognizerStateFailed: - self.dragState = MLNAnnotationViewDragStateNone; - break; - case UIGestureRecognizerStatePossible: - break; - } +- (void)handleLongPress:(UILongPressGestureRecognizer *)sender { + switch (sender.state) { + case UIGestureRecognizerStateBegan: + self.dragState = MLNAnnotationViewDragStateStarting; + break; + case UIGestureRecognizerStateChanged: + self.dragState = MLNAnnotationViewDragStateDragging; + break; + case UIGestureRecognizerStateCancelled: + self.dragState = MLNAnnotationViewDragStateCanceling; + break; + case UIGestureRecognizerStateEnded: + self.dragState = MLNAnnotationViewDragStateEnding; + break; + case UIGestureRecognizerStateFailed: + self.dragState = MLNAnnotationViewDragStateNone; + break; + case UIGestureRecognizerStatePossible: + break; + } } -- (void)handlePan:(UIPanGestureRecognizer *)sender -{ - self.center = [sender locationInView:sender.view.superview]; +- (void)handlePan:(UIPanGestureRecognizer *)sender { + self.center = [sender locationInView:sender.view.superview]; - if (sender.state == UIGestureRecognizerStateEnded) { - self.dragState = MLNAnnotationViewDragStateNone; - } + if (sender.state == UIGestureRecognizerStateEnded) { + self.dragState = MLNAnnotationViewDragStateNone; + } } -- (void)setDragState:(MLNAnnotationViewDragState)dragState -{ - MLNLogDebug(@"Setting dragState: %lu", (unsigned long)dragState); - [self setDragState:dragState animated:YES]; +- (void)setDragState:(MLNAnnotationViewDragState)dragState { + MLNLogDebug(@"Setting dragState: %lu", (unsigned long)dragState); + [self setDragState:dragState animated:YES]; } -- (void)setDragState:(MLNAnnotationViewDragState)dragState animated:(BOOL)animated -{ - MLNLogDebug(@"Setting dragState: %lu animated: %@", (unsigned long)dragState, MLNStringFromBOOL(animated)); - [self willChangeValueForKey:@"dragState"]; - _dragState = dragState; - [self didChangeValueForKey:@"dragState"]; +- (void)setDragState:(MLNAnnotationViewDragState)dragState animated:(BOOL)animated { + MLNLogDebug(@"Setting dragState: %lu animated: %@", (unsigned long)dragState, + MLNStringFromBOOL(animated)); + [self willChangeValueForKey:@"dragState"]; + _dragState = dragState; + [self didChangeValueForKey:@"dragState"]; - if (dragState == MLNAnnotationViewDragStateStarting) - { - [self.mapView.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; - [self.superview bringSubviewToFront:self]; + if (dragState == MLNAnnotationViewDragStateStarting) { + [self.mapView.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; + [self.superview bringSubviewToFront:self]; + } else if (dragState == MLNAnnotationViewDragStateCanceling) { + if (!self.annotation) { + [NSException raise:NSInvalidArgumentException + format:@"Annotation property should not be nil."]; } - else if (dragState == MLNAnnotationViewDragStateCanceling) - { - if (!self.annotation) { - [NSException raise:NSInvalidArgumentException - format:@"Annotation property should not be nil."]; - } - self.panGestureRecognizer.enabled = NO; - self.longPressRecognizer.enabled = NO; - self.center = [self.mapView convertCoordinate:self.annotation.coordinate toPointToView:self.mapView]; - self.panGestureRecognizer.enabled = YES; - self.longPressRecognizer.enabled = YES; - self.dragState = MLNAnnotationViewDragStateNone; - } - else if (dragState == MLNAnnotationViewDragStateEnding) - { - if ([self.annotation respondsToSelector:@selector(setCoordinate:)]) - { - CLLocationCoordinate2D coordinate = [self.mapView convertPoint:self.center toCoordinateFromView:self.mapView]; - [(NSObject *)self.annotation setValue:[NSValue valueWithMLNCoordinate:coordinate] forKey:@"coordinate"]; - } + self.panGestureRecognizer.enabled = NO; + self.longPressRecognizer.enabled = NO; + self.center = [self.mapView convertCoordinate:self.annotation.coordinate + toPointToView:self.mapView]; + self.panGestureRecognizer.enabled = YES; + self.longPressRecognizer.enabled = YES; + self.dragState = MLNAnnotationViewDragStateNone; + } else if (dragState == MLNAnnotationViewDragStateEnding) { + if ([self.annotation respondsToSelector:@selector(setCoordinate:)]) { + CLLocationCoordinate2D coordinate = [self.mapView convertPoint:self.center + toCoordinateFromView:self.mapView]; + [(NSObject *)self.annotation setValue:[NSValue valueWithMLNCoordinate:coordinate] + forKey:@"coordinate"]; } + } } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - BOOL isDragging = self.dragState == MLNAnnotationViewDragStateDragging; +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + BOOL isDragging = self.dragState == MLNAnnotationViewDragStateDragging; - if (gestureRecognizer == _panGestureRecognizer && !(isDragging)) - { - return NO; - } + if (gestureRecognizer == _panGestureRecognizer && !(isDragging)) { + return NO; + } - return YES; + return YES; } -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer -{ - return otherGestureRecognizer == _longPressRecognizer || otherGestureRecognizer == _panGestureRecognizer; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + shouldRecognizeSimultaneouslyWithGestureRecognizer: + (UIGestureRecognizer *)otherGestureRecognizer { + return otherGestureRecognizer == _longPressRecognizer || + otherGestureRecognizer == _panGestureRecognizer; } // MARK: UIAccessibility methods - (BOOL)isAccessibilityElement { - return !self.hidden; + return !self.hidden; } - (UIAccessibilityTraits)accessibilityTraits { - return UIAccessibilityTraitButton | UIAccessibilityTraitAdjustable; + return UIAccessibilityTraitButton | UIAccessibilityTraitAdjustable; } - (NSString *)accessibilityLabel { - return [self.annotation respondsToSelector:@selector(title)] ? self.annotation.title : super.accessibilityLabel; + return [self.annotation respondsToSelector:@selector(title)] ? self.annotation.title + : super.accessibilityLabel; } - (NSString *)accessibilityValue { - return [self.annotation respondsToSelector:@selector(subtitle)] ? self.annotation.subtitle : super.accessibilityValue; + return [self.annotation respondsToSelector:@selector(subtitle)] ? self.annotation.subtitle + : super.accessibilityValue; } - (NSString *)accessibilityHint { - return NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint"); + return NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", + @"Accessibility hint"); } - (CGRect)accessibilityFrame { - CGRect accessibilityFrame = self.frame; - CGRect minimumFrame = CGRectInset({ self.center, CGSizeZero }, - -MLNAnnotationAccessibilityElementMinimumSize.width / 2, - -MLNAnnotationAccessibilityElementMinimumSize.height / 2); - accessibilityFrame = CGRectUnion(accessibilityFrame, minimumFrame); - return accessibilityFrame; + CGRect accessibilityFrame = self.frame; + CGRect minimumFrame = CGRectInset({self.center, CGSizeZero}, + -MLNAnnotationAccessibilityElementMinimumSize.width / 2, + -MLNAnnotationAccessibilityElementMinimumSize.height / 2); + accessibilityFrame = CGRectUnion(accessibilityFrame, minimumFrame); + return accessibilityFrame; } - (void)accessibilityIncrement { - [self.superview accessibilityIncrement]; + [self.superview accessibilityIncrement]; } - (void)accessibilityDecrement { - [self.superview accessibilityDecrement]; + [self.superview accessibilityDecrement]; } @end diff --git a/platform/ios/src/MLNCompassButton.mm b/platform/ios/src/MLNCompassButton.mm index 117fc8d14..cf45c9d21 100644 --- a/platform/ios/src/MLNCompassButton.mm +++ b/platform/ios/src/MLNCompassButton.mm @@ -4,8 +4,8 @@ #import "MLNGeometry.h" #import "MLNMapView_Private.h" -#import "UIImage+MLNAdditions.h" #import "NSBundle+MLNAdditions.h" +#import "UIImage+MLNAdditions.h" #include @@ -19,119 +19,131 @@ @implementation MLNCompassButton + (instancetype)compassButtonWithMapView:(MLNMapView *)mapView { - return [[MLNCompassButton alloc] initWithMapView:mapView]; + return [[MLNCompassButton alloc] initWithMapView:mapView]; } - (instancetype)initWithMapView:(MLNMapView *)mapView { - if (self = [super init]) { - self.mapView = mapView; - [self commonInit]; - } - return self; + if (self = [super init]) { + self.mapView = mapView; + [self commonInit]; + } + return self; } - (void)commonInit { - self.image = self.compassImage; + self.image = self.compassImage; - self.compassVisibility = MLNOrnamentVisibilityAdaptive; + self.compassVisibility = MLNOrnamentVisibilityAdaptive; - self.alpha = 0; - self.userInteractionEnabled = YES; - self.translatesAutoresizingMaskIntoConstraints = NO; + self.alpha = 0; + self.userInteractionEnabled = YES; + self.translatesAutoresizingMaskIntoConstraints = NO; - UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; - [self addGestureRecognizer:tapGesture]; + UITapGestureRecognizer *tapGesture = + [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; + [self addGestureRecognizer:tapGesture]; - self.accessibilityTraits = UIAccessibilityTraitButton; - self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_LABEL", nil, nil, @"Compass", @"Accessibility label"); - self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_HINT", nil, nil, @"Rotates the map to face due north", @"Accessibility hint"); + self.accessibilityTraits = UIAccessibilityTraitButton; + self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"COMPASS_A11Y_LABEL", nil, nil, + @"Compass", @"Accessibility label"); + self.accessibilityHint = NSLocalizedStringWithDefaultValue( + @"COMPASS_A11Y_HINT", nil, nil, @"Rotates the map to face due north", @"Accessibility hint"); - self.accessibilityCompassFormatter = [[MLNCompassDirectionFormatter alloc] init]; - self.accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong; + self.accessibilityCompassFormatter = [[MLNCompassDirectionFormatter alloc] init]; + self.accessibilityCompassFormatter.unitStyle = NSFormattingUnitStyleLong; - [self sizeToFit]; + [self sizeToFit]; } - (void)setCompassVisibility:(MLNOrnamentVisibility)compassVisibility { - if (_compassVisibility == compassVisibility) { return; } - _compassVisibility = compassVisibility; + if (_compassVisibility == compassVisibility) { + return; + } + _compassVisibility = compassVisibility; - [self updateCompassAnimated:NO]; + [self updateCompassAnimated:NO]; } - (UIImage *)compassImage { - UIImage *scaleImage = [UIImage mgl_resourceImageNamed:@"Compass"]; - UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, UIScreen.mainScreen.scale); - [scaleImage drawInRect:{CGPointZero, scaleImage.size}]; + UIImage *scaleImage = [UIImage mgl_resourceImageNamed:@"Compass"]; + UIGraphicsBeginImageContextWithOptions(scaleImage.size, NO, UIScreen.mainScreen.scale); + [scaleImage drawInRect:{CGPointZero, scaleImage.size}]; - UIFont *northFont; - if (@available(iOS 13.0, *)) { - northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightLight]; - } else { - northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightUltraLight]; - } + UIFont *northFont; + if (@available(iOS 13.0, *)) { + northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightLight]; + } else { + northFont = [UIFont systemFontOfSize:11 weight:UIFontWeightUltraLight]; + } - NSAttributedString *north = [[NSAttributedString alloc] initWithString:NSLocalizedStringWithDefaultValue(@"COMPASS_NORTH", nil, nil, @"N", @"Compass abbreviation for north") attributes:@{ - NSFontAttributeName: northFont, - NSForegroundColorAttributeName: [UIColor whiteColor], - }]; - CGRect stringRect = CGRectMake((scaleImage.size.width - north.size.width) / 2, - scaleImage.size.height * 0.435, - north.size.width, north.size.height); - [north drawInRect:stringRect]; + NSAttributedString *north = [[NSAttributedString alloc] + initWithString:NSLocalizedStringWithDefaultValue(@"COMPASS_NORTH", nil, nil, @"N", + @"Compass abbreviation for north") + attributes:@{ + NSFontAttributeName : northFont, + NSForegroundColorAttributeName : [UIColor whiteColor], + }]; + CGRect stringRect = + CGRectMake((scaleImage.size.width - north.size.width) / 2, scaleImage.size.height * 0.435, + north.size.width, north.size.height); + [north drawInRect:stringRect]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; } - (void)handleTapGesture:(__unused UITapGestureRecognizer *)sender { - [self.mapView resetNorth]; + [self.mapView resetNorth]; } - (void)updateCompass { - [self updateCompassAnimated:YES]; + [self updateCompassAnimated:YES]; } - (void)updateCompassAnimated:(BOOL)animated { - CLLocationDirection direction = self.mapView.direction; - CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.); - self.transform = CGAffineTransformMakeRotation(MLNRadiansFromDegrees(plateDirection)); + CLLocationDirection direction = self.mapView.direction; + CLLocationDirection plateDirection = mbgl::util::wrap(-direction, 0., 360.); + self.transform = CGAffineTransformMakeRotation(MLNRadiansFromDegrees(plateDirection)); - self.isAccessibilityElement = direction > 0; - self.accessibilityValue = [self.accessibilityCompassFormatter stringFromDirection:direction]; + self.isAccessibilityElement = direction > 0; + self.accessibilityValue = [self.accessibilityCompassFormatter stringFromDirection:direction]; - switch (self.compassVisibility) { - case MLNOrnamentVisibilityAdaptive: - if (direction > 0 && self.alpha < 1) { - [self showCompass:animated]; - } else if (direction == 0 && self.alpha > 0) { - [self hideCompass:animated]; - } - break; - case MLNOrnamentVisibilityVisible: - [self showCompass:animated]; - break; - case MLNOrnamentVisibilityHidden: - [self hideCompass:animated]; - break; - } + switch (self.compassVisibility) { + case MLNOrnamentVisibilityAdaptive: + if (direction > 0 && self.alpha < 1) { + [self showCompass:animated]; + } else if (direction == 0 && self.alpha > 0) { + [self hideCompass:animated]; + } + break; + case MLNOrnamentVisibilityVisible: + [self showCompass:animated]; + break; + case MLNOrnamentVisibilityHidden: + [self hideCompass:animated]; + break; + } } - (void)showCompass:(BOOL)animated { - animated ? [self animateToAlpha:1] : [self setAlpha:1]; + animated ? [self animateToAlpha:1] : [self setAlpha:1]; } - (void)hideCompass:(BOOL)animated { - animated ? [self animateToAlpha:0] : [self setAlpha:0]; - self.isAccessibilityElement = NO; - self.accessibilityElementsHidden = YES; + animated ? [self animateToAlpha:0] : [self setAlpha:0]; + self.isAccessibilityElement = NO; + self.accessibilityElementsHidden = YES; } - (void)animateToAlpha:(CGFloat)alpha { - [UIView animateWithDuration:MLNAnimationDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.alpha = alpha; - } completion:nil]; + [UIView animateWithDuration:MLNAnimationDuration + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + self.alpha = alpha; + } + completion:nil]; } @end diff --git a/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm b/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm index 72691ed26..40f5768d5 100644 --- a/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm +++ b/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm @@ -1,13 +1,13 @@ #import "MLNFaux3DUserLocationAnnotationView.h" -#import "MLNMapView_Private.h" +#import "MLNLocationManager_Private.h" #import "MLNMapViewDelegate.h" +#import "MLNMapView_Private.h" #import "MLNUserLocation.h" -#import "MLNUserLocationHeadingIndicator.h" +#import "MLNUserLocationAnnotationViewStyle.h" #import "MLNUserLocationHeadingArrowLayer.h" #import "MLNUserLocationHeadingBeamLayer.h" -#import "MLNLocationManager_Private.h" -#import "MLNUserLocationAnnotationViewStyle.h" +#import "MLNUserLocationHeadingIndicator.h" const CGFloat MLNUserLocationAnnotationDotSize = 22.0; const CGFloat MLNUserLocationAnnotationHaloSize = 115.0; @@ -19,619 +19,594 @@ const CGFloat MLNUserLocationHeadingUpdateThreshold = 0.01; const CGFloat MLNUserLocationApproximateZoomThreshold = 7.0; -@implementation MLNFaux3DUserLocationAnnotationView -{ - BOOL _puckModeActivated; - BOOL _approximateModeActivated; +@implementation MLNFaux3DUserLocationAnnotationView { + BOOL _puckModeActivated; + BOOL _approximateModeActivated; - CALayer *_puckDot; - CAShapeLayer *_puckArrow; + CALayer *_puckDot; + CAShapeLayer *_puckArrow; - CALayer *_headingIndicatorLayer; - CALayer *_accuracyRingLayer; - CALayer *_dotBorderLayer; - CALayer *_dotLayer; - CALayer *_haloLayer; + CALayer *_headingIndicatorLayer; + CALayer *_accuracyRingLayer; + CALayer *_dotBorderLayer; + CALayer *_dotLayer; + CALayer *_haloLayer; - CALayer *_approximateLayer; + CALayer *_approximateLayer; - CLLocationDirection _oldHeadingAccuracy; - CLLocationAccuracy _oldHorizontalAccuracy; - double _oldZoom; - double _oldPitch; + CLLocationDirection _oldHeadingAccuracy; + CLLocationAccuracy _oldHorizontalAccuracy; + double _oldZoom; + double _oldPitch; } -- (CALayer *)hitTestLayer -{ - // Only the main dot should be interactive (i.e., exclude the accuracy ring and halo). - return _dotBorderLayer ?: _puckDot; +- (CALayer *)hitTestLayer { + // Only the main dot should be interactive (i.e., exclude the accuracy ring and halo). + return _dotBorderLayer ?: _puckDot; } -- (void)update -{ - if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) - { - CGFloat frameSize = (self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithCourse) ? MLNUserLocationAnnotationPuckSize : MLNUserLocationAnnotationDotSize; - [self updateFrameWithSize:frameSize]; - } +- (void)update { + if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) { + CGFloat frameSize = (self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithCourse) + ? MLNUserLocationAnnotationPuckSize + : MLNUserLocationAnnotationDotSize; + [self updateFrameWithSize:frameSize]; + } - if (CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) - { - if (@available(iOS 14, *)) { + if (CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { + if (@available(iOS 14, *)) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 - if (![self.mapView.locationManager respondsToSelector:@selector(accuracyAuthorization)] || - self.mapView.locationManager.accuracyAuthorization == CLAccuracyAuthorizationFullAccuracy) { - [self drawPreciseLocationPuck]; - } else { - [self drawApproximate]; - [self updatePitch]; - } + if (![self.mapView.locationManager respondsToSelector:@selector(accuracyAuthorization)] || + self.mapView.locationManager.accuracyAuthorization == + CLAccuracyAuthorizationFullAccuracy) { + [self drawPreciseLocationPuck]; + } else { + [self drawApproximate]; + [self updatePitch]; + } #endif - } else { - [self drawPreciseLocationPuck]; - } - + } else { + [self drawPreciseLocationPuck]; } - - + } } - (void)drawPreciseLocationPuck { - if (_approximateModeActivated) { - [_approximateLayer removeFromSuperlayer]; - _approximateLayer = nil; + if (_approximateModeActivated) { + [_approximateLayer removeFromSuperlayer]; + _approximateLayer = nil; - _approximateModeActivated = NO; - } - (self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithCourse) ? [self drawPuck] : [self drawDot]; - [self updatePitch]; - _haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.mapView.userLocation.coordinate) || self.mapView.userLocation.location.horizontalAccuracy > 10; + _approximateModeActivated = NO; + } + (self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithCourse) ? [self drawPuck] + : [self drawDot]; + [self updatePitch]; + _haloLayer.hidden = !CLLocationCoordinate2DIsValid(self.mapView.userLocation.coordinate) || + self.mapView.userLocation.location.horizontalAccuracy > 10; } -- (void)setTintColor:(UIColor *)tintColor -{ - UIColor *puckArrowFillColor = tintColor; - UIColor *puckArrowStrokeColor = tintColor; +- (void)setTintColor:(UIColor *)tintColor { + UIColor *puckArrowFillColor = tintColor; + UIColor *puckArrowStrokeColor = tintColor; - UIColor *approximateFillColor = tintColor; + UIColor *approximateFillColor = tintColor; - UIColor *accuracyFillColor = tintColor; - UIColor *haloFillColor = tintColor; - UIColor *dotFillColor = tintColor; - UIColor *headingFillColor = tintColor; + UIColor *accuracyFillColor = tintColor; + UIColor *haloFillColor = tintColor; + UIColor *dotFillColor = tintColor; + UIColor *headingFillColor = tintColor; - if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { - MLNUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + if ([self.mapView.delegate + respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MLNUserLocationAnnotationViewStyle *style = + [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; - puckArrowFillColor = style.puckArrowFillColor ? style.puckArrowFillColor : puckArrowFillColor; + puckArrowFillColor = style.puckArrowFillColor ? style.puckArrowFillColor : puckArrowFillColor; - if (@available(iOS 14, *)) { - approximateFillColor = style.approximateHaloFillColor ? style.approximateHaloFillColor : approximateFillColor; - } - - haloFillColor = style.haloFillColor ? style.haloFillColor : haloFillColor; - dotFillColor = style.puckFillColor ? style.puckFillColor : dotFillColor; - headingFillColor = style.puckFillColor ? style.puckFillColor : headingFillColor; + if (@available(iOS 14, *)) { + approximateFillColor = + style.approximateHaloFillColor ? style.approximateHaloFillColor : approximateFillColor; } - if (_puckModeActivated) - { - _puckArrow.fillColor = [puckArrowFillColor CGColor]; - _puckArrow.strokeColor = [puckArrowStrokeColor CGColor]; - } - else if (_approximateModeActivated) - { - _approximateLayer.backgroundColor = [approximateFillColor CGColor]; - } - else - { - _accuracyRingLayer.backgroundColor = [accuracyFillColor CGColor]; - _haloLayer.backgroundColor = [haloFillColor CGColor]; - _dotLayer.backgroundColor = [dotFillColor CGColor]; - [_headingIndicatorLayer updateTintColor:[headingFillColor CGColor]]; - } + haloFillColor = style.haloFillColor ? style.haloFillColor : haloFillColor; + dotFillColor = style.puckFillColor ? style.puckFillColor : dotFillColor; + headingFillColor = style.puckFillColor ? style.puckFillColor : headingFillColor; + } + if (_puckModeActivated) { + _puckArrow.fillColor = [puckArrowFillColor CGColor]; + _puckArrow.strokeColor = [puckArrowStrokeColor CGColor]; + } else if (_approximateModeActivated) { + _approximateLayer.backgroundColor = [approximateFillColor CGColor]; + } else { + _accuracyRingLayer.backgroundColor = [accuracyFillColor CGColor]; + _haloLayer.backgroundColor = [haloFillColor CGColor]; + _dotLayer.backgroundColor = [dotFillColor CGColor]; + [_headingIndicatorLayer updateTintColor:[headingFillColor CGColor]]; + } } -- (void)updatePitch -{ - if (self.mapView.camera.pitch != _oldPitch) - { - // disable implicit animation +- (void)updatePitch { + if (self.mapView.camera.pitch != _oldPitch) { + // disable implicit animation + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + CATransform3D t = CATransform3DRotate( + CATransform3DIdentity, MLNRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0); + self.layer.sublayerTransform = t; + + [self updateFaux3DEffect]; + + [CATransaction commit]; + + _oldPitch = self.mapView.camera.pitch; + } +} + +- (void)updateFaux3DEffect { + CGFloat pitch = MLNRadiansFromDegrees(self.mapView.camera.pitch); + + if (_puckDot) { + _puckDot.shadowOffset = CGSizeMake(0, fmaxf(pitch * 10.f, 1.f)); + _puckDot.shadowRadius = fmaxf(pitch * 5.f, 0.75f); + } + + if (_dotBorderLayer) { + _dotBorderLayer.shadowOffset = CGSizeMake(0.f, pitch * 10.f); + _dotBorderLayer.shadowRadius = fmaxf(pitch * 5.f, 3.f); + } + + if (_dotLayer) { + _dotLayer.zPosition = pitch * 2.f; + } +} + +- (void)updateFrameWithSize:(CGFloat)size { + CGSize newSize = CGSizeMake(size, size); + if (CGSizeEqualToSize(self.frame.size, newSize)) { + return; + } + + // Update frame size, keeping the existing center point. + CGPoint oldCenter = self.center; + CGRect newFrame = self.frame; + newFrame.size = newSize; + [self setFrame:newFrame]; + [self setCenter:oldCenter]; +} + +- (void)drawPuck { + if (!_puckModeActivated) { + self.layer.sublayers = nil; + + _headingIndicatorLayer = nil; + _accuracyRingLayer = nil; + _haloLayer = nil; + _dotBorderLayer = nil; + _dotLayer = nil; + + [self updateFrameWithSize:MLNUserLocationAnnotationPuckSize]; + } + + UIColor *arrowColor = self.mapView.tintColor; + UIColor *puckShadowColor = UIColor.blackColor; + CGFloat shadowOpacity = 0.25; + + if ([self.mapView.delegate + respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MLNUserLocationAnnotationViewStyle *style = + [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + arrowColor = style.puckArrowFillColor ? style.puckArrowFillColor : arrowColor; + puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; + shadowOpacity = style.puckShadowOpacity; + } + + // background dot (white with black shadow) + // + if (!_puckDot) { + _puckDot = [self circleLayerWithSize:MLNUserLocationAnnotationPuckSize]; + _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; + _puckDot.shadowColor = [puckShadowColor CGColor]; + _puckDot.shadowOpacity = shadowOpacity; + _puckDot.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_puckDot.bounds] CGPath]; + + if (self.mapView.camera.pitch) { + [self updateFaux3DEffect]; + } else { + _puckDot.shadowOffset = CGSizeMake(0, 1); + _puckDot.shadowRadius = 0.75; + } + + [self.layer addSublayer:_puckDot]; + } + + // arrow + // + if (!_puckArrow) { + _puckArrow = [CAShapeLayer layer]; + _puckArrow.path = [[self puckArrow] CGPath]; + _puckArrow.fillColor = [arrowColor CGColor]; + _puckArrow.bounds = CGRectMake(0, 0, round(MLNUserLocationAnnotationArrowSize), + round(MLNUserLocationAnnotationArrowSize)); + _puckArrow.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); + _puckArrow.shouldRasterize = YES; + _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; + _puckArrow.drawsAsynchronously = YES; + + _puckArrow.lineJoin = @"round"; + _puckArrow.lineWidth = 1.f; + _puckArrow.strokeColor = _puckArrow.fillColor; + + [self.layer addSublayer:_puckArrow]; + } + if (self.userLocation.location.course >= 0) { + _puckArrow.affineTransform = CGAffineTransformRotate( + CGAffineTransformIdentity, + -MLNRadiansFromDegrees(self.mapView.direction - self.userLocation.location.course)); + } + + if (!_puckModeActivated) { + _puckModeActivated = YES; + + [self updateFaux3DEffect]; + } +} + +- (UIBezierPath *)puckArrow { + CGFloat max = MLNUserLocationAnnotationArrowSize; + + UIBezierPath *bezierPath = UIBezierPath.bezierPath; + [bezierPath moveToPoint:CGPointMake(max * 0.5, 0)]; + [bezierPath addLineToPoint:CGPointMake(max * 0.1, max)]; + [bezierPath addLineToPoint:CGPointMake(max * 0.5, max * 0.65)]; + [bezierPath addLineToPoint:CGPointMake(max * 0.9, max)]; + [bezierPath addLineToPoint:CGPointMake(max * 0.5, 0)]; + [bezierPath closePath]; + + return bezierPath; +} + +- (void)drawDot { + if (_puckModeActivated) { + self.layer.sublayers = nil; + + _puckDot = nil; + _puckArrow = nil; + + [self updateFrameWithSize:MLNUserLocationAnnotationDotSize]; + } + + UIColor *haloColor = self.mapView.tintColor; + UIColor *puckBackgroundColor = self.mapView.tintColor; + UIColor *puckShadowColor = UIColor.blackColor; + CGFloat shadowOpacity = 0.25; + + if ([self.mapView.delegate + respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MLNUserLocationAnnotationViewStyle *style = + [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + haloColor = style.haloFillColor ? style.haloFillColor : haloColor; + puckBackgroundColor = style.puckFillColor ? style.puckFillColor : puckBackgroundColor; + puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; + shadowOpacity = style.puckShadowOpacity; + } + + // heading indicator (tinted, beam or arrow) + // + BOOL headingTrackingModeEnabled = + self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithHeading; + BOOL showHeadingIndicator = self.mapView.showsUserHeadingIndicator || headingTrackingModeEnabled; + + if (showHeadingIndicator) { + _headingIndicatorLayer.hidden = NO; + CLLocationDirection headingAccuracy = self.userLocation.heading.headingAccuracy; + + if (([_headingIndicatorLayer isMemberOfClass:[MLNUserLocationHeadingBeamLayer class]] && + !headingTrackingModeEnabled) || + ([_headingIndicatorLayer isMemberOfClass:[MLNUserLocationHeadingArrowLayer class]] && + headingTrackingModeEnabled)) { + [_headingIndicatorLayer removeFromSuperlayer]; + _headingIndicatorLayer = nil; + _oldHeadingAccuracy = -1; + } + + if (!_headingIndicatorLayer && headingAccuracy) { + if (headingTrackingModeEnabled) { + _headingIndicatorLayer = + [[MLNUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self]; + [_headingIndicatorLayer updateTintColor:[haloColor CGColor]]; + [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; + } else { + _headingIndicatorLayer = + [[MLNUserLocationHeadingArrowLayer alloc] initWithUserLocationAnnotationView:self]; + [_headingIndicatorLayer updateTintColor:[puckBackgroundColor CGColor]]; + [self.layer addSublayer:_headingIndicatorLayer]; + _headingIndicatorLayer.zPosition = 1; + } + } + + if (_oldHeadingAccuracy != headingAccuracy) { + [_headingIndicatorLayer updateHeadingAccuracy:headingAccuracy]; + _oldHeadingAccuracy = headingAccuracy; + } + + CLLocationDirection headingDirection = + (self.userLocation.heading.trueHeading >= 0 ? self.userLocation.heading.trueHeading + : self.userLocation.heading.magneticHeading); + if (headingDirection >= 0) { + CGFloat rotation = -MLNRadiansFromDegrees(self.mapView.direction - headingDirection); + + // Don't rotate if the change is imperceptible. + if (fabs(rotation) > MLNUserLocationHeadingUpdateThreshold) { [CATransaction begin]; [CATransaction setDisableActions:YES]; - CATransform3D t = CATransform3DRotate(CATransform3DIdentity, MLNRadiansFromDegrees(self.mapView.camera.pitch), 1.0, 0, 0); - self.layer.sublayerTransform = t; - - [self updateFaux3DEffect]; + _headingIndicatorLayer.affineTransform = + CGAffineTransformRotate(CGAffineTransformIdentity, rotation); [CATransaction commit]; - - _oldPitch = self.mapView.camera.pitch; + } } + } else { + [_headingIndicatorLayer removeFromSuperlayer]; + _headingIndicatorLayer = nil; + } + + // update accuracy ring (if zoom or horizontal accuracy have changed) + // + if (_accuracyRingLayer && + (_oldZoom != self.mapView.zoomLevel || + _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy)) { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize:self.mapView.zoomLevel]; + + // only show the accuracy ring if it won't be obscured by the location dot + if (accuracyRingSize > MLNUserLocationAnnotationDotSize + 15) { + _accuracyRingLayer.hidden = NO; + + // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy + BOOL shouldDisableActions = + _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy; + + [CATransaction begin]; + [CATransaction setDisableActions:shouldDisableActions]; + + _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _accuracyRingLayer.cornerRadius = accuracyRingSize / 2.0; + + // match the halo to the accuracy ring + _haloLayer.bounds = _accuracyRingLayer.bounds; + _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; + _haloLayer.shouldRasterize = NO; + + [CATransaction commit]; + } else { + _accuracyRingLayer.hidden = YES; + + _haloLayer.bounds = + CGRectMake(0, 0, MLNUserLocationAnnotationHaloSize, MLNUserLocationAnnotationHaloSize); + _haloLayer.cornerRadius = MLNUserLocationAnnotationHaloSize / 2.0; + _haloLayer.shouldRasterize = YES; + _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; + } + + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } + + // accuracy ring (circular, tinted, mostly-transparent) + // + if (!_accuracyRingLayer && self.userLocation.location.horizontalAccuracy) { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize:self.mapView.zoomLevel]; + _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; + _accuracyRingLayer.backgroundColor = [self.mapView.tintColor CGColor]; + _accuracyRingLayer.opacity = 0.1; + _accuracyRingLayer.shouldRasterize = NO; + _accuracyRingLayer.allowsGroupOpacity = NO; + + [self.layer addSublayer:_accuracyRingLayer]; + } + + // expanding sonar-like pulse (circular, tinted, fades out) + // + if (!_haloLayer) { + _haloLayer = [self circleLayerWithSize:MLNUserLocationAnnotationHaloSize]; + _haloLayer.backgroundColor = [haloColor CGColor]; + _haloLayer.allowsGroupOpacity = NO; + _haloLayer.zPosition = -0.1f; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; + + // scale out radially with initial acceleration + CAKeyframeAnimation *boundsAnimation = + [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; + boundsAnimation.values = @[ @0, @0.35, @1 ]; + boundsAnimation.keyTimes = @[ @0, @0.2, @1 ]; + + // go transparent as scaled out, start semi-opaque + CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.values = @[ @0.4, @0.4, @0 ]; + opacityAnimation.keyTimes = @[ @0, @0.2, @1 ]; + + animationGroup.animations = @[ boundsAnimation, opacityAnimation ]; + + [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_haloLayer]; + } + + // background dot (white with black shadow) + // + if (!_dotBorderLayer) { + _dotBorderLayer = [self circleLayerWithSize:MLNUserLocationAnnotationDotSize]; + _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; + _dotBorderLayer.shadowColor = [puckShadowColor CGColor]; + _dotBorderLayer.shadowOpacity = shadowOpacity; + _dotBorderLayer.shadowPath = + [[UIBezierPath bezierPathWithOvalInRect:_dotBorderLayer.bounds] CGPath]; + + if (self.mapView.camera.pitch) { + [self updateFaux3DEffect]; + } else { + _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); + _dotBorderLayer.shadowRadius = 3; + } + + [self.layer addSublayer:_dotBorderLayer]; + } + + // inner dot (pulsing, tinted) + // + if (!_dotLayer) { + _dotLayer = [self circleLayerWithSize:MLNUserLocationAnnotationDotSize * 0.75]; + _dotLayer.backgroundColor = [puckBackgroundColor CGColor]; + + // set defaults for the animations + CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; + animationGroup.autoreverses = YES; + animationGroup.fillMode = kCAFillModeBoth; + + // scale the dot up and down + CABasicAnimation *pulseAnimation = + [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; + pulseAnimation.fromValue = @0.8; + pulseAnimation.toValue = @1; + + // fade opacity in and out, subtly + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @0.8; + opacityAnimation.toValue = @1; + + animationGroup.animations = @[ pulseAnimation, opacityAnimation ]; + + [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; + + [self.layer addSublayer:_dotLayer]; + } + + if (_puckModeActivated) { + _puckModeActivated = NO; + + [self updateFaux3DEffect]; + } } -- (void)updateFaux3DEffect -{ - CGFloat pitch = MLNRadiansFromDegrees(self.mapView.camera.pitch); +- (void)drawApproximate { + if (!_approximateModeActivated) { + self.layer.sublayers = nil; - if (_puckDot) - { - _puckDot.shadowOffset = CGSizeMake(0, fmaxf(pitch * 10.f, 1.f)); - _puckDot.shadowRadius = fmaxf(pitch * 5.f, 0.75f); + _headingIndicatorLayer = nil; + _dotBorderLayer = nil; + _dotLayer = nil; + _accuracyRingLayer = nil; + _haloLayer = nil; + _puckDot = nil; + _puckArrow = nil; + + _approximateModeActivated = YES; + } + + UIColor *backgroundColor = self.mapView.tintColor; + UIColor *strokeColor = UIColor.blackColor; + CGFloat borderSize = 2.0; + CGFloat opacity = 0.25; + + if ([self.mapView.delegate + respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { + MLNUserLocationAnnotationViewStyle *style = + [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; + if (@available(iOS 14, *)) { + backgroundColor = + style.approximateHaloFillColor ? style.approximateHaloFillColor : backgroundColor; + strokeColor = + style.approximateHaloBorderColor ? style.approximateHaloBorderColor : strokeColor; + opacity = style.approximateHaloOpacity; + borderSize = style.approximateHaloBorderWidth; + } + } + + // approximate ring + if (!_approximateLayer && self.userLocation.location.horizontalAccuracy) { + CGFloat accuracyRingSize = + [self calculateAccuracyRingSize:MAX(self.mapView.zoomLevel, + MLNUserLocationApproximateZoomThreshold)]; + _approximateLayer = [self circleLayerWithSize:accuracyRingSize]; + _approximateLayer.backgroundColor = [backgroundColor CGColor]; + _approximateLayer.opacity = opacity; + _approximateLayer.shouldRasterize = NO; + _approximateLayer.allowsGroupOpacity = NO; + _approximateLayer.borderWidth = borderSize; + _approximateLayer.borderColor = [strokeColor CGColor]; + + [self.layer addSublayer:_approximateLayer]; + } + + // update approximate ring (if zoom or horizontal accuracy have changed) + if (_approximateLayer && + (_oldZoom != self.mapView.zoomLevel || + _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy)) { + if (self.mapView.zoomLevel < MLNUserLocationApproximateZoomThreshold) { + borderSize = 1.0; + } + _approximateLayer.borderWidth = borderSize; + + if (self.mapView.zoomLevel >= MLNUserLocationApproximateZoomThreshold) { + CGFloat accuracyRingSize = [self calculateAccuracyRingSize:self.mapView.zoomLevel]; + + _approximateLayer.hidden = NO; + + // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy + BOOL shouldDisableActions = + _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy; + + [CATransaction begin]; + [CATransaction setDisableActions:shouldDisableActions]; + + _approximateLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); + _approximateLayer.cornerRadius = accuracyRingSize / 2.0; + + [CATransaction commit]; } - if (_dotBorderLayer) - { - _dotBorderLayer.shadowOffset = CGSizeMake(0.f, pitch * 10.f); - _dotBorderLayer.shadowRadius = fmaxf(pitch * 5.f, 3.f); - } - - if (_dotLayer) - { - _dotLayer.zPosition = pitch * 2.f; - } + // store accuracy and zoom so we're not redrawing unchanged location updates + _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy; + _oldZoom = self.mapView.zoomLevel; + } } -- (void)updateFrameWithSize:(CGFloat)size -{ - CGSize newSize = CGSizeMake(size, size); - if (CGSizeEqualToSize(self.frame.size, newSize)) - { - return; - } +- (CALayer *)circleLayerWithSize:(CGFloat)layerSize { + layerSize = round(layerSize); - // Update frame size, keeping the existing center point. - CGPoint oldCenter = self.center; - CGRect newFrame = self.frame; - newFrame.size = newSize; - [self setFrame:newFrame]; - [self setCenter:oldCenter]; + CALayer *circleLayer = [CALayer layer]; + circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); + circleLayer.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); + circleLayer.cornerRadius = layerSize / 2.0; + circleLayer.shouldRasterize = YES; + circleLayer.rasterizationScale = [UIScreen mainScreen].scale; + circleLayer.drawsAsynchronously = YES; + + return circleLayer; } -- (void)drawPuck -{ - if ( ! _puckModeActivated) - { - self.layer.sublayers = nil; +- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration { + CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; + animationGroup.duration = animationDuration; + animationGroup.repeatCount = INFINITY; + animationGroup.removedOnCompletion = NO; + animationGroup.timingFunction = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - _headingIndicatorLayer = nil; - _accuracyRingLayer = nil; - _haloLayer = nil; - _dotBorderLayer = nil; - _dotLayer = nil; - - [self updateFrameWithSize:MLNUserLocationAnnotationPuckSize]; - } - - UIColor *arrowColor = self.mapView.tintColor; - UIColor *puckShadowColor = UIColor.blackColor; - CGFloat shadowOpacity = 0.25; - - - if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { - MLNUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; - arrowColor = style.puckArrowFillColor ? style.puckArrowFillColor : arrowColor; - puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; - shadowOpacity = style.puckShadowOpacity; - } - - // background dot (white with black shadow) - // - if ( ! _puckDot) - { - _puckDot = [self circleLayerWithSize:MLNUserLocationAnnotationPuckSize]; - _puckDot.backgroundColor = [[UIColor whiteColor] CGColor]; - _puckDot.shadowColor = [puckShadowColor CGColor]; - _puckDot.shadowOpacity = shadowOpacity; - _puckDot.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_puckDot.bounds] CGPath]; - - if (self.mapView.camera.pitch) - { - [self updateFaux3DEffect]; - } - else - { - _puckDot.shadowOffset = CGSizeMake(0, 1); - _puckDot.shadowRadius = 0.75; - } - - [self.layer addSublayer:_puckDot]; - } - - // arrow - // - if ( ! _puckArrow) - { - _puckArrow = [CAShapeLayer layer]; - _puckArrow.path = [[self puckArrow] CGPath]; - _puckArrow.fillColor = [arrowColor CGColor]; - _puckArrow.bounds = CGRectMake(0, 0, round(MLNUserLocationAnnotationArrowSize), round(MLNUserLocationAnnotationArrowSize)); - _puckArrow.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); - _puckArrow.shouldRasterize = YES; - _puckArrow.rasterizationScale = [UIScreen mainScreen].scale; - _puckArrow.drawsAsynchronously = YES; - - _puckArrow.lineJoin = @"round"; - _puckArrow.lineWidth = 1.f; - _puckArrow.strokeColor = _puckArrow.fillColor; - - [self.layer addSublayer:_puckArrow]; - } - if (self.userLocation.location.course >= 0) - { - _puckArrow.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, -MLNRadiansFromDegrees(self.mapView.direction - self.userLocation.location.course)); - } - - if ( ! _puckModeActivated) - { - _puckModeActivated = YES; - - [self updateFaux3DEffect]; - } + return animationGroup; } -- (UIBezierPath *)puckArrow -{ - CGFloat max = MLNUserLocationAnnotationArrowSize; - - UIBezierPath *bezierPath = UIBezierPath.bezierPath; - [bezierPath moveToPoint: CGPointMake(max * 0.5, 0)]; - [bezierPath addLineToPoint: CGPointMake(max * 0.1, max)]; - [bezierPath addLineToPoint: CGPointMake(max * 0.5, max * 0.65)]; - [bezierPath addLineToPoint: CGPointMake(max * 0.9, max)]; - [bezierPath addLineToPoint: CGPointMake(max * 0.5, 0)]; - [bezierPath closePath]; - - return bezierPath; -} - -- (void)drawDot -{ - if (_puckModeActivated) - { - self.layer.sublayers = nil; - - _puckDot = nil; - _puckArrow = nil; - - [self updateFrameWithSize:MLNUserLocationAnnotationDotSize]; - } - - UIColor *haloColor = self.mapView.tintColor; - UIColor *puckBackgroundColor = self.mapView.tintColor; - UIColor *puckShadowColor = UIColor.blackColor; - CGFloat shadowOpacity = 0.25; - - - if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { - MLNUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; - haloColor = style.haloFillColor ? style.haloFillColor : haloColor; - puckBackgroundColor = style.puckFillColor ? style.puckFillColor : puckBackgroundColor; - puckShadowColor = style.puckShadowColor ? style.puckShadowColor : puckShadowColor; - shadowOpacity = style.puckShadowOpacity; - } - - // heading indicator (tinted, beam or arrow) - // - BOOL headingTrackingModeEnabled = self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithHeading; - BOOL showHeadingIndicator = self.mapView.showsUserHeadingIndicator || headingTrackingModeEnabled; - - if (showHeadingIndicator) - { - _headingIndicatorLayer.hidden = NO; - CLLocationDirection headingAccuracy = self.userLocation.heading.headingAccuracy; - - if (([_headingIndicatorLayer isMemberOfClass:[MLNUserLocationHeadingBeamLayer class]] && ! headingTrackingModeEnabled) || - ([_headingIndicatorLayer isMemberOfClass:[MLNUserLocationHeadingArrowLayer class]] && headingTrackingModeEnabled)) - { - [_headingIndicatorLayer removeFromSuperlayer]; - _headingIndicatorLayer = nil; - _oldHeadingAccuracy = -1; - } - - if ( ! _headingIndicatorLayer && headingAccuracy) - { - if (headingTrackingModeEnabled) - { - _headingIndicatorLayer = [[MLNUserLocationHeadingBeamLayer alloc] initWithUserLocationAnnotationView:self]; - [_headingIndicatorLayer updateTintColor:[haloColor CGColor]]; - [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer]; - } - else - { - _headingIndicatorLayer = [[MLNUserLocationHeadingArrowLayer alloc] initWithUserLocationAnnotationView:self]; - [_headingIndicatorLayer updateTintColor:[puckBackgroundColor CGColor]]; - [self.layer addSublayer:_headingIndicatorLayer]; - _headingIndicatorLayer.zPosition = 1; - } - } - - if (_oldHeadingAccuracy != headingAccuracy) - { - [_headingIndicatorLayer updateHeadingAccuracy:headingAccuracy]; - _oldHeadingAccuracy = headingAccuracy; - } - - CLLocationDirection headingDirection = (self.userLocation.heading.trueHeading >= 0 ? self.userLocation.heading.trueHeading : self.userLocation.heading.magneticHeading); - if (headingDirection >= 0) - { - CGFloat rotation = -MLNRadiansFromDegrees(self.mapView.direction - headingDirection); - - // Don't rotate if the change is imperceptible. - if (fabs(rotation) > MLNUserLocationHeadingUpdateThreshold) - { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - - _headingIndicatorLayer.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, rotation); - - [CATransaction commit]; - } - } - } - else - { - [_headingIndicatorLayer removeFromSuperlayer]; - _headingIndicatorLayer = nil; - } - - // update accuracy ring (if zoom or horizontal accuracy have changed) - // - if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy)) - { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize: self.mapView.zoomLevel]; - - // only show the accuracy ring if it won't be obscured by the location dot - if (accuracyRingSize > MLNUserLocationAnnotationDotSize + 15) - { - _accuracyRingLayer.hidden = NO; - - // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy - BOOL shouldDisableActions = _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy; - - [CATransaction begin]; - [CATransaction setDisableActions:shouldDisableActions]; - - _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); - _accuracyRingLayer.cornerRadius = accuracyRingSize / 2.0; - - // match the halo to the accuracy ring - _haloLayer.bounds = _accuracyRingLayer.bounds; - _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius; - _haloLayer.shouldRasterize = NO; - - [CATransaction commit]; - } - else - { - _accuracyRingLayer.hidden = YES; - - _haloLayer.bounds = CGRectMake(0, 0, MLNUserLocationAnnotationHaloSize, MLNUserLocationAnnotationHaloSize); - _haloLayer.cornerRadius = MLNUserLocationAnnotationHaloSize / 2.0; - _haloLayer.shouldRasterize = YES; - _haloLayer.rasterizationScale = [UIScreen mainScreen].scale; - } - - // store accuracy and zoom so we're not redrawing unchanged location updates - _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy; - _oldZoom = self.mapView.zoomLevel; - } - - // accuracy ring (circular, tinted, mostly-transparent) - // - if ( ! _accuracyRingLayer && self.userLocation.location.horizontalAccuracy) - { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize: self.mapView.zoomLevel]; - _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize]; - _accuracyRingLayer.backgroundColor = [self.mapView.tintColor CGColor]; - _accuracyRingLayer.opacity = 0.1; - _accuracyRingLayer.shouldRasterize = NO; - _accuracyRingLayer.allowsGroupOpacity = NO; - - [self.layer addSublayer:_accuracyRingLayer]; - } - - // expanding sonar-like pulse (circular, tinted, fades out) - // - if ( ! _haloLayer) - { - _haloLayer = [self circleLayerWithSize:MLNUserLocationAnnotationHaloSize]; - _haloLayer.backgroundColor = [haloColor CGColor]; - _haloLayer.allowsGroupOpacity = NO; - _haloLayer.zPosition = -0.1f; - - // set defaults for the animations - CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0]; - - // scale out radially with initial acceleration - CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"]; - boundsAnimation.values = @[@0, @0.35, @1]; - boundsAnimation.keyTimes = @[@0, @0.2, @1]; - - // go transparent as scaled out, start semi-opaque - CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.values = @[@0.4, @0.4, @0]; - opacityAnimation.keyTimes = @[@0, @0.2, @1]; - - animationGroup.animations = @[boundsAnimation, opacityAnimation]; - - [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; - - [self.layer addSublayer:_haloLayer]; - } - - // background dot (white with black shadow) - // - if ( ! _dotBorderLayer) - { - _dotBorderLayer = [self circleLayerWithSize:MLNUserLocationAnnotationDotSize]; - _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor]; - _dotBorderLayer.shadowColor = [puckShadowColor CGColor]; - _dotBorderLayer.shadowOpacity = shadowOpacity; - _dotBorderLayer.shadowPath = [[UIBezierPath bezierPathWithOvalInRect:_dotBorderLayer.bounds] CGPath]; - - if (self.mapView.camera.pitch) - { - [self updateFaux3DEffect]; - } - else - { - _dotBorderLayer.shadowOffset = CGSizeMake(0, 0); - _dotBorderLayer.shadowRadius = 3; - } - - [self.layer addSublayer:_dotBorderLayer]; - } - - // inner dot (pulsing, tinted) - // - if ( ! _dotLayer) - { - _dotLayer = [self circleLayerWithSize:MLNUserLocationAnnotationDotSize * 0.75]; - _dotLayer.backgroundColor = [puckBackgroundColor CGColor]; - - // set defaults for the animations - CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5]; - animationGroup.autoreverses = YES; - animationGroup.fillMode = kCAFillModeBoth; - - // scale the dot up and down - CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"]; - pulseAnimation.fromValue = @0.8; - pulseAnimation.toValue = @1; - - // fade opacity in and out, subtly - CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; - opacityAnimation.fromValue = @0.8; - opacityAnimation.toValue = @1; - - animationGroup.animations = @[pulseAnimation, opacityAnimation]; - - [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"]; - - [self.layer addSublayer:_dotLayer]; - } - - if (_puckModeActivated) - { - _puckModeActivated = NO; - - [self updateFaux3DEffect]; - } - -} - -- (void)drawApproximate -{ - - if ( ! _approximateModeActivated) - { - self.layer.sublayers = nil; - - _headingIndicatorLayer = nil; - _dotBorderLayer = nil; - _dotLayer = nil; - _accuracyRingLayer = nil; - _haloLayer = nil; - _puckDot = nil; - _puckArrow = nil; - - _approximateModeActivated = YES; - } - - UIColor *backgroundColor = self.mapView.tintColor; - UIColor *strokeColor = UIColor.blackColor; - CGFloat borderSize = 2.0; - CGFloat opacity = 0.25; - - if ([self.mapView.delegate respondsToSelector:@selector(mapViewStyleForDefaultUserLocationAnnotationView:)]) { - MLNUserLocationAnnotationViewStyle *style = [self.mapView.delegate mapViewStyleForDefaultUserLocationAnnotationView:self.mapView]; - if (@available(iOS 14, *)) { - backgroundColor = style.approximateHaloFillColor ? style.approximateHaloFillColor : backgroundColor; - strokeColor = style.approximateHaloBorderColor ? style.approximateHaloBorderColor : strokeColor; - opacity = style.approximateHaloOpacity; - borderSize = style.approximateHaloBorderWidth; - } - } - - // approximate ring - if ( ! _approximateLayer && self.userLocation.location.horizontalAccuracy) - { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize: MAX(self.mapView.zoomLevel, MLNUserLocationApproximateZoomThreshold)]; - _approximateLayer = [self circleLayerWithSize:accuracyRingSize]; - _approximateLayer.backgroundColor = [backgroundColor CGColor]; - _approximateLayer.opacity = opacity; - _approximateLayer.shouldRasterize = NO; - _approximateLayer.allowsGroupOpacity = NO; - _approximateLayer.borderWidth = borderSize; - _approximateLayer.borderColor = [strokeColor CGColor]; - - [self.layer addSublayer:_approximateLayer]; - } - - // update approximate ring (if zoom or horizontal accuracy have changed) - if (_approximateLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.userLocation.location.horizontalAccuracy)) - { - if (self.mapView.zoomLevel < MLNUserLocationApproximateZoomThreshold) { - borderSize = 1.0; - } - _approximateLayer.borderWidth = borderSize; - - if (self.mapView.zoomLevel >= MLNUserLocationApproximateZoomThreshold) { - CGFloat accuracyRingSize = [self calculateAccuracyRingSize: self.mapView.zoomLevel]; - - _approximateLayer.hidden = NO; - - // disable implicit animation of the accuracy ring, unless triggered by a change in accuracy - BOOL shouldDisableActions = _oldHorizontalAccuracy == self.userLocation.location.horizontalAccuracy; - - [CATransaction begin]; - [CATransaction setDisableActions:shouldDisableActions]; - - _approximateLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize); - _approximateLayer.cornerRadius = accuracyRingSize / 2.0; - - [CATransaction commit]; - } - - // store accuracy and zoom so we're not redrawing unchanged location updates - _oldHorizontalAccuracy = self.userLocation.location.horizontalAccuracy; - _oldZoom = self.mapView.zoomLevel; - } -} - -- (CALayer *)circleLayerWithSize:(CGFloat)layerSize -{ - layerSize = round(layerSize); - - CALayer *circleLayer = [CALayer layer]; - circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize); - circleLayer.position = CGPointMake(CGRectGetMidX(super.bounds), CGRectGetMidY(super.bounds)); - circleLayer.cornerRadius = layerSize / 2.0; - circleLayer.shouldRasterize = YES; - circleLayer.rasterizationScale = [UIScreen mainScreen].scale; - circleLayer.drawsAsynchronously = YES; - - return circleLayer; -} - -- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration -{ - CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; - animationGroup.duration = animationDuration; - animationGroup.repeatCount = INFINITY; - animationGroup.removedOnCompletion = NO; - animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - - return animationGroup; -} - -- (CGFloat)calculateAccuracyRingSize:(double)zoomLevel -{ - // diameter in screen points - return round(self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude zoomLevel:zoomLevel] * 2.0); +- (CGFloat)calculateAccuracyRingSize:(double)zoomLevel { + // diameter in screen points + return round(self.userLocation.location.horizontalAccuracy / + [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude + zoomLevel:zoomLevel] * + 2.0); } @end diff --git a/platform/ios/src/MLNMapAccessibilityElement.mm b/platform/ios/src/MLNMapAccessibilityElement.mm index 3c07c2323..2df0d6f33 100644 --- a/platform/ios/src/MLNMapAccessibilityElement.mm +++ b/platform/ios/src/MLNMapAccessibilityElement.mm @@ -1,6 +1,6 @@ #import "MLNMapAccessibilityElement.h" -#import "MLNDistanceFormatter.h" #import "MLNCompassDirectionFormatter.h" +#import "MLNDistanceFormatter.h" #import "MLNFeature.h" #import "MLNGeometry_Private.h" @@ -13,15 +13,15 @@ @implementation MLNMapAccessibilityElement - (UIAccessibilityTraits)accessibilityTraits { - return super.accessibilityTraits | UIAccessibilityTraitAdjustable; + return super.accessibilityTraits | UIAccessibilityTraitAdjustable; } - (void)accessibilityIncrement { - [self.accessibilityContainer accessibilityIncrement]; + [self.accessibilityContainer accessibilityIncrement]; } - (void)accessibilityDecrement { - [self.accessibilityContainer accessibilityDecrement]; + [self.accessibilityContainer accessibilityDecrement]; } @end @@ -29,15 +29,16 @@ @implementation MLNAnnotationAccessibilityElement - (instancetype)initWithAccessibilityContainer:(id)container tag:(MLNAnnotationTag)tag { - if (self = [super initWithAccessibilityContainer:container]) { - _tag = tag; - self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint"); - } - return self; + if (self = [super initWithAccessibilityContainer:container]) { + _tag = tag; + self.accessibilityHint = NSLocalizedStringWithDefaultValue( + @"ANNOTATION_A11Y_HINT", nil, nil, @"Shows more info", @"Accessibility hint"); + } + return self; } - (UIAccessibilityTraits)accessibilityTraits { - return super.accessibilityTraits | UIAccessibilityTraitButton; + return super.accessibilityTraits | UIAccessibilityTraitButton; } @end @@ -45,27 +46,28 @@ @implementation MLNFeatureAccessibilityElement - (instancetype)initWithAccessibilityContainer:(id)container feature:(id)feature { - if (self = [super initWithAccessibilityContainer:container]) { - _feature = feature; + if (self = [super initWithAccessibilityContainer:container]) { + _feature = feature; - NSString *languageCode = [MLNVectorTileSource preferredMapboxStreetsLanguage]; - NSString *nameAttribute = [NSString stringWithFormat:@"name_%@", languageCode]; - NSString *name = [feature attributeForKey:nameAttribute]; + NSString *languageCode = [MLNVectorTileSource preferredMapboxStreetsLanguage]; + NSString *nameAttribute = [NSString stringWithFormat:@"name_%@", languageCode]; + NSString *name = [feature attributeForKey:nameAttribute]; - // If a feature hasn’t been translated into the preferred language, it - // may be in the local language, which may be written in another script. - // Attempt to transform to the script of the preferred language, keeping - // the original string if no transform exists or if transformation fails. - NSString *dominantScript = [NSOrthography mgl_dominantScriptForMapboxStreetsLanguage:languageCode]; - name = [name mgl_stringByTransliteratingIntoScript:dominantScript]; + // If a feature hasn’t been translated into the preferred language, it + // may be in the local language, which may be written in another script. + // Attempt to transform to the script of the preferred language, keeping + // the original string if no transform exists or if transformation fails. + NSString *dominantScript = + [NSOrthography mgl_dominantScriptForMapboxStreetsLanguage:languageCode]; + name = [name mgl_stringByTransliteratingIntoScript:dominantScript]; - self.accessibilityLabel = name; - } - return self; + self.accessibilityLabel = name; + } + return self; } - (UIAccessibilityTraits)accessibilityTraits { - return super.accessibilityTraits | UIAccessibilityTraitStaticText; + return super.accessibilityTraits | UIAccessibilityTraitStaticText; } @end @@ -73,53 +75,54 @@ @implementation MLNPlaceFeatureAccessibilityElement - (instancetype)initWithAccessibilityContainer:(id)container feature:(id)feature { - if (self = [super initWithAccessibilityContainer:container feature:feature]) { - NSDictionary *attributes = feature.attributes; - NSMutableArray *facts = [NSMutableArray array]; + if (self = [super initWithAccessibilityContainer:container feature:feature]) { + NSDictionary *attributes = feature.attributes; + NSMutableArray *facts = [NSMutableArray array]; - // Announce the kind of place or POI. - NSString *languageCode = [MLNVectorTileSource preferredMapboxStreetsLanguage]; - NSString *categoryAttribute = [NSString stringWithFormat:@"category_%@", languageCode]; - if (attributes[categoryAttribute]) { - [facts addObject:attributes[categoryAttribute]]; - } else if (attributes[@"type"]) { - // FIXME: Unfortunately, these types aren’t a closed set that can be - // localized, since they’re based on OpenStreetMap tags. - NSString *type = [attributes[@"type"] stringByReplacingOccurrencesOfString:@"_" - withString:@" "]; - [facts addObject:type]; - } - // Announce the kind of airport, rail station, or mountain based on its - // Maki image name. - else if (attributes[@"maki"]) { - [facts addObject:attributes[@"maki"]]; - } - - // Announce the peak’s elevation in the preferred units. - if (attributes[@"elevation_m"] ?: attributes[@"elevation_ft"]) { - NSLengthFormatter *formatter = [[NSLengthFormatter alloc] init]; - formatter.unitStyle = NSFormattingUnitStyleLong; - - NSNumber *elevationValue; - NSLengthFormatterUnit unit; - BOOL usesMetricSystem = ![[formatter.numberFormatter.locale objectForKey:NSLocaleMeasurementSystem] - isEqualToString:@"U.S."]; - if (usesMetricSystem) { - elevationValue = attributes[@"elevation_m"]; - unit = NSLengthFormatterUnitMeter; - } else { - elevationValue = attributes[@"elevation_ft"]; - unit = NSLengthFormatterUnitFoot; - } - [facts addObject:[formatter stringFromValue:elevationValue.doubleValue unit:unit]]; - } - - if (facts.count) { - NSString *separator = NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); - self.accessibilityValue = [facts componentsJoinedByString:separator]; - } + // Announce the kind of place or POI. + NSString *languageCode = [MLNVectorTileSource preferredMapboxStreetsLanguage]; + NSString *categoryAttribute = [NSString stringWithFormat:@"category_%@", languageCode]; + if (attributes[categoryAttribute]) { + [facts addObject:attributes[categoryAttribute]]; + } else if (attributes[@"type"]) { + // FIXME: Unfortunately, these types aren’t a closed set that can be + // localized, since they’re based on OpenStreetMap tags. + NSString *type = [attributes[@"type"] stringByReplacingOccurrencesOfString:@"_" + withString:@" "]; + [facts addObject:type]; } - return self; + // Announce the kind of airport, rail station, or mountain based on its + // Maki image name. + else if (attributes[@"maki"]) { + [facts addObject:attributes[@"maki"]]; + } + + // Announce the peak’s elevation in the preferred units. + if (attributes[@"elevation_m"] ?: attributes[@"elevation_ft"]) { + NSLengthFormatter *formatter = [[NSLengthFormatter alloc] init]; + formatter.unitStyle = NSFormattingUnitStyleLong; + + NSNumber *elevationValue; + NSLengthFormatterUnit unit; + BOOL usesMetricSystem = ![[formatter.numberFormatter.locale + objectForKey:NSLocaleMeasurementSystem] isEqualToString:@"U.S."]; + if (usesMetricSystem) { + elevationValue = attributes[@"elevation_m"]; + unit = NSLengthFormatterUnitMeter; + } else { + elevationValue = attributes[@"elevation_ft"]; + unit = NSLengthFormatterUnitFoot; + } + [facts addObject:[formatter stringFromValue:elevationValue.doubleValue unit:unit]]; + } + + if (facts.count) { + NSString *separator = + NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); + self.accessibilityValue = [facts componentsJoinedByString:separator]; + } + } + return self; } @end @@ -127,56 +130,74 @@ @implementation MLNRoadFeatureAccessibilityElement - (instancetype)initWithAccessibilityContainer:(id)container feature:(id)feature { - if (self = [super initWithAccessibilityContainer:container feature:feature]) { - NSDictionary *attributes = feature.attributes; - NSMutableArray *facts = [NSMutableArray array]; + if (self = [super initWithAccessibilityContainer:container feature:feature]) { + NSDictionary *attributes = feature.attributes; + NSMutableArray *facts = [NSMutableArray array]; - // Announce the route number. - if (attributes[@"ref"]) { - // TODO: Decorate the route number with the network name based on the shield attribute. - NSString *ref = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ROAD_REF_A11Y_FMT", nil, nil, @"Route %@", @"String format for accessibility value for road feature; {route number}"), attributes[@"ref"]]; - [facts addObject:ref]; - } - - // Announce whether the road is a one-way road. - if ([attributes[@"oneway"] isEqualToString:@"true"]) { - [facts addObject:NSLocalizedStringWithDefaultValue(@"ROAD_ONEWAY_A11Y_VALUE", nil, nil, @"One way", @"Accessibility value indicating that a road is a one-way road")]; - } - - // Announce whether the road is a divided road. - MLNPolyline *polyline; - if ([feature isKindOfClass:[MLNMultiPolylineFeature class]]) { - [facts addObject:NSLocalizedStringWithDefaultValue(@"ROAD_DIVIDED_A11Y_VALUE", nil, nil, @"Divided road", @"Accessibility value indicating that a road is a divided road (dual carriageway)")]; - polyline = [(MLNMultiPolylineFeature *)feature polylines].firstObject; - } - - // Announce the road’s general direction. - if ([feature isKindOfClass:[MLNPolylineFeature class]]) { - polyline = (MLNPolylineFeature *)feature; - } - if (polyline) { - NSUInteger pointCount = polyline.pointCount; - if (pointCount) { - CLLocationCoordinate2D *coordinates = polyline.coordinates; - CLLocationDirection startDirection = MLNDirectionBetweenCoordinates(coordinates[pointCount - 1], coordinates[0]); - CLLocationDirection endDirection = MLNDirectionBetweenCoordinates(coordinates[0], coordinates[pointCount - 1]); - - MLNCompassDirectionFormatter *formatter = [[MLNCompassDirectionFormatter alloc] init]; - formatter.unitStyle = NSFormattingUnitStyleLong; - - NSString *startDirectionString = [formatter stringFromDirection:startDirection]; - NSString *endDirectionString = [formatter stringFromDirection:endDirection]; - NSString *directionString = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"ROAD_DIRECTION_A11Y_FMT", nil, nil, @"%@ to %@", @"String format for accessibility value for road feature; {starting compass direction}, {ending compass direction}"), startDirectionString, endDirectionString]; - [facts addObject:directionString]; - } - } - - if (facts.count) { - NSString *separator = NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); - self.accessibilityValue = [facts componentsJoinedByString:separator]; - } + // Announce the route number. + if (attributes[@"ref"]) { + // TODO: Decorate the route number with the network name based on the shield attribute. + NSString *ref = [NSString + stringWithFormat: + NSLocalizedStringWithDefaultValue( + @"ROAD_REF_A11Y_FMT", nil, nil, @"Route %@", + @"String format for accessibility value for road feature; {route number}"), + attributes[@"ref"]]; + [facts addObject:ref]; } - return self; + + // Announce whether the road is a one-way road. + if ([attributes[@"oneway"] isEqualToString:@"true"]) { + [facts addObject:NSLocalizedStringWithDefaultValue( + @"ROAD_ONEWAY_A11Y_VALUE", nil, nil, @"One way", + @"Accessibility value indicating that a road is a one-way road")]; + } + + // Announce whether the road is a divided road. + MLNPolyline *polyline; + if ([feature isKindOfClass:[MLNMultiPolylineFeature class]]) { + [facts addObject:NSLocalizedStringWithDefaultValue( + @"ROAD_DIVIDED_A11Y_VALUE", nil, nil, @"Divided road", + @"Accessibility value indicating that a road is a divided road (dual " + @"carriageway)")]; + polyline = [(MLNMultiPolylineFeature *)feature polylines].firstObject; + } + + // Announce the road’s general direction. + if ([feature isKindOfClass:[MLNPolylineFeature class]]) { + polyline = (MLNPolylineFeature *)feature; + } + if (polyline) { + NSUInteger pointCount = polyline.pointCount; + if (pointCount) { + CLLocationCoordinate2D *coordinates = polyline.coordinates; + CLLocationDirection startDirection = + MLNDirectionBetweenCoordinates(coordinates[pointCount - 1], coordinates[0]); + CLLocationDirection endDirection = + MLNDirectionBetweenCoordinates(coordinates[0], coordinates[pointCount - 1]); + + MLNCompassDirectionFormatter *formatter = [[MLNCompassDirectionFormatter alloc] init]; + formatter.unitStyle = NSFormattingUnitStyleLong; + + NSString *startDirectionString = [formatter stringFromDirection:startDirection]; + NSString *endDirectionString = [formatter stringFromDirection:endDirection]; + NSString *directionString = [NSString + stringWithFormat:NSLocalizedStringWithDefaultValue( + @"ROAD_DIRECTION_A11Y_FMT", nil, nil, @"%@ to %@", + @"String format for accessibility value for road feature; " + @"{starting compass direction}, {ending compass direction}"), + startDirectionString, endDirectionString]; + [facts addObject:directionString]; + } + } + + if (facts.count) { + NSString *separator = + NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator"); + self.accessibilityValue = [facts componentsJoinedByString:separator]; + } + } + return self; } @end @@ -184,12 +205,15 @@ @implementation MLNMapViewProxyAccessibilityElement - (instancetype)initWithAccessibilityContainer:(id)container { - if (self = [super initWithAccessibilityContainer:container]) { - self.accessibilityTraits = UIAccessibilityTraitButton; - self.accessibilityLabel = [self.accessibilityContainer accessibilityLabel]; - self.accessibilityHint = NSLocalizedStringWithDefaultValue(@"CLOSE_CALLOUT_A11Y_HINT", nil, nil, @"Returns to the map", @"Accessibility hint for closing the selected annotation’s callout view and returning to the map"); - } - return self; + if (self = [super initWithAccessibilityContainer:container]) { + self.accessibilityTraits = UIAccessibilityTraitButton; + self.accessibilityLabel = [self.accessibilityContainer accessibilityLabel]; + self.accessibilityHint = NSLocalizedStringWithDefaultValue( + @"CLOSE_CALLOUT_A11Y_HINT", nil, nil, @"Returns to the map", + @"Accessibility hint for closing the selected annotation’s callout view and returning to " + @"the map"); + } + return self; } @end diff --git a/platform/ios/src/MLNMapProjection.mm b/platform/ios/src/MLNMapProjection.mm index bbb6cfb75..135c5188b 100644 --- a/platform/ios/src/MLNMapProjection.mm +++ b/platform/ios/src/MLNMapProjection.mm @@ -3,9 +3,9 @@ #include #include +#import "MLNGeometry_Private.h" #import "MLNMapProjection.h" #import "MLNMapView_Private.h" -#import "MLNGeometry_Private.h" @interface MLNMapProjection () @@ -13,102 +13,91 @@ @end -@implementation MLNMapProjection -{ - std::unique_ptr _mbglProjection; +@implementation MLNMapProjection { + std::unique_ptr _mbglProjection; } -- (instancetype)initWithMapView:(MLNMapView *)mapView -{ - if (self = [super init]) - { - _mbglProjection = std::make_unique([mapView mbglMap]); - self.mapFrameSize = mapView.frame.size; - } - return self; +- (instancetype)initWithMapView:(MLNMapView *)mapView { + if (self = [super init]) { + _mbglProjection = std::make_unique([mapView mbglMap]); + self.mapFrameSize = mapView.frame.size; + } + return self; } -- (MLNMapCamera*)camera -{ - mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); +- (MLNMapCamera *)camera { + mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); - CLLocationCoordinate2D centerCoordinate = MLNLocationCoordinate2DFromLatLng(*cameraOptions.center); - double zoomLevel = *cameraOptions.zoom; - CLLocationDirection direction = mbgl::util::wrap(*cameraOptions.bearing, 0., 360.); - CGFloat pitch = *cameraOptions.pitch; - CLLocationDistance altitude = MLNAltitudeForZoomLevel(zoomLevel, pitch, - centerCoordinate.latitude, self.mapFrameSize); - return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate altitude:altitude - pitch:pitch heading:direction]; + CLLocationCoordinate2D centerCoordinate = + MLNLocationCoordinate2DFromLatLng(*cameraOptions.center); + double zoomLevel = *cameraOptions.zoom; + CLLocationDirection direction = mbgl::util::wrap(*cameraOptions.bearing, 0., 360.); + CGFloat pitch = *cameraOptions.pitch; + CLLocationDistance altitude = + MLNAltitudeForZoomLevel(zoomLevel, pitch, centerCoordinate.latitude, self.mapFrameSize); + return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + altitude:altitude + pitch:pitch + heading:direction]; } -- (void)setCamera:(MLNMapCamera * _Nonnull)camera withEdgeInsets:(UIEdgeInsets)insets -{ - mbgl::CameraOptions cameraOptions; - if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) - { - cameraOptions.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); - } - cameraOptions.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - cameraOptions.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, - camera.centerCoordinate.latitude, - self.mapFrameSize); - if (camera.heading >= 0) - { - cameraOptions.bearing = camera.heading; - } - if (camera.pitch >= 0) - { - cameraOptions.pitch = camera.pitch; - } - cameraOptions.roll = camera.roll; +- (void)setCamera:(MLNMapCamera *_Nonnull)camera withEdgeInsets:(UIEdgeInsets)insets { + mbgl::CameraOptions cameraOptions; + if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) { + cameraOptions.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); + } + cameraOptions.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + cameraOptions.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, + camera.centerCoordinate.latitude, self.mapFrameSize); + if (camera.heading >= 0) { + cameraOptions.bearing = camera.heading; + } + if (camera.pitch >= 0) { + cameraOptions.pitch = camera.pitch; + } + cameraOptions.roll = camera.roll; - _mbglProjection->setCamera(cameraOptions); + _mbglProjection->setCamera(cameraOptions); } - (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets { - CLLocationCoordinate2D coordinates[] = { - {bounds.ne.latitude, bounds.sw.longitude}, - bounds.sw, - {bounds.sw.latitude, bounds.ne.longitude}, - bounds.ne, - }; + CLLocationCoordinate2D coordinates[] = { + {bounds.ne.latitude, bounds.sw.longitude}, + bounds.sw, + {bounds.sw.latitude, bounds.ne.longitude}, + bounds.ne, + }; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - std::vector latLngs; - latLngs.reserve(4); - for (NSUInteger i = 0; i < 4; i++) - { - latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude}); - } + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + std::vector latLngs; + latLngs.reserve(4); + for (NSUInteger i = 0; i < 4; i++) { + latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude}); + } - _mbglProjection->setVisibleCoordinates(latLngs, padding); + _mbglProjection->setVisibleCoordinates(latLngs, padding); } -- (CLLocationCoordinate2D)convertPoint:(CGPoint)point -{ - mbgl::ScreenCoordinate screenCoordinate = mbgl::ScreenCoordinate(point.x, point.y); - return MLNLocationCoordinate2DFromLatLng(_mbglProjection->latLngForPixel(screenCoordinate).wrapped()); +- (CLLocationCoordinate2D)convertPoint:(CGPoint)point { + mbgl::ScreenCoordinate screenCoordinate = mbgl::ScreenCoordinate(point.x, point.y); + return MLNLocationCoordinate2DFromLatLng( + _mbglProjection->latLngForPixel(screenCoordinate).wrapped()); } -- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate -{ - if ( !CLLocationCoordinate2DIsValid(coordinate)) - { - return CGPointMake(NAN, NAN); - } +- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate { + if (!CLLocationCoordinate2DIsValid(coordinate)) { + return CGPointMake(NAN, NAN); + } - mbgl::LatLng latLng = MLNLatLngFromLocationCoordinate2D(coordinate); - mbgl::ScreenCoordinate pixel = _mbglProjection->pixelForLatLng(latLng); - return CGPointMake(pixel.x, pixel.y); + mbgl::LatLng latLng = MLNLatLngFromLocationCoordinate2D(coordinate); + mbgl::ScreenCoordinate pixel = _mbglProjection->pixelForLatLng(latLng); + return CGPointMake(pixel.x, pixel.y); } -- (CLLocationDistance)metersPerPoint -{ - mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); - return mbgl::Projection::getMetersPerPixelAtLatitude(cameraOptions.center->latitude(), - *cameraOptions.zoom); +- (CLLocationDistance)metersPerPoint { + mbgl::CameraOptions cameraOptions = _mbglProjection->getCamera(); + return mbgl::Projection::getMetersPerPixelAtLatitude(cameraOptions.center->latitude(), + *cameraOptions.zoom); } - @end diff --git a/platform/ios/src/MLNMapView+Impl.h b/platform/ios/src/MLNMapView+Impl.h index 39dc58e34..5367ede6f 100644 --- a/platform/ios/src/MLNMapView+Impl.h +++ b/platform/ios/src/MLNMapView+Impl.h @@ -11,7 +11,7 @@ @class MLNMapView; class MLNMapViewImpl : public mbgl::MapObserver { - public: +public: static std::unique_ptr Create(MLNMapView*); MLNMapViewImpl(MLNMapView*); @@ -88,7 +88,7 @@ class MLNMapViewImpl : public mbgl::MapObserver { void onSpriteError(const std::optional&, std::exception_ptr) override; void onSpriteRequested(const std::optional&) override; - protected: +protected: /// Cocoa map view that this adapter bridges to. __weak MLNMapView* mapView = nullptr; }; diff --git a/platform/ios/src/MLNMapView+Impl.mm b/platform/ios/src/MLNMapView+Impl.mm index 37ecfd67e..75d1382a2 100644 --- a/platform/ios/src/MLNMapView+Impl.mm +++ b/platform/ios/src/MLNMapView+Impl.mm @@ -5,200 +5,211 @@ #if MLN_RENDER_BACKEND_METAL #import "MLNMapView+Metal.h" -#else // MLN_RENDER_BACKEND_OPENGL +#else // MLN_RENDER_BACKEND_OPENGL #import "MLNMapView+OpenGL.h" #endif std::unique_ptr MLNMapViewImpl::Create(MLNMapView* nativeView) { #if MLN_RENDER_BACKEND_METAL - return std::make_unique(nativeView); -#else // MLN_RENDER_BACKEND_OPENGL - return std::make_unique(nativeView); + return std::make_unique(nativeView); +#else // MLN_RENDER_BACKEND_OPENGL + return std::make_unique(nativeView); #endif } -MLNMapViewImpl::MLNMapViewImpl(MLNMapView* nativeView_) : mapView(nativeView_) { -} +MLNMapViewImpl::MLNMapViewImpl(MLNMapView* nativeView_) : mapView(nativeView_) {} -void MLNMapViewImpl::render() { - [mapView renderSync]; -} +void MLNMapViewImpl::render() { [mapView renderSync]; } void MLNMapViewImpl::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraWillChangeAnimated:animated]; + bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; + [mapView cameraWillChangeAnimated:animated]; } -void MLNMapViewImpl::onCameraIsChanging() { - [mapView cameraIsChanging]; -} +void MLNMapViewImpl::onCameraIsChanging() { [mapView cameraIsChanging]; } void MLNMapViewImpl::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraDidChangeAnimated:animated]; + bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; + [mapView cameraDidChangeAnimated:animated]; } -void MLNMapViewImpl::onWillStartLoadingMap() { - [mapView mapViewWillStartLoadingMap]; -} +void MLNMapViewImpl::onWillStartLoadingMap() { [mapView mapViewWillStartLoadingMap]; } -void MLNMapViewImpl::onDidFinishLoadingMap() { - [mapView mapViewDidFinishLoadingMap]; -} +void MLNMapViewImpl::onDidFinishLoadingMap() { [mapView mapViewDidFinishLoadingMap]; } void MLNMapViewImpl::onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) { - NSString *description; - MLNErrorCode code; - switch (mapError) { - case mbgl::MapLoadError::StyleParseError: - code = MLNErrorCodeParseStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"PARSE_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style is corrupted.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::StyleLoadError: - code = MLNErrorCodeLoadStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"LOAD_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style can’t be loaded.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::NotFoundError: - code = MLNErrorCodeNotFound; - description = NSLocalizedStringWithDefaultValue(@"STYLE_NOT_FOUND_DESC", nil, nil, @"The map failed to load because the style can’t be found or is incompatible.", @"User-friendly error description"); - break; - default: - code = MLNErrorCodeUnknown; - description = NSLocalizedStringWithDefaultValue(@"LOAD_MAP_FAILED_DESC", nil, nil, @"The map failed to load because an unknown error occurred.", @"User-friendly error description"); - } - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: description, - NSLocalizedFailureReasonErrorKey: @(what.c_str()), - }; - NSError *error = [NSError errorWithDomain:MLNErrorDomain code:code userInfo:userInfo]; + NSString* description; + MLNErrorCode code; + switch (mapError) { + case mbgl::MapLoadError::StyleParseError: + code = MLNErrorCodeParseStyleFailed; + description = NSLocalizedStringWithDefaultValue( + @"PARSE_STYLE_FAILED_DESC", nil, nil, + @"The map failed to load because the style is corrupted.", + @"User-friendly error description"); + break; + case mbgl::MapLoadError::StyleLoadError: + code = MLNErrorCodeLoadStyleFailed; + description = NSLocalizedStringWithDefaultValue( + @"LOAD_STYLE_FAILED_DESC", nil, nil, + @"The map failed to load because the style can’t be loaded.", + @"User-friendly error description"); + break; + case mbgl::MapLoadError::NotFoundError: + code = MLNErrorCodeNotFound; + description = NSLocalizedStringWithDefaultValue( + @"STYLE_NOT_FOUND_DESC", nil, nil, + @"The map failed to load because the style can’t be found or is incompatible.", + @"User-friendly error description"); + break; + default: + code = MLNErrorCodeUnknown; + description = NSLocalizedStringWithDefaultValue( + @"LOAD_MAP_FAILED_DESC", nil, nil, + @"The map failed to load because an unknown error occurred.", + @"User-friendly error description"); + } + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : description, + NSLocalizedFailureReasonErrorKey : @(what.c_str()), + }; + NSError* error = [NSError errorWithDomain:MLNErrorDomain code:code userInfo:userInfo]; - [mapView mapViewDidFailLoadingMapWithError:error]; + [mapView mapViewDidFailLoadingMapWithError:error]; } -void MLNMapViewImpl::onWillStartRenderingFrame() { - [mapView mapViewWillStartRenderingFrame]; -} +void MLNMapViewImpl::onWillStartRenderingFrame() { [mapView mapViewWillStartRenderingFrame]; } void MLNMapViewImpl::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) { - const bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered renderingStats:status.renderingStats]; + const bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; + [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered + renderingStats:status.renderingStats]; } -void MLNMapViewImpl::onWillStartRenderingMap() { - [mapView mapViewWillStartRenderingMap]; -} +void MLNMapViewImpl::onWillStartRenderingMap() { [mapView mapViewWillStartRenderingMap]; } void MLNMapViewImpl::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) { - bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; + bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; + [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; } -void MLNMapViewImpl::onDidFinishLoadingStyle() { - [mapView mapViewDidFinishLoadingStyle]; -} +void MLNMapViewImpl::onDidFinishLoadingStyle() { [mapView mapViewDidFinishLoadingStyle]; } void MLNMapViewImpl::onSourceChanged(mbgl::style::Source& source) { - NSString *identifier = @(source.getID().c_str()); - MLNSource * nativeSource = [mapView.style sourceWithIdentifier:identifier]; - [mapView sourceDidChange:nativeSource]; + NSString* identifier = @(source.getID().c_str()); + MLNSource* nativeSource = [mapView.style sourceWithIdentifier:identifier]; + [mapView sourceDidChange:nativeSource]; } -void MLNMapViewImpl::onDidBecomeIdle() { - [mapView mapViewDidBecomeIdle]; -} +void MLNMapViewImpl::onDidBecomeIdle() { [mapView mapViewDidBecomeIdle]; } void MLNMapViewImpl::onStyleImageMissing(const std::string& imageIdentifier) { - NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; - [mapView didFailToLoadImage:imageName]; + NSString* imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; + [mapView didFailToLoadImage:imageName]; } -bool MLNMapViewImpl::onCanRemoveUnusedStyleImage(const std::string &imageIdentifier) { - NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; - return [mapView shouldRemoveStyleImage:imageName]; +bool MLNMapViewImpl::onCanRemoveUnusedStyleImage(const std::string& imageIdentifier) { + NSString* imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; + return [mapView shouldRemoveStyleImage:imageName]; } -void MLNMapViewImpl::onRegisterShaders(mbgl::gfx::ShaderRegistry& shaders) { +void MLNMapViewImpl::onRegisterShaders(mbgl::gfx::ShaderRegistry& shaders) {} +void MLNMapViewImpl::onPreCompileShader(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderWillCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onPreCompileShader(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderWillCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onPostCompileShader(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderDidCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onPostCompileShader(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderDidCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onShaderCompileFailed(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderDidFailCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onShaderCompileFailed(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderDidFailCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onGlyphsLoaded(const mbgl::FontStack& fontStack, + const mbgl::GlyphRange& range) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); + + [mapView glyphsDidLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsLoaded(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); +void MLNMapViewImpl::onGlyphsError(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range, + std::exception_ptr error) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); - [mapView glyphsDidLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; + [mapView glyphsDidError:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsError(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range, std::exception_ptr error) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); +void MLNMapViewImpl::onGlyphsRequested(const mbgl::FontStack& fontStack, + const mbgl::GlyphRange& range) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); - [mapView glyphsDidError:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; + [mapView glyphsWillLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsRequested(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); - - [mapView glyphsWillLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; -} - -void MLNMapViewImpl::onTileAction(mbgl::TileOperation operation, const mbgl::OverscaledTileID& tile, const std::string& sourceID) { - [mapView tileDidTriggerAction:MLNTileOperation(static_cast(operation)) - x:tile.canonical.x - y:tile.canonical.y - z:tile.canonical.z - wrap:tile.wrap - overscaledZ:tile.overscaledZ - sourceID:[NSString stringWithUTF8String:sourceID.c_str()]]; +void MLNMapViewImpl::onTileAction(mbgl::TileOperation operation, const mbgl::OverscaledTileID& tile, + const std::string& sourceID) { + [mapView tileDidTriggerAction:MLNTileOperation(static_cast(operation)) + x:tile.canonical.x + y:tile.canonical.y + z:tile.canonical.z + wrap:tile.wrap + overscaledZ:tile.overscaledZ + sourceID:[NSString stringWithUTF8String:sourceID.c_str()]]; } void MLNMapViewImpl::onSpriteLoaded(const std::optional& spriteID) { - if (!spriteID.has_value()) { - [mapView spriteDidLoad:nil url:nil]; - return; - } + if (!spriteID.has_value()) { + [mapView spriteDidLoad:nil url:nil]; + return; + } - [mapView spriteDidLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteDidLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } -void MLNMapViewImpl::onSpriteError(const std::optional& spriteID, std::exception_ptr error) { - if (!spriteID.has_value()) { - [mapView spriteDidError:nil url:nil]; - return; - } +void MLNMapViewImpl::onSpriteError(const std::optional& spriteID, + std::exception_ptr error) { + if (!spriteID.has_value()) { + [mapView spriteDidError:nil url:nil]; + return; + } - [mapView spriteDidError:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteDidError:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } void MLNMapViewImpl::onSpriteRequested(const std::optional& spriteID) { - if (!spriteID.has_value()) { - [mapView spriteWillLoad:nil url:nil]; - return; - } + if (!spriteID.has_value()) { + [mapView spriteWillLoad:nil url:nil]; + return; + } - [mapView spriteWillLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteWillLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } diff --git a/platform/ios/src/MLNMapView+Metal.h b/platform/ios/src/MLNMapView+Metal.h index 39cdc98a2..e602af621 100644 --- a/platform/ios/src/MLNMapView+Metal.h +++ b/platform/ios/src/MLNMapView+Metal.h @@ -10,29 +10,29 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl, public mbgl::mtl::RendererBackend, public mbgl::gfx::Renderable { - public: +public: MLNMapViewMetalImpl(MLNMapView*); ~MLNMapViewMetalImpl() override; - public: +public: void restoreFramebufferBinding(); // Implementation of mbgl::gfx::RendererBackend - public: +public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } - private: +private: void activate() override; void deactivate() override; // End implementation of mbgl::gfx::RendererBackend // Implementation of mbgl::gl::RendererBackend - public: +public: void updateAssumedState() override; // End implementation of mbgl::gl::Rendererbackend // Implementation of MLNMapViewImpl - public: +public: mbgl::gfx::RendererBackend& getRendererBackend() override { return *this; } void setOpaque(bool) override; diff --git a/platform/ios/src/MLNMapView+Metal.mm b/platform/ios/src/MLNMapView+Metal.mm index 67b4a4313..1136eef5e 100644 --- a/platform/ios/src/MLNMapView+Metal.mm +++ b/platform/ios/src/MLNMapView+Metal.mm @@ -5,8 +5,8 @@ #import -#import #import +#import #import #import @@ -15,225 +15,225 @@ @end @implementation MLNMapViewImplDelegate { - MLNMapViewMetalImpl* _impl; + MLNMapViewMetalImpl* _impl; } - (instancetype)initWithImpl:(MLNMapViewMetalImpl*)impl { - if (self = [super init]) { - _impl = impl; - } - return self; + if (self = [super init]) { + _impl = impl; + } + return self; } -- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {} +- (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size { +} -- (void)drawInMTKView:(MTKView *)view { - _impl->render(); +- (void)drawInMTKView:(MTKView*)view { + _impl->render(); } @end class MLNMapViewMetalRenderableResource final : public mbgl::mtl::RenderableResource { public: - MLNMapViewMetalRenderableResource(MLNMapViewMetalImpl& backend_) - : backend(backend_), - delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]) { + MLNMapViewMetalRenderableResource(MLNMapViewMetalImpl& backend_) + : backend(backend_), delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]) {} + + void bind() override { + if (!commandQueue) { + commandQueue = [mtlView.device newCommandQueue]; } - void bind() override { - if (!commandQueue) { - commandQueue = [mtlView.device newCommandQueue]; - } + if (!commandBuffer) { + commandBuffer = [commandQueue commandBuffer]; + commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); + } + } - if (!commandBuffer) { - commandBuffer = [commandQueue commandBuffer]; - commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); - } + const mbgl::mtl::RendererBackend& getBackend() const override { return backend; } + + const mbgl::mtl::MTLCommandBufferPtr& getCommandBuffer() const override { + return commandBufferPtr; + } + + virtual mbgl::mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { + // Create from render pass descriptor? + return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); + } + + const mbgl::mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { + if (!cachedRenderPassDescriptor) { + auto* mtlDesc = mtlView.currentRenderPassDescriptor; + cachedRenderPassDescriptor = NS::RetainPtr((__bridge MTL::RenderPassDescriptor*)mtlDesc); + } + return cachedRenderPassDescriptor; + } + + void swap() override { + id currentDrawable = [mtlView currentDrawable]; + if (currentDrawable) { + if (presentsWithTransaction) { + [commandBuffer commit]; + [commandBuffer waitUntilCompleted]; + [currentDrawable present]; + } else { + [commandBuffer presentDrawable:currentDrawable]; + [commandBuffer commit]; + } } - const mbgl::mtl::RendererBackend& getBackend() const override { return backend; } + commandBuffer = nil; + commandBufferPtr.reset(); - const mbgl::mtl::MTLCommandBufferPtr& getCommandBuffer() const override { - return commandBufferPtr; - } + cachedRenderPassDescriptor.reset(); + } - virtual mbgl::mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { - // Create from render pass descriptor? - return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); - } - - const mbgl::mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { - if (!cachedRenderPassDescriptor) { - auto* mtlDesc = mtlView.currentRenderPassDescriptor; - cachedRenderPassDescriptor = NS::RetainPtr((__bridge MTL::RenderPassDescriptor*)mtlDesc); - } - return cachedRenderPassDescriptor; - } - - void swap() override { - id currentDrawable = [mtlView currentDrawable]; - if (currentDrawable) { - if (presentsWithTransaction) { - [commandBuffer commit]; - [commandBuffer waitUntilCompleted]; - [currentDrawable present]; - } else { - [commandBuffer presentDrawable:currentDrawable]; - [commandBuffer commit]; - } - } - - commandBuffer = nil; - commandBufferPtr.reset(); - - cachedRenderPassDescriptor.reset(); - } - - mbgl::Size framebufferSize() { - assert(mtlView); - return { static_cast(mtlView.drawableSize.width), - static_cast(mtlView.drawableSize.height) }; - } + mbgl::Size framebufferSize() { + assert(mtlView); + return {static_cast(mtlView.drawableSize.width), + static_cast(mtlView.drawableSize.height)}; + } private: - MLNMapViewMetalImpl& backend; - mbgl::mtl::MTLCommandBufferPtr commandBufferPtr; - mutable mbgl::mtl::MTLRenderPassDescriptorPtr cachedRenderPassDescriptor; + MLNMapViewMetalImpl& backend; + mbgl::mtl::MTLCommandBufferPtr commandBufferPtr; + mutable mbgl::mtl::MTLRenderPassDescriptorPtr cachedRenderPassDescriptor; public: - MLNMapViewImplDelegate* delegate = nil; - MTKView *mtlView = nil; - id commandBuffer; - id commandQueue; - bool presentsWithTransaction = false; + MLNMapViewImplDelegate* delegate = nil; + MTKView* mtlView = nil; + id commandBuffer; + id commandQueue; + bool presentsWithTransaction = false; - // We count how often the context was activated/deactivated so that we can truly deactivate it - // after the activation count drops to 0. - NSUInteger activationCount = 0; + // We count how often the context was activated/deactivated so that we can truly deactivate it + // after the activation count drops to 0. + NSUInteger activationCount = 0; }; MLNMapViewMetalImpl::MLNMapViewMetalImpl(MLNMapView* nativeView_) : MLNMapViewImpl(nativeView_), mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique), - mbgl::gfx::Renderable({ 0, 0 }, std::make_unique(*this)) { -} + mbgl::gfx::Renderable({0, 0}, std::make_unique(*this)) {} MLNMapViewMetalImpl::~MLNMapViewMetalImpl() = default; void MLNMapViewMetalImpl::setOpaque(const bool opaque) { - auto& resource = getResource(); - resource.mtlView.opaque = opaque; - resource.mtlView.layer.opaque = opaque; + auto& resource = getResource(); + resource.mtlView.opaque = opaque; + resource.mtlView.layer.opaque = opaque; } void MLNMapViewMetalImpl::setPresentsWithTransaction(const bool value) { - auto& resource = getResource(); - resource.presentsWithTransaction = value; + auto& resource = getResource(); + resource.presentsWithTransaction = value; - if (@available(iOS 13.0, *)) { - if (CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer)) { - metalLayer.presentsWithTransaction = value; - } + if (@available(iOS 13.0, *)) { + if (CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer)) { + metalLayer.presentsWithTransaction = value; } + } } void MLNMapViewMetalImpl::display() { - auto& resource = getResource(); + auto& resource = getResource(); - // Calling `display` here directly causes the stuttering bug (if - // `presentsWithTransaction` is `YES` - see above) - // as reported in https://github.com/mapbox/mapbox-gl-native-ios/issues/350 - // - // Since we use `presentsWithTransaction` to synchronize with UIView - // annotations, we now let the system handle when the view is rendered. This - // has the potential to increase latency - [resource.mtlView setNeedsDisplay]; + // Calling `display` here directly causes the stuttering bug (if + // `presentsWithTransaction` is `YES` - see above) + // as reported in https://github.com/mapbox/mapbox-gl-native-ios/issues/350 + // + // Since we use `presentsWithTransaction` to synchronize with UIView + // annotations, we now let the system handle when the view is rendered. This + // has the potential to increase latency + [resource.mtlView setNeedsDisplay]; } void MLNMapViewMetalImpl::createView() { - auto& resource = getResource(); - if (resource.mtlView) { - return; - } + auto& resource = getResource(); + if (resource.mtlView) { + return; + } - id device = (__bridge id)resource.getBackend().getDevice().get(); - const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView); + id device = (__bridge id)resource.getBackend().getDevice().get(); + const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView); - resource.mtlView = [[MTKView alloc] initWithFrame:mapView.bounds device:device]; - resource.mtlView.delegate = resource.delegate; - resource.mtlView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - resource.mtlView.contentScaleFactor = scaleFactor; - resource.mtlView.contentMode = UIViewContentModeCenter; - resource.mtlView.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - resource.mtlView.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - resource.mtlView.opaque = mapView.opaque; - resource.mtlView.layer.opaque = mapView.opaque; - resource.mtlView.enableSetNeedsDisplay = YES; - if (@available(iOS 13.0, *)) { - CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer); - metalLayer.presentsWithTransaction = resource.presentsWithTransaction; - } + resource.mtlView = [[MTKView alloc] initWithFrame:mapView.bounds device:device]; + resource.mtlView.delegate = resource.delegate; + resource.mtlView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + resource.mtlView.contentScaleFactor = scaleFactor; + resource.mtlView.contentMode = UIViewContentModeCenter; + resource.mtlView.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + resource.mtlView.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + resource.mtlView.opaque = mapView.opaque; + resource.mtlView.layer.opaque = mapView.opaque; + resource.mtlView.enableSetNeedsDisplay = YES; + if (@available(iOS 13.0, *)) { + CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer); + metalLayer.presentsWithTransaction = resource.presentsWithTransaction; + } - [mapView insertSubview:resource.mtlView atIndex:0]; + [mapView insertSubview:resource.mtlView atIndex:0]; } UIView* MLNMapViewMetalImpl::getView() { - auto& resource = getResource(); - return resource.mtlView; + auto& resource = getResource(); + return resource.mtlView; } void MLNMapViewMetalImpl::deleteView() { - auto& resource = getResource(); - [resource.mtlView releaseDrawables]; + auto& resource = getResource(); + [resource.mtlView releaseDrawables]; } void MLNMapViewMetalImpl::activate() { - auto& resource = getResource(); - if (resource.activationCount++) { - return; - } + auto& resource = getResource(); + if (resource.activationCount++) { + return; + } } void MLNMapViewMetalImpl::deactivate() { - auto& resource = getResource(); - if (--resource.activationCount) { - return; - } + auto& resource = getResource(); + if (--resource.activationCount) { + return; + } } /// This function is called before we start rendering, when iOS invokes our rendering method. /// iOS already sets the correct framebuffer and viewport for us, so we need to update the /// context state with the anticipated values. void MLNMapViewMetalImpl::updateAssumedState() { - auto& resource = getResource(); - assumeFramebufferBinding(ImplicitFramebufferBinding); - assumeViewport(0, 0, resource.framebufferSize()); + auto& resource = getResource(); + assumeFramebufferBinding(ImplicitFramebufferBinding); + assumeViewport(0, 0, resource.framebufferSize()); } UIImage* MLNMapViewMetalImpl::snapshot() { - auto& resource = getResource(); - return nil; //TODO: resource.mtlView.snapshot; + auto& resource = getResource(); + return nil; // TODO: resource.mtlView.snapshot; } void MLNMapViewMetalImpl::layoutChanged() { - const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView); - const auto screenSize = mapView.bounds.size; + const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView); + const auto screenSize = mapView.bounds.size; - auto& resource = getResource(); + auto& resource = getResource(); - resource.mtlView.contentScaleFactor = scaleFactor; - resource.mtlView.drawableSize = CGSizeMake(screenSize.width * scaleFactor, screenSize.height * scaleFactor); + resource.mtlView.contentScaleFactor = scaleFactor; + resource.mtlView.drawableSize = + CGSizeMake(screenSize.width * scaleFactor, screenSize.height * scaleFactor); - size = { static_cast(resource.mtlView.drawableSize.width), - static_cast(resource.mtlView.drawableSize.height) }; + size = {static_cast(resource.mtlView.drawableSize.width), + static_cast(resource.mtlView.drawableSize.height)}; } MLNBackendResource* MLNMapViewMetalImpl::getObject() { - auto& resource = getResource(); - auto renderPassDescriptor = resource.getRenderPassDescriptor().get(); + auto& resource = getResource(); + auto renderPassDescriptor = resource.getRenderPassDescriptor().get(); - return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView - device:resource.mtlView.device - renderPassDescriptor:[MTLRenderPassDescriptor renderPassDescriptor] - commandBuffer:resource.commandBuffer]; + return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView + device:resource.mtlView.device + renderPassDescriptor:[MTLRenderPassDescriptor renderPassDescriptor] + commandBuffer:resource.commandBuffer]; } diff --git a/platform/ios/src/MLNMapView+OpenGL.h b/platform/ios/src/MLNMapView+OpenGL.h index 263ab3461..6841a22f3 100644 --- a/platform/ios/src/MLNMapView+OpenGL.h +++ b/platform/ios/src/MLNMapView+OpenGL.h @@ -10,37 +10,37 @@ class MLNMapViewOpenGLImpl final : public MLNMapViewImpl, public mbgl::gl::RendererBackend, public mbgl::gfx::Renderable { - public: +public: MLNMapViewOpenGLImpl(MLNMapView*); ~MLNMapViewOpenGLImpl() override; - public: +public: void restoreFramebufferBinding(); #ifdef MLN_RECREATE_GL_IN_AN_EMERGENCY - private: +private: void emergencyRecreateGL(); #endif // Implementation of mbgl::gfx::RendererBackend - public: +public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } - private: +private: void activate() override; void deactivate() override; // End implementation of mbgl::gfx::RendererBackend // Implementation of mbgl::gl::RendererBackend - public: +public: void updateAssumedState() override; - private: +private: mbgl::gl::ProcAddress getExtensionFunctionPointer(const char* name) override; // End implementation of mbgl::gl::Rendererbackend // Implementation of MLNMapViewImpl - public: +public: mbgl::gfx::RendererBackend& getRendererBackend() override { return *this; } EAGLContext* getEAGLContext() override; diff --git a/platform/ios/src/MLNMapView+OpenGL.mm b/platform/ios/src/MLNMapView+OpenGL.mm index ab3c6625a..c7d2516e2 100644 --- a/platform/ios/src/MLNMapView+OpenGL.mm +++ b/platform/ios/src/MLNMapView+OpenGL.mm @@ -12,247 +12,242 @@ @end @implementation MLNMapViewImplDelegate { - MLNMapViewOpenGLImpl* _impl; + MLNMapViewOpenGLImpl* _impl; } - (instancetype)initWithImpl:(MLNMapViewOpenGLImpl*)impl { - if (self = [super init]) { - _impl = impl; - } - return self; + if (self = [super init]) { + _impl = impl; + } + return self; } - (void)glkView:(nonnull GLKView*)view drawInRect:(CGRect)rect { - _impl->render(); + _impl->render(); } @end namespace { CGFloat contentScaleFactor() { - return [UIScreen instancesRespondToSelector:@selector(nativeScale)] - ? [[UIScreen mainScreen] nativeScale] - : [[UIScreen mainScreen] scale]; + return [UIScreen instancesRespondToSelector:@selector(nativeScale)] + ? [[UIScreen mainScreen] nativeScale] + : [[UIScreen mainScreen] scale]; } -} // namespace +} // namespace class MLNMapViewOpenGLRenderableResource final : public mbgl::gl::RenderableResource { public: - MLNMapViewOpenGLRenderableResource(MLNMapViewOpenGLImpl& backend_) - : backend(backend_), - delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]), - atLeastiOS_12_2_0([NSProcessInfo.processInfo - isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 12, 2, 0 }]) { - } + MLNMapViewOpenGLRenderableResource(MLNMapViewOpenGLImpl& backend_) + : backend(backend_), + delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]), + atLeastiOS_12_2_0([NSProcessInfo.processInfo + isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){12, 2, 0}]) {} - void bind() override { - backend.restoreFramebufferBinding(); - } + void bind() override { backend.restoreFramebufferBinding(); } - mbgl::Size framebufferSize() { - assert(glView); - return { static_cast(glView.drawableWidth), - static_cast(glView.drawableHeight) }; - } + mbgl::Size framebufferSize() { + assert(glView); + return {static_cast(glView.drawableWidth), + static_cast(glView.drawableHeight)}; + } private: - MLNMapViewOpenGLImpl& backend; + MLNMapViewOpenGLImpl& backend; public: - MLNMapViewImplDelegate* delegate = nil; - GLKView *glView = nil; - EAGLContext *context = nil; - const bool atLeastiOS_12_2_0; + MLNMapViewImplDelegate* delegate = nil; + GLKView* glView = nil; + EAGLContext* context = nil; + const bool atLeastiOS_12_2_0; - // We count how often the context was activated/deactivated so that we can truly deactivate it - // after the activation count drops to 0. - NSUInteger activationCount = 0; + // We count how often the context was activated/deactivated so that we can truly deactivate it + // after the activation count drops to 0. + NSUInteger activationCount = 0; }; MLNMapViewOpenGLImpl::MLNMapViewOpenGLImpl(MLNMapView* nativeView_) : MLNMapViewImpl(nativeView_), mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique), - mbgl::gfx::Renderable({ 0, 0 }, std::make_unique(*this)) { -} + mbgl::gfx::Renderable({0, 0}, std::make_unique(*this)) {} MLNMapViewOpenGLImpl::~MLNMapViewOpenGLImpl() { - auto& resource = getResource(); - if (resource.context && [[EAGLContext currentContext] isEqual:resource.context]) { - [EAGLContext setCurrentContext:nil]; - } + auto& resource = getResource(); + if (resource.context && [[EAGLContext currentContext] isEqual:resource.context]) { + [EAGLContext setCurrentContext:nil]; + } } void MLNMapViewOpenGLImpl::setOpaque(const bool opaque) { - auto& resource = getResource(); - resource.glView.opaque = opaque; - resource.glView.layer.opaque = opaque; + auto& resource = getResource(); + resource.glView.opaque = opaque; + resource.glView.layer.opaque = opaque; } void MLNMapViewOpenGLImpl::setPresentsWithTransaction(const bool value) { - auto& resource = getResource(); - CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = value; + auto& resource = getResource(); + CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); + eaglLayer.presentsWithTransaction = value; } void MLNMapViewOpenGLImpl::display() { - auto& resource = getResource(); + auto& resource = getResource(); - // Calling `display` here directly causes the stuttering bug (if - // `presentsWithTransaction` is `YES` - see above) - // as reported in https://github.com/mapbox/mapbox-gl-native-ios/issues/350 - // - // Since we use `presentsWithTransaction` to synchronize with UIView - // annotations, we now let the system handle when the view is rendered. This - // has the potential to increase latency - [resource.glView setNeedsDisplay]; + // Calling `display` here directly causes the stuttering bug (if + // `presentsWithTransaction` is `YES` - see above) + // as reported in https://github.com/mapbox/mapbox-gl-native-ios/issues/350 + // + // Since we use `presentsWithTransaction` to synchronize with UIView + // annotations, we now let the system handle when the view is rendered. This + // has the potential to increase latency + [resource.glView setNeedsDisplay]; } void MLNMapViewOpenGLImpl::createView() { - auto& resource = getResource(); - if (resource.glView) { - return; - } + auto& resource = getResource(); + if (resource.glView) { + return; + } - if (!resource.context) { - resource.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; - assert(resource.context); - } + if (!resource.context) { + resource.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; + assert(resource.context); + } - resource.glView = [[GLKView alloc] initWithFrame:mapView.bounds context:resource.context]; - resource.glView.delegate = resource.delegate; - resource.glView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - resource.glView.contentScaleFactor = contentScaleFactor(); - resource.glView.contentMode = UIViewContentModeCenter; - resource.glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; - resource.glView.drawableDepthFormat = GLKViewDrawableDepthFormat16; - resource.glView.opaque = mapView.opaque; - resource.glView.layer.opaque = mapView.opaque; - resource.glView.enableSetNeedsDisplay = YES; - CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = NO; + resource.glView = [[GLKView alloc] initWithFrame:mapView.bounds context:resource.context]; + resource.glView.delegate = resource.delegate; + resource.glView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + resource.glView.contentScaleFactor = contentScaleFactor(); + resource.glView.contentMode = UIViewContentModeCenter; + resource.glView.drawableStencilFormat = GLKViewDrawableStencilFormat8; + resource.glView.drawableDepthFormat = GLKViewDrawableDepthFormat16; + resource.glView.opaque = mapView.opaque; + resource.glView.layer.opaque = mapView.opaque; + resource.glView.enableSetNeedsDisplay = YES; + CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); + eaglLayer.presentsWithTransaction = NO; - [mapView insertSubview:resource.glView atIndex:0]; + [mapView insertSubview:resource.glView atIndex:0]; } UIView* MLNMapViewOpenGLImpl::getView() { - auto& resource = getResource(); - return resource.glView; + auto& resource = getResource(); + return resource.glView; } void MLNMapViewOpenGLImpl::deleteView() { - auto& resource = getResource(); - [resource.glView deleteDrawable]; + auto& resource = getResource(); + [resource.glView deleteDrawable]; } #ifdef MLN_RECREATE_GL_IN_AN_EMERGENCY // TODO: Fix or remove // See https://github.com/mapbox/mapbox-gl-native/issues/14232 void MLNMapViewOpenGLImpl::emergencyRecreateGL() { - auto& resource = getResource(); - MLNLogError(@"Rendering took too long - creating GL views"); + auto& resource = getResource(); + MLNLogError(@"Rendering took too long - creating GL views"); - CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = NO; + CAEAGLLayer* eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); + eaglLayer.presentsWithTransaction = NO; - [mapView pauseRendering:nil]; + [mapView pauseRendering:nil]; - // Just performing a pauseRendering:/resumeRendering: pair isn't sufficient - in this case - // we can still get errors when calling bindDrawable. Here we completely - // recreate the GLKView + // Just performing a pauseRendering:/resumeRendering: pair isn't sufficient - in this case + // we can still get errors when calling bindDrawable. Here we completely + // recreate the GLKView - [mapView.userLocationAnnotationView removeFromSuperview]; - [resource.glView removeFromSuperview]; + [mapView.userLocationAnnotationView removeFromSuperview]; + [resource.glView removeFromSuperview]; - // Recreate the view - resource.glView = nil; - createView(); + // Recreate the view + resource.glView = nil; + createView(); - if (mapView.annotationContainerView) { - [resource.glView insertSubview:mapView.annotationContainerView atIndex:0]; - } + if (mapView.annotationContainerView) { + [resource.glView insertSubview:mapView.annotationContainerView atIndex:0]; + } - [mapView updateUserLocationAnnotationView]; + [mapView updateUserLocationAnnotationView]; - // Do not bind...yet + // Do not bind...yet - if (mapView.window) { - [mapView resumeRendering:nil]; - eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); - eaglLayer.presentsWithTransaction = mapView.enablePresentsWithTransaction; - } else { - MLNLogDebug(@"No window - skipping resumeRendering"); - } + if (mapView.window) { + [mapView resumeRendering:nil]; + eaglLayer = MLN_OBJC_DYNAMIC_CAST(resource.glView.layer, CAEAGLLayer); + eaglLayer.presentsWithTransaction = mapView.enablePresentsWithTransaction; + } else { + MLNLogDebug(@"No window - skipping resumeRendering"); + } } #endif mbgl::gl::ProcAddress MLNMapViewOpenGLImpl::getExtensionFunctionPointer(const char* name) { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } + static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles")); + if (!framework) { + throw std::runtime_error("Failed to load OpenGL framework."); + } - return reinterpret_cast(CFBundleGetFunctionPointerForName( - framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); + return reinterpret_cast(CFBundleGetFunctionPointerForName( + framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); } void MLNMapViewOpenGLImpl::activate() { - auto& resource = getResource(); - if (resource.activationCount++) { - return; - } + auto& resource = getResource(); + if (resource.activationCount++) { + return; + } - [EAGLContext setCurrentContext:resource.context]; + [EAGLContext setCurrentContext:resource.context]; } void MLNMapViewOpenGLImpl::deactivate() { - auto& resource = getResource(); - if (--resource.activationCount) { - return; - } + auto& resource = getResource(); + if (--resource.activationCount) { + return; + } - [EAGLContext setCurrentContext:nil]; + [EAGLContext setCurrentContext:nil]; } /// This function is called before we start rendering, when iOS invokes our rendering method. /// iOS already sets the correct framebuffer and viewport for us, so we need to update the /// context state with the anticipated values. void MLNMapViewOpenGLImpl::updateAssumedState() { - auto& resource = getResource(); - assumeFramebufferBinding(ImplicitFramebufferBinding); - assumeViewport(0, 0, resource.framebufferSize()); + auto& resource = getResource(); + assumeFramebufferBinding(ImplicitFramebufferBinding); + assumeViewport(0, 0, resource.framebufferSize()); } void MLNMapViewOpenGLImpl::restoreFramebufferBinding() { - auto& resource = getResource(); - if (!implicitFramebufferBound()) { - // Something modified our state, and we need to bind the original drawable again. - // Doing this also sets the viewport to the full framebuffer. - // Note that in reality, iOS does not use the Framebuffer 0 (it's typically 1), and we - // only use this is a placeholder value. - [resource.glView bindDrawable]; - updateAssumedState(); - } else { - // Our framebuffer is still bound, but the viewport might have changed. - setViewport(0, 0, resource.framebufferSize()); - } + auto& resource = getResource(); + if (!implicitFramebufferBound()) { + // Something modified our state, and we need to bind the original drawable again. + // Doing this also sets the viewport to the full framebuffer. + // Note that in reality, iOS does not use the Framebuffer 0 (it's typically 1), and we + // only use this is a placeholder value. + [resource.glView bindDrawable]; + updateAssumedState(); + } else { + // Our framebuffer is still bound, but the viewport might have changed. + setViewport(0, 0, resource.framebufferSize()); + } } UIImage* MLNMapViewOpenGLImpl::snapshot() { - auto& resource = getResource(); - return resource.glView.snapshot; + auto& resource = getResource(); + return resource.glView.snapshot; } void MLNMapViewOpenGLImpl::layoutChanged() { - const auto scaleFactor = contentScaleFactor(); - size = { static_cast(mapView.bounds.size.width * scaleFactor), - static_cast(mapView.bounds.size.height * scaleFactor) }; + const auto scaleFactor = contentScaleFactor(); + size = {static_cast(mapView.bounds.size.width * scaleFactor), + static_cast(mapView.bounds.size.height * scaleFactor)}; } EAGLContext* MLNMapViewOpenGLImpl::getEAGLContext() { - auto& resource = getResource(); - return resource.context; + auto& resource = getResource(); + return resource.context; } -MLNBackendResource* MLNMapViewOpenGLImpl::getObject() { - return [[MLNBackendResource alloc] init]; -} +MLNBackendResource* MLNMapViewOpenGLImpl::getObject() { return [[MLNBackendResource alloc] init]; } diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 176f91f82..b47c9feb7 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -1,49 +1,48 @@ -#import "MLNMapView_Private.h" #import "MLNMapView+Impl.h" +#import "MLNMapView_Private.h" +#include +#include +#include #include #include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#import "Mapbox.h" -#import "MLNShape_Private.h" #import "MLNFeature_Private.h" +#import "MLNFoundation_Private.h" #import "MLNGeometry_Private.h" #import "MLNMultiPoint_Private.h" #import "MLNOfflineStorage_Private.h" -#import "MLNVectorTileSource_Private.h" -#import "MLNFoundation_Private.h" -#import "MLNRendererFrontend.h" #import "MLNRendererConfiguration.h" +#import "MLNRendererFrontend.h" +#import "MLNShape_Private.h" +#import "MLNVectorTileSource_Private.h" +#import "Mapbox.h" #import "NSBundle+MLNAdditions.h" #import "NSDate+MLNAdditions.h" @@ -53,35 +52,35 @@ #import "NSURL+MLNAdditions.h" #import "UIDevice+MLNAdditions.h" #import "UIImage+MLNAdditions.h" -#import "UIViewController+MLNAdditions.h" #import "UIView+MLNAdditions.h" +#import "UIViewController+MLNAdditions.h" +#import "MLNActionJournalOptions_Private.h" +#import "MLNAnnotationContainerView.h" +#import "MLNAnnotationContainerView_Private.h" +#import "MLNAnnotationImage_Private.h" +#import "MLNAnnotationView_Private.h" +#import "MLNAttributionInfo_Private.h" +#import "MLNCompactCalloutView.h" +#import "MLNCompassButton_Private.h" #import "MLNFaux3DUserLocationAnnotationView.h" +#import "MLNLocationManager_Private.h" +#import "MLNLoggingConfiguration_Private.h" +#import "MLNMapAccessibilityElement.h" +#import "MLNMapProjection.h" +#import "MLNNetworkConfiguration_Private.h" +#import "MLNPluginLayer.h" +#include "MLNPluginStyleLayer_Private.h" +#import "MLNReachability.h" +#import "MLNRenderingStats_Private.h" +#import "MLNScaleBar.h" +#import "MLNSettings_Private.h" +#import "MLNStyleLayerManager.h" +#import "MLNStyleLayer_Private.h" +#import "MLNStyle_Private.h" #import "MLNUserLocationAnnotationView.h" #import "MLNUserLocationAnnotationView_Private.h" #import "MLNUserLocation_Private.h" -#import "MLNAnnotationImage_Private.h" -#import "MLNAnnotationView_Private.h" -#import "MLNCompassButton_Private.h" -#import "MLNScaleBar.h" -#import "MLNStyle_Private.h" -#import "MLNStyleLayer_Private.h" -#import "MLNCompactCalloutView.h" -#import "MLNAnnotationContainerView.h" -#import "MLNAnnotationContainerView_Private.h" -#import "MLNAttributionInfo_Private.h" -#import "MLNMapAccessibilityElement.h" -#import "MLNLocationManager_Private.h" -#import "MLNLoggingConfiguration_Private.h" -#import "MLNNetworkConfiguration_Private.h" -#import "MLNReachability.h" -#import "MLNRenderingStats_Private.h" -#import "MLNSettings_Private.h" -#import "MLNActionJournalOptions_Private.h" -#import "MLNMapProjection.h" -#import "MLNPluginLayer.h" -#import "MLNStyleLayerManager.h" -#include "MLNPluginStyleLayer_Private.h" #include #include @@ -104,148 +103,144 @@ @end @implementation MBGLPanGesture { - bool customVelocity; - NSTimeInterval _lastTime; - CGPoint _lastTouch; - CGPoint __velocity; + bool customVelocity; + NSTimeInterval _lastTime; + CGPoint _lastTouch; + CGPoint __velocity; } - (CGPoint)velocityInView:(nullable UIView *)view { - if (customVelocity) { - return __velocity; - } + if (customVelocity) { + return __velocity; + } - return [super velocityInView:view]; + return [super velocityInView:view]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - _lastTime = NSProcessInfo.processInfo.systemUptime; + _lastTime = NSProcessInfo.processInfo.systemUptime; - UITouch *touch = [touches anyObject]; - _lastTouch = [touch locationInView:self.view]; - customVelocity = true; + UITouch *touch = [touches anyObject]; + _lastTouch = [touch locationInView:self.view]; + customVelocity = true; - [super touchesBegan:touches withEvent:event]; + [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - if (customVelocity && touches.count) { - CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; - UITouch *touch = [touches anyObject]; - CGPoint touchLoc = [touch locationInView:self.view]; - auto delta = now - _lastTime; - if (delta > 0) { - __velocity.x = ((touchLoc.x - _lastTouch.x) / delta) * 0.9 + __velocity.x * 0.1; - __velocity.y = ((touchLoc.y - _lastTouch.y) / delta) * 0.9 + __velocity.y * 0.1; - } - _lastTouch = touchLoc; - _lastTime = now; + if (customVelocity && touches.count) { + CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; + UITouch *touch = [touches anyObject]; + CGPoint touchLoc = [touch locationInView:self.view]; + auto delta = now - _lastTime; + if (delta > 0) { + __velocity.x = ((touchLoc.x - _lastTouch.x) / delta) * 0.9 + __velocity.x * 0.1; + __velocity.y = ((touchLoc.y - _lastTouch.y) / delta) * 0.9 + __velocity.y * 0.1; } - [super touchesMoved:touches withEvent:event]; - + _lastTouch = touchLoc; + _lastTime = now; + } + [super touchesMoved:touches withEvent:event]; } @end @implementation MBGLPinchGesture { - bool customVelocity; - NSTimeInterval _lastTime; - CGFloat _lastScale; - CGFloat __velocity; + bool customVelocity; + NSTimeInterval _lastTime; + CGFloat _lastScale; + CGFloat __velocity; } - (CGFloat)velocity { - if (customVelocity) { - return __velocity; - } + if (customVelocity) { + return __velocity; + } - return [super velocity]; + return [super velocity]; } - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - _lastTime = NSProcessInfo.processInfo.systemUptime; + _lastTime = NSProcessInfo.processInfo.systemUptime; - _lastScale = 0; - customVelocity = true; + _lastScale = 0; + customVelocity = true; - [super touchesBegan:touches withEvent:event]; + [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [super touchesMoved:touches withEvent:event]; + [super touchesMoved:touches withEvent:event]; - if (customVelocity && touches.count) { - CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; - CGFloat scale = [super scale]; - auto delta = now - _lastTime; - if (delta > 0) { - __velocity = ((scale - _lastScale) / delta) * 0.9 + __velocity * 0.1; - } - _lastScale = scale; - _lastTime = now; + if (customVelocity && touches.count) { + CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; + CGFloat scale = [super scale]; + auto delta = now - _lastTime; + if (delta > 0) { + __velocity = ((scale - _lastScale) / delta) * 0.9 + __velocity * 0.1; } + _lastScale = scale; + _lastTime = now; + } } @end @implementation MBGLRotationGesture { - bool customVelocity; - NSTimeInterval _lastTime; - CGFloat _lastRotation; - CGFloat __velocity; + bool customVelocity; + NSTimeInterval _lastTime; + CGFloat _lastRotation; + CGFloat __velocity; } - (CGFloat)velocity { - if (customVelocity) { - return __velocity; - } + if (customVelocity) { + return __velocity; + } - return [super velocity]; + return [super velocity]; } - - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { - _lastTime = NSProcessInfo.processInfo.systemUptime; + _lastTime = NSProcessInfo.processInfo.systemUptime; - _lastRotation = 0; - customVelocity = true; + _lastRotation = 0; + customVelocity = true; - [super touchesBegan:touches withEvent:event]; + [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { - [super touchesMoved:touches withEvent:event]; + [super touchesMoved:touches withEvent:event]; - if (customVelocity && touches.count) { - CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; - CGFloat rotation = [super rotation]; - auto delta = now - _lastTime; - if (delta > 0) { - __velocity = ((rotation - _lastRotation) / delta) * 0.9 + __velocity * 0.1; - } - _lastRotation = rotation; - _lastTime = now; + if (customVelocity && touches.count) { + CFTimeInterval now = NSProcessInfo.processInfo.systemUptime; + CGFloat rotation = [super rotation]; + auto delta = now - _lastTime; + if (delta > 0) { + __velocity = ((rotation - _lastRotation) / delta) * 0.9 + __velocity * 0.1; } + _lastRotation = rotation; + _lastTime = now; + } } @end int processIsTranslated() { - int ret = 0; - size_t size = sizeof(ret); + int ret = 0; + size_t size = sizeof(ret); - // Call the sysctl and if successful return the result - if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) != -1) - return ret; - // If "sysctl.proc_translated" is not present then must be native - if (errno == ENOENT) - return 0; - return -1; + // Call the sysctl and if successful return the result + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) != -1) return ret; + // If "sysctl.proc_translated" is not present then must be native + if (errno == ENOENT) return 0; + return -1; } #endif class MLNAnnotationContext; -const MLNMapViewDecelerationRate MLNMapViewDecelerationRateNormal = UIScrollViewDecelerationRateNormal; +const MLNMapViewDecelerationRate MLNMapViewDecelerationRateNormal = + UIScrollViewDecelerationRateNormal; const MLNMapViewDecelerationRate MLNMapViewDecelerationRateFast = UIScrollViewDecelerationRateFast; const MLNMapViewDecelerationRate MLNMapViewDecelerationRateImmediate = 0.0; @@ -253,22 +248,25 @@ const MLNMapViewPreferredFramesPerSecond MLNMapViewPreferredFramesPerSecondDefau const MLNMapViewPreferredFramesPerSecond MLNMapViewPreferredFramesPerSecondLowPower = 30; const MLNMapViewPreferredFramesPerSecond MLNMapViewPreferredFramesPerSecondMaximum = 0; -const MLNExceptionName MLNMissingLocationServicesUsageDescriptionException = @"MLNMissingLocationServicesUsageDescriptionException"; -const MLNExceptionName MLNUserLocationAnnotationTypeException = @"MLNUserLocationAnnotationTypeException"; -const MLNExceptionName MLNUnderlyingMapUnavailableException = @"MLNUnderlyingMapUnavailableException"; +const MLNExceptionName MLNMissingLocationServicesUsageDescriptionException = + @"MLNMissingLocationServicesUsageDescriptionException"; +const MLNExceptionName MLNUserLocationAnnotationTypeException = + @"MLNUserLocationAnnotationTypeException"; +const MLNExceptionName MLNUnderlyingMapUnavailableException = + @"MLNUnderlyingMapUnavailableException"; const CGPoint MLNOrnamentDefaultPositionOffset = CGPointMake(8, 8); /// Indicates the manner in which the map view is tracking the user location. typedef NS_ENUM(NSUInteger, MLNUserTrackingState) { - /// The map view is not yet tracking the user location. - MLNUserTrackingStatePossible = 0, - /// The map view has begun to move to the first reported user location. - MLNUserTrackingStateBegan, - /// The map view begins a significant transition. - MLNUserTrackingStateBeginSignificantTransition, - /// The map view has finished moving to the first reported user location. - MLNUserTrackingStateChanged, + /// The map view is not yet tracking the user location. + MLNUserTrackingStatePossible = 0, + /// The map view has begun to move to the first reported user location. + MLNUserTrackingStateBegan, + /// The map view begins a significant transition. + MLNUserTrackingStateBeginSignificantTransition, + /// The map view has finished moving to the first reported user location. + MLNUserTrackingStateChanged, }; const NSTimeInterval MLNAnimationDuration = 0.3; @@ -294,18 +292,18 @@ const double MLNDefaultZoomLevelForUserTracking = 14.0; const CLLocationDistance MLNDistanceThresholdForCameraPause = 500; /// Rotation threshold while a pinch gesture is occurring. -static NSString * const MLNRotationThresholdWhileZoomingKey = @"MLNRotationThresholdWhileZooming"; +static NSString *const MLNRotationThresholdWhileZoomingKey = @"MLNRotationThresholdWhileZooming"; /// Reuse identifier and file name of the default point annotation image. -static NSString * const MLNDefaultStyleMarkerSymbolName = @"default_marker"; +static NSString *const MLNDefaultStyleMarkerSymbolName = @"default_marker"; /// Reuse identifier and file name of the invisible point annotation image used /// by annotations that are visually backed by MLNAnnotationView objects -static NSString * const MLNInvisibleStyleMarkerSymbolName = @"invisible_marker"; +static NSString *const MLNInvisibleStyleMarkerSymbolName = @"invisible_marker"; /// Prefix that denotes a sprite installed by MLNMapView, to avoid collisions /// with style-defined sprites. -NSString * const MLNAnnotationSpritePrefix = @"org.maplibre.sprites."; +NSString *const MLNAnnotationSpritePrefix = @"org.maplibre.sprites."; /// Slop area around the hit testing point, allowing for imprecise annotation selection. const CGFloat MLNAnnotationImagePaddingForHitTest = 5; @@ -332,27 +330,25 @@ typedef std::unordered_map MLNAnnotation /// Mapping from an annotation object to an annotation tag. typedef std::map, MLNAnnotationTag> MLNAnnotationObjectTagMap; -mbgl::util::UnitBezier MLNUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) -{ - if ( ! function) - { - function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - } - float p1[2], p2[2]; - [function getControlPointAtIndex:0 values:p1]; - [function getControlPointAtIndex:1 values:p2]; - return { p1[0], p1[1], p2[0], p2[1] }; +mbgl::util::UnitBezier MLNUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { + if (!function) { + function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } + float p1[2], p2[2]; + [function getControlPointAtIndex:0 values:p1]; + [function getControlPointAtIndex:1 values:p2]; + return {p1[0], p1[1], p2[0], p2[1]}; } /// Lightweight container for metadata about an annotation, including the annotation itself. class MLNAnnotationContext { public: - id annotation; - /// The annotation’s image’s reuse identifier. - NSString *imageReuseIdentifier; - MLNAnnotationAccessibilityElement *accessibilityElement; - MLNAnnotationView *annotationView; - NSString *viewReuseIdentifier; + id annotation; + /// The annotation’s image’s reuse identifier. + NSString *imageReuseIdentifier; + MLNAnnotationAccessibilityElement *accessibilityElement; + MLNAnnotationView *annotationView; + NSString *viewReuseIdentifier; }; // MARK: - Private - @@ -391,7 +387,8 @@ public: @property (nonatomic) MLNCameraChangeReason cameraChangeReasonBitmask; /// Mapping from reusable identifiers to annotation images. -@property (nonatomic) NSMutableDictionary *annotationImagesByIdentifier; +@property (nonatomic) + NSMutableDictionary *annotationImagesByIdentifier; /// Currently shown popover representing the selected annotation. @property (nonatomic) UIView *calloutViewForSelectedAnnotation; @@ -419,7 +416,8 @@ public: @property (nonatomic) MLNMapViewProxyAccessibilityElement *mapViewProxyAccessibilityElement; @property (nonatomic) MLNAnnotationContainerView *annotationContainerView; @property (nonatomic) MLNUserLocation *userLocation; -@property (nonatomic) NSMutableDictionary *> *annotationViewReuseQueueByIdentifier; +@property (nonatomic) NSMutableDictionary *> + *annotationViewReuseQueueByIdentifier; @property (nonatomic, readonly) BOOL enablePresentsWithTransaction; @property (nonatomic) NSMutableArray *pendingCompletionBlocks; @@ -454,671 +452,697 @@ public: @end -@implementation MLNMapView -{ - std::unique_ptr _mbglMap; - std::unique_ptr _mbglView; - std::unique_ptr _rendererFrontend; +@implementation MLNMapView { + std::unique_ptr _mbglMap; + std::unique_ptr _mbglView; + std::unique_ptr _rendererFrontend; - BOOL _opaque; + BOOL _opaque; - MLNAnnotationTagContextMap _annotationContextsByAnnotationTag; - MLNAnnotationObjectTagMap _annotationTagsByAnnotation; + MLNAnnotationTagContextMap _annotationContextsByAnnotationTag; + MLNAnnotationObjectTagMap _annotationTagsByAnnotation; - /// Tag of the selected annotation. If the user location annotation is selected, this ivar is set to ``MLNAnnotationTagNotFound``. - MLNAnnotationTag _selectedAnnotationTag; + /// Tag of the selected annotation. If the user location annotation is selected, this ivar is set + /// to ``MLNAnnotationTagNotFound``. + MLNAnnotationTag _selectedAnnotationTag; - BOOL _userLocationAnnotationIsSelected; - /// Size of the rectangle formed by unioning the maximum slop area around every annotation image and annotation image view. - CGSize _unionedAnnotationRepresentationSize; - CGSize _largestAnnotationViewSize; - std::vector _annotationsNearbyLastTap; - CGPoint _initialImplicitCalloutViewOffset; - NSDate *_userLocationAnimationCompletionDate; - /// True if a willChange notification has been issued for shape annotation layers and a didChange notification is pending. - BOOL _isChangingAnnotationLayers; - BOOL _isWaitingForRedundantReachableNotification; + BOOL _userLocationAnnotationIsSelected; + /// Size of the rectangle formed by unioning the maximum slop area around every annotation image + /// and annotation image view. + CGSize _unionedAnnotationRepresentationSize; + CGSize _largestAnnotationViewSize; + std::vector _annotationsNearbyLastTap; + CGPoint _initialImplicitCalloutViewOffset; + NSDate *_userLocationAnimationCompletionDate; + /// True if a willChange notification has been issued for shape annotation layers and a didChange + /// notification is pending. + BOOL _isChangingAnnotationLayers; + BOOL _isWaitingForRedundantReachableNotification; - CLLocationDegrees _pendingLatitude; - CLLocationDegrees _pendingLongitude; + CLLocationDegrees _pendingLatitude; + CLLocationDegrees _pendingLongitude; - NSInteger _changeDelimiterSuppressionDepth; + NSInteger _changeDelimiterSuppressionDepth; - /// Center of the pinch gesture on the previous iteration of the gesture. - CGPoint _previousPinchCenterPoint; - NSUInteger _previousPinchNumberOfTouches; + /// Center of the pinch gesture on the previous iteration of the gesture. + CGPoint _previousPinchCenterPoint; + NSUInteger _previousPinchNumberOfTouches; - CLLocationDistance _distanceFromOldUserLocation; + CLLocationDistance _distanceFromOldUserLocation; - BOOL _delegateHasAlphasForShapeAnnotations; - BOOL _delegateHasStrokeColorsForShapeAnnotations; - BOOL _delegateHasFillColorsForShapeAnnotations; - BOOL _delegateHasLineWidthsForShapeAnnotations; + BOOL _delegateHasAlphasForShapeAnnotations; + BOOL _delegateHasStrokeColorsForShapeAnnotations; + BOOL _delegateHasFillColorsForShapeAnnotations; + BOOL _delegateHasLineWidthsForShapeAnnotations; - NSArray> *_visiblePlaceFeatures; - NSArray> *_visibleRoadFeatures; - NSMutableSet *_featureAccessibilityElements; - BOOL _accessibilityValueAnnouncementIsPending; + NSArray> *_visiblePlaceFeatures; + NSArray> *_visibleRoadFeatures; + NSMutableSet *_featureAccessibilityElements; + BOOL _accessibilityValueAnnouncementIsPending; - MLNReachability *_reachability; + MLNReachability *_reachability; - /// Experimental rendering performance measurement. - CFTimeInterval _frameCounterStartTime; - NSInteger _frameCount; - CFTimeInterval _frameDurations; + /// Experimental rendering performance measurement. + CFTimeInterval _frameCounterStartTime; + NSInteger _frameCount; + CFTimeInterval _frameDurations; - MLNRenderingStats* _renderingStats; + MLNRenderingStats *_renderingStats; } // MARK: - Setup & Teardown - -- (instancetype)initWithFrame:(CGRect)frame -{ - if (self = [super initWithFrame:frame]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@", NSStringFromCGRect(frame)); - [self commonInitWithOptions:nil]; - self.styleURL = nil; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@", NSStringFromCGRect(frame)); + [self commonInitWithOptions:nil]; + self.styleURL = nil; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } -- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL -{ - if (self = [super initWithFrame:frame]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromCGRect(frame), styleURL); - [self commonInitWithOptions:nil]; - self.styleURL = styleURL; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; +- (instancetype)initWithFrame:(CGRect)frame styleURL:(nullable NSURL *)styleURL { + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromCGRect(frame), styleURL); + [self commonInitWithOptions:nil]; + self.styleURL = styleURL; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } -- (instancetype)initWithFrame:(CGRect)frame styleJSON:(NSString *)styleJSON -{ - if (self = [super initWithFrame:frame]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@ styleJSON: %@", NSStringFromCGRect(frame), styleJSON); - [self commonInitWithOptions:nil]; - self.styleJSON = styleJSON; - _initialStyleJSON = [styleJSON copy]; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; +- (instancetype)initWithFrame:(CGRect)frame styleJSON:(NSString *)styleJSON { + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@ styleJSON: %@", NSStringFromCGRect(frame), styleJSON); + [self commonInitWithOptions:nil]; + self.styleJSON = styleJSON; + _initialStyleJSON = [styleJSON copy]; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } -- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options -{ - if (self = [super initWithFrame:frame]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromCGRect(frame)); - [self commonInitWithOptions:options]; +- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options { + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromCGRect(frame)); + [self commonInitWithOptions:options]; - if (options) - { - if (options.pluginLayers) { - for (Class c in options.pluginLayers) { - [self addPluginLayerType:c]; - } - } - - if (options.styleURL) { - self.styleURL = options.styleURL; - } else if (options.styleJSON) { - self.styleJSON = options.styleJSON; - _initialStyleJSON = [options.styleJSON copy]; - } else { - self.styleURL = nil; - } - } else { - self.styleURL = nil; + if (options) { + if (options.pluginLayers) { + for (Class c in options.pluginLayers) { + [self addPluginLayerType:c]; } + } - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; -} - -- (instancetype)initWithCoder:(nonnull NSCoder *)decoder -{ - if (self = [super initWithCoder:decoder]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - [self commonInitWithOptions:nil]; + if (options.styleURL) { + self.styleURL = options.styleURL; + } else if (options.styleJSON) { + self.styleJSON = options.styleJSON; + _initialStyleJSON = [options.styleJSON copy]; + } else { self.styleURL = nil; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; -} - -+ (NSSet *)keyPathsForValuesAffectingStyle -{ - return [NSSet setWithObject:@"styleURL"]; -} - -+ (NSSet *)keyPathsForValuesAffectingStyleURL -{ - return [NSSet setWithObjects:@"styleURL__", nil]; -} - -- (nonnull NSURL *)styleURL -{ - if (!_mbglMap) - { - NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); - return self.residualStyleURL; + } + } else { + self.styleURL = nil; } - if (self.mbglMap.getStyle().getJSON().length() > 0 && - self.mbglMap.getStyle().getURL().empty()) { - return [NSURL URLWithString:@"local://style.json"]; - } - - NSString *styleURLString = @(self.mbglMap.getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty; - MLNAssert(styleURLString, @"Invalid style URL string %@", styleURLString); - return styleURLString ? [NSURL URLWithString:styleURLString] : nil; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } -- (void)setStyleURL:(nullable NSURL *)styleURL -{ - if ( ! styleURL) - { - styleURL = [MLNStyle defaultStyleURL]; - } - MLNLogDebug(@"Setting styleURL: %@", styleURL); - styleURL = styleURL.mgl_URLByStandardizingScheme; - self.style = nil; - self.mbglMap.getStyle().loadURL([[styleURL absoluteString] UTF8String]); +- (instancetype)initWithCoder:(nonnull NSCoder *)decoder { + if (self = [super initWithCoder:decoder]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + [self commonInitWithOptions:nil]; + self.styleURL = nil; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; +} + ++ (NSSet *)keyPathsForValuesAffectingStyle { + return [NSSet setWithObject:@"styleURL"]; +} + ++ (NSSet *)keyPathsForValuesAffectingStyleURL { + return [NSSet setWithObjects:@"styleURL__", nil]; +} + +- (nonnull NSURL *)styleURL { + if (!_mbglMap) { + NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); + return self.residualStyleURL; + } + + if (self.mbglMap.getStyle().getJSON().length() > 0 && self.mbglMap.getStyle().getURL().empty()) { + return [NSURL URLWithString:@"local://style.json"]; + } + + NSString *styleURLString = @(self.mbglMap.getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty; + MLNAssert(styleURLString, @"Invalid style URL string %@", styleURLString); + return styleURLString ? [NSURL URLWithString:styleURLString] : nil; +} + +- (void)setStyleURL:(nullable NSURL *)styleURL { + if (!styleURL) { + styleURL = [MLNStyle defaultStyleURL]; + } + MLNLogDebug(@"Setting styleURL: %@", styleURL); + styleURL = styleURL.mgl_URLByStandardizingScheme; + self.style = nil; + self.mbglMap.getStyle().loadURL([[styleURL absoluteString] UTF8String]); } - (NSString *)styleJSON { - return self.style.styleJSON; + return self.style.styleJSON; } - (void)setStyleJSON:(NSString *)styleJSON { - // Reset style and load new JSON - self.style = nil; - self.mbglMap.getStyle().loadJSON([styleJSON UTF8String]); + // Reset style and load new JSON + self.style = nil; + self.mbglMap.getStyle().loadJSON([styleJSON UTF8String]); } - (IBAction)reloadStyle:(__unused id)sender { - MLNLogInfo(@"Reloading style."); - NSURL *styleURL = self.styleURL; - self.mbglMap.getStyle().loadURL(""); - self.styleURL = styleURL; + MLNLogInfo(@"Reloading style."); + NSURL *styleURL = self.styleURL; + self.mbglMap.getStyle().loadURL(""); + self.styleURL = styleURL; } -- (mbgl::Map &)mbglMap -{ - if (!_mbglMap) - { - [NSException raise:MLNUnderlyingMapUnavailableException - format:@"The underlying map is not available - this happens during app termination"]; - } - return *_mbglMap; +- (mbgl::Map &)mbglMap { + if (!_mbglMap) { + [NSException + raise:MLNUnderlyingMapUnavailableException + format:@"The underlying map is not available - this happens during app termination"]; + } + return *_mbglMap; } -- (mbgl::Renderer *)renderer -{ - return _rendererFrontend->getRenderer(); +- (mbgl::Renderer *)renderer { + return _rendererFrontend->getRenderer(); } -- (void)commonInitWithOptions:(MLNMapOptions*)mlnMapoptions -{ - if (mlnMapoptions == nil) - { - mlnMapoptions = [[MLNMapOptions alloc] init]; - } +- (void)commonInitWithOptions:(MLNMapOptions *)mlnMapoptions { + if (mlnMapoptions == nil) { + mlnMapoptions = [[MLNMapOptions alloc] init]; + } - _opaque = NO; + _opaque = NO; - // setup accessibility -// self.isAccessibilityElement = YES; + // setup accessibility + // self.isAccessibilityElement = YES; - // Ensure network configuration is set up (connect gl-native networking to - // platform SDK via delegation). Calling `resetNativeNetworkManagerDelegate` - // is not necessary here, since the shared manager already calls it. - [MLNNetworkConfiguration sharedManager]; + // Ensure network configuration is set up (connect gl-native networking to + // platform SDK via delegation). Calling `resetNativeNetworkManagerDelegate` + // is not necessary here, since the shared manager already calls it. + [MLNNetworkConfiguration sharedManager]; - self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_LABEL", nil, nil, @"Map", @"Accessibility label"); - self.accessibilityTraits = UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitAdjustable; - self.backgroundColor = [UIColor clearColor]; - self.clipsToBounds = YES; - if (@available(iOS 11.0, *)) { self.accessibilityIgnoresInvertColors = YES; } + self.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_LABEL", nil, nil, @"Map", + @"Accessibility label"); + self.accessibilityTraits = + UIAccessibilityTraitAllowsDirectInteraction | UIAccessibilityTraitAdjustable; + self.backgroundColor = [UIColor clearColor]; + self.clipsToBounds = YES; + if (@available(iOS 11.0, *)) { + self.accessibilityIgnoresInvertColors = YES; + } - self.preferredFramesPerSecond = MLNMapViewPreferredFramesPerSecondDefault; + self.preferredFramesPerSecond = MLNMapViewPreferredFramesPerSecondDefault; - // setup mbgl view - _mbglView = MLNMapViewImpl::Create(self); + // setup mbgl view + _mbglView = MLNMapViewImpl::Create(self); - BOOL background = [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; - if (!background) - { - _mbglView->createView(); - } - // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db. - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"]; - [[NSFileManager defaultManager] removeItemAtPath:fileCachePath error:NULL]; + BOOL background = + [UIApplication sharedApplication].applicationState == UIApplicationStateBackground; + if (!background) { + _mbglView->createView(); + } + // Delete the pre-offline ambient cache at ~/Library/Caches/cache.db. + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *fileCachePath = [paths.firstObject stringByAppendingPathComponent:@"cache.db"]; + [[NSFileManager defaultManager] removeItemAtPath:fileCachePath error:NULL]; - // setup mbgl map - MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; + // setup mbgl map + MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; - std::optional localFontFamilyName = config.localFontFamilyName ? std::optional(std::string(config.localFontFamilyName.UTF8String)) : std::nullopt; - auto renderer = std::make_unique(_mbglView->getRendererBackend(), config.scaleFactor, localFontFamilyName); - BOOL enableCrossSourceCollisions = !config.perSourceCollisions; - _rendererFrontend = std::make_unique(std::move(renderer), self, _mbglView->getRendererBackend()); + std::optional localFontFamilyName = + config.localFontFamilyName + ? std::optional(std::string(config.localFontFamilyName.UTF8String)) + : std::nullopt; + auto renderer = std::make_unique(_mbglView->getRendererBackend(), + config.scaleFactor, localFontFamilyName); + BOOL enableCrossSourceCollisions = !config.perSourceCollisions; + _rendererFrontend = std::make_unique(std::move(renderer), self, + _mbglView->getRendererBackend()); - mbgl::MapOptions mapOptions; - mapOptions.withMapMode(mbgl::MapMode::Continuous) - .withSize(self.size) - .withPixelRatio(config.scaleFactor) - .withConstrainMode(mbgl::ConstrainMode::None) - .withViewportMode(mbgl::ViewportMode::Default) - .withCrossSourceCollisions(enableCrossSourceCollisions); + mbgl::MapOptions mapOptions; + mapOptions.withMapMode(mbgl::MapMode::Continuous) + .withSize(self.size) + .withPixelRatio(config.scaleFactor) + .withConstrainMode(mbgl::ConstrainMode::None) + .withViewportMode(mbgl::ViewportMode::Default) + .withCrossSourceCollisions(enableCrossSourceCollisions); + mbgl::TileServerOptions *tileServerOptions = + [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) + .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) + .withTileServerOptions(*tileServerOptions); + mbgl::ClientOptions clientOptions; - mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - mbgl::ResourceOptions resourceOptions; - resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) - .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) - .withTileServerOptions(*tileServerOptions); - mbgl::ClientOptions clientOptions; + auto apiKey = [[MLNSettings sharedSettings] apiKey]; + if (apiKey) { + resourceOptions.withApiKey([apiKey UTF8String]); + } - auto apiKey = [[MLNSettings sharedSettings] apiKey]; - if (apiKey) { - resourceOptions.withApiKey([apiKey UTF8String]); - } + const mbgl::util::ActionJournalOptions &actionJournalOptions = + [mlnMapoptions.actionJournalOptions getCoreOptions]; - const mbgl::util::ActionJournalOptions& actionJournalOptions = [mlnMapoptions.actionJournalOptions getCoreOptions]; + NSAssert(!_mbglMap, @"_mbglMap should be NULL"); + _mbglMap = std::make_unique(*_rendererFrontend, *_mbglView, mapOptions, + resourceOptions, clientOptions, actionJournalOptions); - NSAssert(!_mbglMap, @"_mbglMap should be NULL"); - _mbglMap = std::make_unique(*_rendererFrontend, *_mbglView, mapOptions, - resourceOptions, clientOptions, actionJournalOptions); + // start paused if launch into the background + if (background) { + self.dormant = YES; + } - // start paused if launch into the background - if (background) { - self.dormant = YES; - } + // Notify map object when network reachability status changes. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(reachabilityChanged:) + name:kMLNReachabilityChangedNotification + object:nil]; - // Notify map object when network reachability status changes. - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(reachabilityChanged:) - name:kMLNReachabilityChangedNotification - object:nil]; + _reachability = [MLNReachability reachabilityForInternetConnection]; + if ([_reachability isReachable]) { + _isWaitingForRedundantReachableNotification = YES; + } + [_reachability startNotifier]; - _reachability = [MLNReachability reachabilityForInternetConnection]; - if ([_reachability isReachable]) - { - _isWaitingForRedundantReachableNotification = YES; - } - [_reachability startNotifier]; + // setup default location manager + self.locationManager = nil; - // setup default location manager - self.locationManager = nil; + // Set up annotation management and selection state. + _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; + _annotationContextsByAnnotationTag = {}; + _annotationTagsByAnnotation = {}; + _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary]; + _selectedAnnotationTag = MLNAnnotationTagNotFound; + _annotationsNearbyLastTap = {}; - // Set up annotation management and selection state. - _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; - _annotationContextsByAnnotationTag = {}; - _annotationTagsByAnnotation = {}; - _annotationViewReuseQueueByIdentifier = [NSMutableDictionary dictionary]; - _selectedAnnotationTag = MLNAnnotationTagNotFound; - _annotationsNearbyLastTap = {}; + // TODO: This warning should be removed when automaticallyAdjustsScrollViewInsets is removed from + // the UIViewController api. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSLog(@"%@ WARNING UIViewController.automaticallyAdjustsScrollViewInsets is deprecated use " + @"MLNMapView.automaticallyAdjustContentInset instead.", + NSStringFromClass(self.class)); + }); - // TODO: This warning should be removed when automaticallyAdjustsScrollViewInsets is removed from - // the UIViewController api. - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSLog(@"%@ WARNING UIViewController.automaticallyAdjustsScrollViewInsets is deprecated use MLNMapView.automaticallyAdjustContentInset instead.", - NSStringFromClass(self.class)); - }); + _showsLogoView = YES; + _showsCompassView = YES; + _showsAttributionButton = YES; - _showsLogoView = YES; - _showsCompassView = YES; - _showsAttributionButton = YES; + // setup logo + // + UIImage *logo = [UIImage mgl_resourceImageNamed:@"maplibre-logo-stroke-gray"]; + _logoView = [[UIImageView alloc] initWithImage:logo]; + _logoView.accessibilityTraits = UIAccessibilityTraitStaticText; + _logoView.accessibilityLabel = NSLocalizedStringWithDefaultValue( + @"LOGO_A11Y_LABEL", nil, nil, @"Mapbox", @"Accessibility label"); + _logoView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_logoView]; + _logoView.hidden = !_showsLogoView; + _logoViewConstraints = [NSMutableArray array]; + _logoViewPosition = MLNOrnamentPositionBottomLeft; + _logoViewMargins = MLNOrnamentDefaultPositionOffset; - // setup logo - // - UIImage *logo = [UIImage mgl_resourceImageNamed:@"maplibre-logo-stroke-gray"]; - _logoView = [[UIImageView alloc] initWithImage:logo]; - _logoView.accessibilityTraits = UIAccessibilityTraitStaticText; - _logoView.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"LOGO_A11Y_LABEL", nil, nil, @"Mapbox", @"Accessibility label"); - _logoView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_logoView]; - _logoView.hidden = !_showsLogoView; - _logoViewConstraints = [NSMutableArray array]; - _logoViewPosition = MLNOrnamentPositionBottomLeft; - _logoViewMargins = MLNOrnamentDefaultPositionOffset; + // setup attribution + // + _attributionButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; + _attributionButton.accessibilityLabel = NSLocalizedStringWithDefaultValue( + @"INFO_A11Y_LABEL", nil, nil, @"About this map", @"Accessibility label"); + _attributionButton.accessibilityHint = NSLocalizedStringWithDefaultValue( + @"INFO_A11Y_HINT", nil, nil, @"Shows credits, a feedback form, and more", + @"Accessibility hint"); + [_attributionButton addTarget:self + action:@selector(showAttribution:) + forControlEvents:UIControlEventTouchUpInside]; + _attributionButton.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_attributionButton]; + _attributionButton.hidden = !_showsAttributionButton; + _attributionButtonConstraints = [NSMutableArray array]; - // setup attribution - // - _attributionButton = [UIButton buttonWithType:UIButtonTypeInfoLight]; - _attributionButton.accessibilityLabel = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_LABEL", nil, nil, @"About this map", @"Accessibility label"); - _attributionButton.accessibilityHint = NSLocalizedStringWithDefaultValue(@"INFO_A11Y_HINT", nil, nil, @"Shows credits, a feedback form, and more", @"Accessibility hint"); - [_attributionButton addTarget:self action:@selector(showAttribution:) forControlEvents:UIControlEventTouchUpInside]; - _attributionButton.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_attributionButton]; - _attributionButton.hidden = !_showsAttributionButton; - _attributionButtonConstraints = [NSMutableArray array]; + UILongPressGestureRecognizer *attributionLongPress = + [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showAttribution:)]; + [_attributionButton addGestureRecognizer:attributionLongPress]; + _attributionButtonPosition = MLNOrnamentPositionBottomRight; + _attributionButtonMargins = MLNOrnamentDefaultPositionOffset; - UILongPressGestureRecognizer *attributionLongPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(showAttribution:)]; - [_attributionButton addGestureRecognizer:attributionLongPress]; - _attributionButtonPosition = MLNOrnamentPositionBottomRight; - _attributionButtonMargins = MLNOrnamentDefaultPositionOffset; + // setup compass + // + _compassView = [MLNCompassButton compassButtonWithMapView:self]; + [self addSubview:_compassView]; + _compassView.hidden = !_showsCompassView; + _compassViewConstraints = [NSMutableArray array]; + _compassViewPosition = MLNOrnamentPositionTopRight; + _compassViewMargins = MLNOrnamentDefaultPositionOffset; - // setup compass - // - _compassView = [MLNCompassButton compassButtonWithMapView:self]; - [self addSubview:_compassView]; - _compassView.hidden = !_showsCompassView; - _compassViewConstraints = [NSMutableArray array]; - _compassViewPosition = MLNOrnamentPositionTopRight; - _compassViewMargins = MLNOrnamentDefaultPositionOffset; + // setup scale control + // + _scaleBar = [[MLNScaleBar alloc] init]; + _scaleBar.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_scaleBar]; + _scaleBarConstraints = [NSMutableArray array]; + _scaleBarPosition = MLNOrnamentPositionTopLeft; + _scaleBarMargins = MLNOrnamentDefaultPositionOffset; - // setup scale control - // - _scaleBar = [[MLNScaleBar alloc] init]; - _scaleBar.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_scaleBar]; - _scaleBarConstraints = [NSMutableArray array]; - _scaleBarPosition = MLNOrnamentPositionTopLeft; - _scaleBarMargins = MLNOrnamentDefaultPositionOffset; + [self installConstraints]; - [self installConstraints]; + // setup interaction + // - // setup interaction - // - - self.anchorRotateOrZoomGesturesToCenterCoordinate = NO; + self.anchorRotateOrZoomGesturesToCenterCoordinate = NO; #if TARGET_IPHONE_SIMULATOR & (TARGET_CPU_X86 | TARGET_CPU_X86_64) - bool isM1Simulator = processIsTranslated() > 0; - if (isM1Simulator) { - _pan = [[MBGLPanGesture alloc] initWithTarget:self action:@selector(handlePanGesture:)]; - } else { - _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; - } -#else + bool isM1Simulator = processIsTranslated() > 0; + if (isM1Simulator) { + _pan = [[MBGLPanGesture alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + } else { _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + } +#else + _pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; #endif - _pan.delegate = self; - _pan.maximumNumberOfTouches = 1; - [self addGestureRecognizer:_pan]; - _scrollEnabled = YES; - _panScrollingMode = MLNPanScrollingModeDefault; + _pan.delegate = self; + _pan.maximumNumberOfTouches = 1; + [self addGestureRecognizer:_pan]; + _scrollEnabled = YES; + _panScrollingMode = MLNPanScrollingModeDefault; #if TARGET_IPHONE_SIMULATOR & (TARGET_CPU_X86 | TARGET_CPU_X86_64) - if (isM1Simulator) { - _pinch = [[MBGLPinchGesture alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; - } else { - _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; - } + if (isM1Simulator) { + _pinch = [[MBGLPinchGesture alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; + } else { + _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self + action:@selector(handlePinchGesture:)]; + } #else - _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; + _pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self + action:@selector(handlePinchGesture:)]; #endif - _pinch.delegate = self; - [self addGestureRecognizer:_pinch]; - _zoomEnabled = YES; - _quickZoomReversed = NO; + _pinch.delegate = self; + [self addGestureRecognizer:_pinch]; + _zoomEnabled = YES; + _quickZoomReversed = NO; #if TARGET_IPHONE_SIMULATOR & (TARGET_CPU_X86 | TARGET_CPU_X86_64) - if (isM1Simulator) { - _rotate = [[MBGLRotationGesture alloc] initWithTarget:self action:@selector(handleRotateGesture:)]; - } else { - _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)]; - } + if (isM1Simulator) { + _rotate = [[MBGLRotationGesture alloc] initWithTarget:self + action:@selector(handleRotateGesture:)]; + } else { + _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self + action:@selector(handleRotateGesture:)]; + } #else - _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotateGesture:)]; + _rotate = [[UIRotationGestureRecognizer alloc] initWithTarget:self + action:@selector(handleRotateGesture:)]; #endif - _rotate.delegate = self; - [self addGestureRecognizer:_rotate]; - _rotateEnabled = YES; - _rotationThresholdWhileZooming = 3; - _toleranceForSnappingToNorth = 7; + _rotate.delegate = self; + [self addGestureRecognizer:_rotate]; + _rotateEnabled = YES; + _rotationThresholdWhileZooming = 3; + _toleranceForSnappingToNorth = 7; - _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)]; - _doubleTap.numberOfTapsRequired = 2; - [self addGestureRecognizer:_doubleTap]; + _doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(handleDoubleTapGesture:)]; + _doubleTap.numberOfTapsRequired = 2; + [self addGestureRecognizer:_doubleTap]; - _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)]; - _twoFingerDrag.minimumNumberOfTouches = 2; - _twoFingerDrag.maximumNumberOfTouches = 2; - _twoFingerDrag.delegate = self; - [_twoFingerDrag requireGestureRecognizerToFail:_pan]; - [self addGestureRecognizer:_twoFingerDrag]; - _pitchEnabled = YES; + _twoFingerDrag = + [[UIPanGestureRecognizer alloc] initWithTarget:self + action:@selector(handleTwoFingerDragGesture:)]; + _twoFingerDrag.minimumNumberOfTouches = 2; + _twoFingerDrag.maximumNumberOfTouches = 2; + _twoFingerDrag.delegate = self; + [_twoFingerDrag requireGestureRecognizerToFail:_pan]; + [self addGestureRecognizer:_twoFingerDrag]; + _pitchEnabled = YES; - _twoFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerTapGesture:)]; - _twoFingerTap.numberOfTouchesRequired = 2; - [_twoFingerTap requireGestureRecognizerToFail:_pinch]; - [_twoFingerTap requireGestureRecognizerToFail:_rotate]; - [_twoFingerTap requireGestureRecognizerToFail:_twoFingerDrag]; - [self addGestureRecognizer:_twoFingerTap]; + _twoFingerTap = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(handleTwoFingerTapGesture:)]; + _twoFingerTap.numberOfTouchesRequired = 2; + [_twoFingerTap requireGestureRecognizerToFail:_pinch]; + [_twoFingerTap requireGestureRecognizerToFail:_rotate]; + [_twoFingerTap requireGestureRecognizerToFail:_twoFingerDrag]; + [self addGestureRecognizer:_twoFingerTap]; - _hapticFeedbackEnabled = YES; + _hapticFeedbackEnabled = YES; - _decelerationRate = MLNMapViewDecelerationRateNormal; + _decelerationRate = MLNMapViewDecelerationRateNormal; - _quickZoom = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleQuickZoomGesture:)]; - _quickZoom.numberOfTapsRequired = 1; - _quickZoom.minimumPressDuration = 0; - [_quickZoom requireGestureRecognizerToFail:_doubleTap]; - [self addGestureRecognizer:_quickZoom]; + _quickZoom = + [[UILongPressGestureRecognizer alloc] initWithTarget:self + action:@selector(handleQuickZoomGesture:)]; + _quickZoom.numberOfTapsRequired = 1; + _quickZoom.minimumPressDuration = 0; + [_quickZoom requireGestureRecognizerToFail:_doubleTap]; + [self addGestureRecognizer:_quickZoom]; - _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; - [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; - _singleTapGestureRecognizer.delegate = self; - [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom]; - [self addGestureRecognizer:_singleTapGestureRecognizer]; + _singleTapGestureRecognizer = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(handleSingleTapGesture:)]; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; + _singleTapGestureRecognizer.delegate = self; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom]; + [self addGestureRecognizer:_singleTapGestureRecognizer]; - // observe app activity - // - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; + // observe app activity + // + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willTerminate) + name:UIApplicationWillTerminateNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willResignActive:) + name:UIApplicationWillResignActiveNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(willEnterForeground:) + name:UIApplicationWillEnterForegroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didBecomeActive:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; - // Pending completion blocks are called *after* annotation views have been updated - // in updateFromDisplayLink. - _pendingCompletionBlocks = [NSMutableArray array]; + // Pending completion blocks are called *after* annotation views have been updated + // in updateFromDisplayLink. + _pendingCompletionBlocks = [NSMutableArray array]; + // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or + // call `pauseRendering:` in response to it, as doing so causes a loop when asking for location + // permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225 - // As of 3.7.5, we intentionally do not listen for `UIApplicationWillResignActiveNotification` or call `pauseRendering:` in response to it, as doing - // so causes a loop when asking for location permission. See: https://github.com/mapbox/mapbox-gl-native/issues/11225 + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(didReceiveMemoryWarning) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; + // Device orientation management + self.currentOrientation = UIInterfaceOrientationUnknown; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(deviceOrientationDidChange:) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; - // Device orientation management - self.currentOrientation = UIInterfaceOrientationUnknown; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; - [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + // set initial position + // + mbgl::CameraOptions options; + options.center = mbgl::LatLng(0, 0); + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + options.padding = padding; + options.zoom = 0; - // set initial position - // - mbgl::CameraOptions options; - options.center = mbgl::LatLng(0, 0); - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); - options.padding = padding; - options.zoom = 0; + _cameraChangeReasonBitmask = MLNCameraChangeReasonNone; - _cameraChangeReasonBitmask = MLNCameraChangeReasonNone; + _mbglMap->jumpTo(options); + _pendingLatitude = NAN; + _pendingLongitude = NAN; + _targetCoordinate = kCLLocationCoordinate2DInvalid; - _mbglMap->jumpTo(options); - _pendingLatitude = NAN; - _pendingLongitude = NAN; - _targetCoordinate = kCLLocationCoordinate2DInvalid; + _shouldRequestAuthorizationToUseLocationServices = YES; - _shouldRequestAuthorizationToUseLocationServices = YES; - - _dynamicNavigationCameraAnimationDuration = NO; + _dynamicNavigationCameraAnimationDuration = NO; } -- (mbgl::Size)size -{ - // check for minimum texture size supported by OpenGL ES 2.0 - // - CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64)); - return { static_cast(size.width), - static_cast(size.height) }; +- (mbgl::Size)size { + // check for minimum texture size supported by OpenGL ES 2.0 + // + CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64)); + return {static_cast(size.width), static_cast(size.height)}; } -- (void)reachabilityChanged:(NSNotification *)notification -{ - MLNAssertIsMainThread(); +- (void)reachabilityChanged:(NSNotification *)notification { + MLNAssertIsMainThread(); - MLNReachability *reachability = [notification object]; - if ( ! _isWaitingForRedundantReachableNotification && [reachability isReachable]) - { - mbgl::NetworkStatus::Reachable(); - } - _isWaitingForRedundantReachableNotification = NO; + MLNReachability *reachability = [notification object]; + if (!_isWaitingForRedundantReachableNotification && [reachability isReachable]) { + mbgl::NetworkStatus::Reachable(); + } + _isWaitingForRedundantReachableNotification = NO; } - - (void)destroyCoreObjects { - // Record the current state. Currently only saving a limited set of properties. - self.terminated = YES; - self.residualCamera = self.camera; - self.residualDebugMask = self.debugMask; - if (!_initialStyleJSON) { - self.residualStyleURL = self.styleURL; - } + // Record the current state. Currently only saving a limited set of properties. + self.terminated = YES; + self.residualCamera = self.camera; + self.residualDebugMask = self.debugMask; + if (!_initialStyleJSON) { + self.residualStyleURL = self.styleURL; + } - // Tear down C++ objects, insuring worker threads correctly terminate. - // Because of how _mbglMap is constructed, we need to destroy it first. - _mbglMap.reset(); + // Tear down C++ objects, insuring worker threads correctly terminate. + // Because of how _mbglMap is constructed, we need to destroy it first. + _mbglMap.reset(); - _mbglView.reset(); + _mbglView.reset(); - _rendererFrontend.reset(); + _rendererFrontend.reset(); - _style = nil; + _style = nil; } -- (void)dealloc -{ - MLNLogInfo(@"Deallocating MLNMapView."); - [_reachability stopNotifier]; +- (void)dealloc { + MLNLogInfo(@"Deallocating MLNMapView."); + [_reachability stopNotifier]; - [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; - // Removing the annotations unregisters any outstanding KVO observers. - NSArray *annotations = self.annotations; - if (annotations) - { - [self removeAnnotations:annotations]; - } + // Removing the annotations unregisters any outstanding KVO observers. + NSArray *annotations = self.annotations; + if (annotations) { + [self removeAnnotations:annotations]; + } - [self destroyDisplayLink]; + [self destroyDisplayLink]; - [self destroyCoreObjects]; + [self destroyCoreObjects]; - [self.compassViewConstraints removeAllObjects]; - self.compassViewConstraints = nil; + [self.compassViewConstraints removeAllObjects]; + self.compassViewConstraints = nil; - [self.scaleBarConstraints removeAllObjects]; - self.scaleBarConstraints = nil; + [self.scaleBarConstraints removeAllObjects]; + self.scaleBarConstraints = nil; - [self.logoViewConstraints removeAllObjects]; - self.logoViewConstraints = nil; + [self.logoViewConstraints removeAllObjects]; + self.logoViewConstraints = nil; - [self.attributionButtonConstraints removeAllObjects]; - self.attributionButtonConstraints = nil; + [self.attributionButtonConstraints removeAllObjects]; + self.attributionButtonConstraints = nil; - [_locationManager stopUpdatingLocation]; - [_locationManager stopUpdatingHeading]; - _locationManager.delegate = nil; + [_locationManager stopUpdatingLocation]; + [_locationManager stopUpdatingHeading]; + _locationManager.delegate = nil; } -- (void)setDelegate:(nullable id)delegate -{ - MLNLogDebug(@"Setting delegate: %@", delegate); - if (_delegate == delegate) return; +- (void)setDelegate:(nullable id)delegate { + MLNLogDebug(@"Setting delegate: %@", delegate); + if (_delegate == delegate) return; - _delegate = delegate; + _delegate = delegate; - _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; - _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; - _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; - _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; + _delegateHasAlphasForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; + _delegateHasStrokeColorsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; + _delegateHasFillColorsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasLineWidthsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; } -- (void)didReceiveMemoryWarning -{ - MLNAssertIsMainThread(); +- (void)didReceiveMemoryWarning { + MLNAssertIsMainThread(); - if ( ! self.dormant && _rendererFrontend) - { - _rendererFrontend->reduceMemoryUse(); - } + if (!self.dormant && _rendererFrontend) { + _rendererFrontend->reduceMemoryUse(); + } } -- (MLNMapViewImpl *)viewImpl -{ - return _mbglView.get(); +- (MLNMapViewImpl *)viewImpl { + return _mbglView.get(); } // MARK: - Layout - -+ (BOOL)requiresConstraintBasedLayout -{ - return YES; ++ (BOOL)requiresConstraintBasedLayout { + return YES; } - (void)setScaleBarPosition:(MLNOrnamentPosition)scaleBarPosition { - MLNLogDebug(@"Setting scaleBarPosition: %lu", scaleBarPosition); - _scaleBarPosition = scaleBarPosition; - [self installScaleBarConstraints]; + MLNLogDebug(@"Setting scaleBarPosition: %lu", scaleBarPosition); + _scaleBarPosition = scaleBarPosition; + [self installScaleBarConstraints]; } - (void)setScaleBarMargins:(CGPoint)scaleBarMargins { - MLNLogDebug(@"Setting scaleBarMargins: (x:%f, y:%f)", scaleBarMargins.x, scaleBarMargins.y); - _scaleBarMargins = scaleBarMargins; - [self installScaleBarConstraints]; + MLNLogDebug(@"Setting scaleBarMargins: (x:%f, y:%f)", scaleBarMargins.x, scaleBarMargins.y); + _scaleBarMargins = scaleBarMargins; + [self installScaleBarConstraints]; } - (void)setCompassViewPosition:(MLNOrnamentPosition)compassViewPosition { - MLNLogDebug(@"Setting compassViewPosition: %lu", compassViewPosition); - _compassViewPosition = compassViewPosition; - [self installCompassViewConstraints]; + MLNLogDebug(@"Setting compassViewPosition: %lu", compassViewPosition); + _compassViewPosition = compassViewPosition; + [self installCompassViewConstraints]; } - (void)setCompassViewMargins:(CGPoint)compassViewMargins { - MLNLogDebug(@"Setting compassViewOffset: (x:%f, y:%f)", compassViewMargins.x, compassViewMargins.y); - _compassViewMargins = compassViewMargins; - [self installCompassViewConstraints]; + MLNLogDebug(@"Setting compassViewOffset: (x:%f, y:%f)", compassViewMargins.x, + compassViewMargins.y); + _compassViewMargins = compassViewMargins; + [self installCompassViewConstraints]; } - (void)setLogoViewPosition:(MLNOrnamentPosition)logoViewPosition { - MLNLogDebug(@"Setting logoViewPosition: %lu", logoViewPosition); - _logoViewPosition = logoViewPosition; - [self installLogoViewConstraints]; + MLNLogDebug(@"Setting logoViewPosition: %lu", logoViewPosition); + _logoViewPosition = logoViewPosition; + [self installLogoViewConstraints]; } - (void)setLogoViewMargins:(CGPoint)logoViewMargins { - MLNLogDebug(@"Setting logoViewMargins: (x:%f, y:%f)", logoViewMargins.x, logoViewMargins.y); - _logoViewMargins = logoViewMargins; - [self installLogoViewConstraints]; + MLNLogDebug(@"Setting logoViewMargins: (x:%f, y:%f)", logoViewMargins.x, logoViewMargins.y); + _logoViewMargins = logoViewMargins; + [self installLogoViewConstraints]; } - (void)setAttributionButtonPosition:(MLNOrnamentPosition)attributionButtonPosition { - MLNLogDebug(@"Setting attributionButtonPosition: %lu", attributionButtonPosition); - _attributionButtonPosition = attributionButtonPosition; - [self installAttributionButtonConstraints]; + MLNLogDebug(@"Setting attributionButtonPosition: %lu", attributionButtonPosition); + _attributionButtonPosition = attributionButtonPosition; + [self installAttributionButtonConstraints]; } - (void)setAttributionButtonMargins:(CGPoint)attributionButtonMargins { - MLNLogDebug(@"Setting attributionButtonMargins: (x:%f, y:%f)", attributionButtonMargins.x, attributionButtonMargins.y); - _attributionButtonMargins = attributionButtonMargins; - [self installAttributionButtonConstraints]; + MLNLogDebug(@"Setting attributionButtonMargins: (x:%f, y:%f)", attributionButtonMargins.x, + attributionButtonMargins.y); + _attributionButtonMargins = attributionButtonMargins; + [self installAttributionButtonConstraints]; } - (void)updateConstraintsForOrnament:(UIView *)view @@ -1126,1408 +1150,1343 @@ public: position:(MLNOrnamentPosition)position size:(CGSize)size margins:(CGPoint)margins { - NSMutableArray *updatedConstraints = [NSMutableArray array]; - UIEdgeInsets inset = UIEdgeInsetsZero; + NSMutableArray *updatedConstraints = [NSMutableArray array]; + UIEdgeInsets inset = UIEdgeInsetsZero; - BOOL automaticallyAdjustContentInset; - if (_automaticallyAdjustContentInsetHolder) { - automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue; - } else { - UIViewController *viewController = [self rootViewController]; + BOOL automaticallyAdjustContentInset; + if (_automaticallyAdjustContentInsetHolder) { + automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue; + } else { + UIViewController *viewController = [self rootViewController]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets; + automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets; #pragma clang diagnostic pop - } + } - if (! automaticallyAdjustContentInset) { - inset = UIEdgeInsetsMake(self.contentInset.top - self.safeMapViewContentInsets.top, - self.contentInset.left - self.safeMapViewContentInsets.left, - self.contentInset.bottom - self.safeMapViewContentInsets.bottom, - self.contentInset.right - self.safeMapViewContentInsets.right); + if (!automaticallyAdjustContentInset) { + inset = UIEdgeInsetsMake(self.contentInset.top - self.safeMapViewContentInsets.top, + self.contentInset.left - self.safeMapViewContentInsets.left, + self.contentInset.bottom - self.safeMapViewContentInsets.bottom, + self.contentInset.right - self.safeMapViewContentInsets.right); - // makes sure the insets don't have negative values that could hide the ornaments - // thus violating our ToS - inset = UIEdgeInsetsMake(fmaxf(inset.top, 0), - fmaxf(inset.left, 0), - fmaxf(inset.bottom, 0), - fmaxf(inset.right, 0)); - } + // makes sure the insets don't have negative values that could hide the ornaments + // thus violating our ToS + inset = UIEdgeInsetsMake(fmaxf(inset.top, 0), fmaxf(inset.left, 0), fmaxf(inset.bottom, 0), + fmaxf(inset.right, 0)); + } - switch (position) { - case MLNOrnamentPositionTopLeft: - [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]]; - [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]]; - break; - case MLNOrnamentPositionTopRight: - [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor constant:margins.y + inset.top]]; - [updatedConstraints addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]]; - break; - case MLNOrnamentPositionBottomLeft: - [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]]; - [updatedConstraints addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor constant:margins.x + inset.left]]; - break; - case MLNOrnamentPositionBottomRight: - [updatedConstraints addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:margins.y + inset.bottom]]; - [updatedConstraints addObject: [self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor constant:margins.x + inset.right]]; - break; - } + switch (position) { + case MLNOrnamentPositionTopLeft: + [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor + constant:margins.y + inset.top]]; + [updatedConstraints + addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor + constant:margins.x + inset.left]]; + break; + case MLNOrnamentPositionTopRight: + [updatedConstraints addObject:[view.topAnchor constraintEqualToAnchor:self.mgl_safeTopAnchor + constant:margins.y + inset.top]]; + [updatedConstraints + addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor + constant:margins.x + inset.right]]; + break; + case MLNOrnamentPositionBottomLeft: + [updatedConstraints + addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor + constant:margins.y + inset.bottom]]; + [updatedConstraints + addObject:[view.leadingAnchor constraintEqualToAnchor:self.mgl_safeLeadingAnchor + constant:margins.x + inset.left]]; + break; + case MLNOrnamentPositionBottomRight: + [updatedConstraints + addObject:[self.mgl_safeBottomAnchor constraintEqualToAnchor:view.bottomAnchor + constant:margins.y + inset.bottom]]; + [updatedConstraints + addObject:[self.mgl_safeTrailingAnchor constraintEqualToAnchor:view.trailingAnchor + constant:margins.x + inset.right]]; + break; + } - if (!CGSizeEqualToSize(size, CGSizeZero)) { - NSLayoutConstraint *widthConstraint = [view.widthAnchor constraintEqualToConstant:size.width]; - widthConstraint.identifier = @"width"; - NSLayoutConstraint *heightConstraint = [view.heightAnchor constraintEqualToConstant:size.height]; - heightConstraint.identifier = @"height"; - [updatedConstraints addObjectsFromArray:@[widthConstraint,heightConstraint]]; - } + if (!CGSizeEqualToSize(size, CGSizeZero)) { + NSLayoutConstraint *widthConstraint = [view.widthAnchor constraintEqualToConstant:size.width]; + widthConstraint.identifier = @"width"; + NSLayoutConstraint *heightConstraint = + [view.heightAnchor constraintEqualToConstant:size.height]; + heightConstraint.identifier = @"height"; + [updatedConstraints addObjectsFromArray:@[ widthConstraint, heightConstraint ]]; + } - [NSLayoutConstraint deactivateConstraints:constraints]; - [constraints removeAllObjects]; - [NSLayoutConstraint activateConstraints:updatedConstraints]; - [constraints addObjectsFromArray:updatedConstraints]; + [NSLayoutConstraint deactivateConstraints:constraints]; + [constraints removeAllObjects]; + [NSLayoutConstraint activateConstraints:updatedConstraints]; + [constraints addObjectsFromArray:updatedConstraints]; } -- (void)installConstraints -{ - [self installCompassViewConstraints]; - [self installScaleBarConstraints]; - [self installLogoViewConstraints]; - [self installAttributionButtonConstraints]; +- (void)installConstraints { + [self installCompassViewConstraints]; + [self installScaleBarConstraints]; + [self installLogoViewConstraints]; + [self installAttributionButtonConstraints]; } - (void)installCompassViewConstraints { - // compass view - [self updateConstraintsForOrnament:self.compassView - constraints:self.compassViewConstraints - position:self.compassViewPosition - size:[self sizeForOrnament:self.compassView constraints:self.compassViewConstraints] - margins:self.compassViewMargins]; + // compass view + [self updateConstraintsForOrnament:self.compassView + constraints:self.compassViewConstraints + position:self.compassViewPosition + size:[self sizeForOrnament:self.compassView + constraints:self.compassViewConstraints] + margins:self.compassViewMargins]; } - (void)installScaleBarConstraints { - // scale bar view - [self updateConstraintsForOrnament:self.scaleBar - constraints:self.scaleBarConstraints - position:self.scaleBarPosition - size:CGSizeZero - margins:self.scaleBarMargins]; + // scale bar view + [self updateConstraintsForOrnament:self.scaleBar + constraints:self.scaleBarConstraints + position:self.scaleBarPosition + size:CGSizeZero + margins:self.scaleBarMargins]; } - (void)installLogoViewConstraints { - // logo view - [self updateConstraintsForOrnament:self.logoView - constraints:self.logoViewConstraints - position:self.logoViewPosition - size:[self sizeForOrnament:self.logoView constraints:self.logoViewConstraints] - margins:self.logoViewMargins]; + // logo view + [self updateConstraintsForOrnament:self.logoView + constraints:self.logoViewConstraints + position:self.logoViewPosition + size:[self sizeForOrnament:self.logoView + constraints:self.logoViewConstraints] + margins:self.logoViewMargins]; } - (void)installAttributionButtonConstraints { - // attribution button - [self updateConstraintsForOrnament:self.attributionButton - constraints:self.attributionButtonConstraints - position:self.attributionButtonPosition - size:[self sizeForOrnament:self.attributionButton constraints:self.attributionButtonConstraints] - margins:self.attributionButtonMargins]; + // attribution button + [self updateConstraintsForOrnament:self.attributionButton + constraints:self.attributionButtonConstraints + position:self.attributionButtonPosition + size:[self sizeForOrnament:self.attributionButton + constraints:self.attributionButtonConstraints] + margins:self.attributionButtonMargins]; } -- (CGSize)sizeForOrnament:(UIView *)view - constraints:(NSMutableArray *)constraints { - // avoid regenerating size constraints - CGSize size = view.bounds.size; - if(constraints && constraints.count > 0) { - for (NSLayoutConstraint * constraint in constraints) { - if([constraint.identifier isEqualToString:@"width"]) { - size.width = constraint.constant; - } - else if ([constraint.identifier isEqualToString:@"height"]) { - size.height = constraint.constant; - } - } +- (CGSize)sizeForOrnament:(UIView *)view constraints:(NSMutableArray *)constraints { + // avoid regenerating size constraints + CGSize size = view.bounds.size; + if (constraints && constraints.count > 0) { + for (NSLayoutConstraint *constraint in constraints) { + if ([constraint.identifier isEqualToString:@"width"]) { + size.width = constraint.constant; + } else if ([constraint.identifier isEqualToString:@"height"]) { + size.height = constraint.constant; + } } + } - return size; + return size; } -- (BOOL)isOpaque -{ - return _opaque; +- (BOOL)isOpaque { + return _opaque; } -- (void)setOpaque:(BOOL)opaque -{ - _opaque = opaque; - if (_mbglView) { - _mbglView->setOpaque(opaque); - } +- (void)setOpaque:(BOOL)opaque { + _opaque = opaque; + if (_mbglView) { + _mbglView->setOpaque(opaque); + } } - (void)updateViewsWithCurrentUpdateParameters { - // Update UIKit elements, prior to rendering - [self updateUserLocationAnnotationView]; - [self updateAnnotationViews]; - [self updateCalloutView]; + // Update UIKit elements, prior to rendering + [self updateUserLocationAnnotationView]; + [self updateAnnotationViews]; + [self updateCalloutView]; } -- (BOOL)renderSync -{ - BOOL hasPendingBlocks = (self.pendingCompletionBlocks.count > 0); +- (BOOL)renderSync { + BOOL hasPendingBlocks = (self.pendingCompletionBlocks.count > 0); - if (!self.needsDisplayRefresh && !hasPendingBlocks) { - return NO; + if (!self.needsDisplayRefresh && !hasPendingBlocks) { + return NO; + } + + BOOL needsRender = self.needsDisplayRefresh; + + self.needsDisplayRefresh = NO; + + if (!self.dormant && needsRender) { + // It's important to call this *before* `_rendererFrontend->render()`, as + // that function saves the current `updateParameters` before rendering. If this + // occurs after then the views will be a frame behind. + // + // The update parameters will have been updated earlier, for example by + // calls to easeTo, flyTo, called from gesture handlers. + + [self updateViewsWithCurrentUpdateParameters]; + + if (_rendererFrontend) { + _rendererFrontend->render(); } + } - BOOL needsRender = self.needsDisplayRefresh; + if (hasPendingBlocks) { + // Call any pending completion blocks. This is primarily to ensure + // that annotations are in the expected position after core rendering + // and map update. + // + // TODO: Consider using this same mechanism for delegate callbacks. + [self processPendingBlocks]; + } - self.needsDisplayRefresh = NO; - - if (!self.dormant && needsRender) - { - // It's important to call this *before* `_rendererFrontend->render()`, as - // that function saves the current `updateParameters` before rendering. If this - // occurs after then the views will be a frame behind. - // - // The update parameters will have been updated earlier, for example by - // calls to easeTo, flyTo, called from gesture handlers. - - [self updateViewsWithCurrentUpdateParameters]; - - if (_rendererFrontend) { - - _rendererFrontend->render(); - - } - - } - - if (hasPendingBlocks) { - // Call any pending completion blocks. This is primarily to ensure - // that annotations are in the expected position after core rendering - // and map update. - // - // TODO: Consider using this same mechanism for delegate callbacks. - [self processPendingBlocks]; - } - - return YES; + return YES; } // This gets called when the view dimension changes, e.g. because the device is being rotated. -- (void)layoutSubviews -{ - [super layoutSubviews]; +- (void)layoutSubviews { + [super layoutSubviews]; - // Calling this here instead of in the scale bar itself because if this is done in the - // scale bar instance, it triggers a call to this `layoutSubviews` method that calls - // `_mbglMap->setSize()` just below that triggers rendering update which triggers - // another scale bar update which causes a rendering update loop and a major performace - // degradation. - [self.scaleBar invalidateIntrinsicContentSize]; + // Calling this here instead of in the scale bar itself because if this is done in the + // scale bar instance, it triggers a call to this `layoutSubviews` method that calls + // `_mbglMap->setSize()` just below that triggers rendering update which triggers + // another scale bar update which causes a rendering update loop and a major performace + // degradation. + [self.scaleBar invalidateIntrinsicContentSize]; - [self adjustContentInset]; + [self adjustContentInset]; - if (_mbglView) { - _mbglView->layoutChanged(); - } + if (_mbglView) { + _mbglView->layoutChanged(); + } - if (_mbglMap) { - self.mbglMap.setSize([self size]); - } + if (_mbglMap) { + self.mbglMap.setSize([self size]); + } - if (self.compassView.alpha) - { - [self updateCompass]; - } + if (self.compassView.alpha) { + [self updateCompass]; + } - if (self.compassView.alpha || self.showsUserHeadingIndicator) - { - [self updateHeadingForDeviceOrientation]; - } + if (self.compassView.alpha || self.showsUserHeadingIndicator) { + [self updateHeadingForDeviceOrientation]; + } - [self updateUserLocationAnnotationView]; + [self updateUserLocationAnnotationView]; - [self updateAttributionAlertView]; + [self updateAttributionAlertView]; - MLNAssert(self.attributionButton.isHidden || CGRectContainsRect(self.bounds, self.attributionButton.mgl_frameForIdentifyTransform), - @"The attribution is not in the visible area of the mapview. Please check your position and offset settings"); - MLNAssert(self.scaleBar.isHidden || CGRectContainsRect(self.bounds, self.scaleBar.mgl_frameForIdentifyTransform), - @"The scaleBar is not in the visible area of the mapview. Please check your position and offset settings"); - MLNAssert(self.compassView.isHidden || self.compassView.compassVisibility == MLNOrnamentVisibilityHidden || CGRectContainsRect(self.bounds, self.compassView.mgl_frameForIdentifyTransform), - @"The compassView is not in the visible area of the mapview. Please check your position and offset settings"); - MLNAssert(self.logoView.isHidden || CGRectContainsRect(self.bounds, self.logoView.mgl_frameForIdentifyTransform), - @"The logoView is not in the visible area of the mapview. Please check your position and offset settings"); + MLNAssert( + self.attributionButton.isHidden || + CGRectContainsRect(self.bounds, self.attributionButton.mgl_frameForIdentifyTransform), + @"The attribution is not in the visible area of the mapview. Please check your position and " + @"offset settings"); + MLNAssert(self.scaleBar.isHidden || + CGRectContainsRect(self.bounds, self.scaleBar.mgl_frameForIdentifyTransform), + @"The scaleBar is not in the visible area of the mapview. Please check your position " + @"and offset settings"); + MLNAssert(self.compassView.isHidden || + self.compassView.compassVisibility == MLNOrnamentVisibilityHidden || + CGRectContainsRect(self.bounds, self.compassView.mgl_frameForIdentifyTransform), + @"The compassView is not in the visible area of the mapview. Please check your " + @"position and offset settings"); + MLNAssert(self.logoView.isHidden || + CGRectContainsRect(self.bounds, self.logoView.mgl_frameForIdentifyTransform), + @"The logoView is not in the visible area of the mapview. Please check your position " + @"and offset settings"); } /// Updates `contentInset` to reflect the current window geometry. -- (void)adjustContentInset -{ - UIEdgeInsets adjustedContentInsets = UIEdgeInsetsZero; - UIViewController *viewController = [self rootViewController]; - BOOL automaticallyAdjustContentInset; - adjustedContentInsets = self.safeAreaInsets; +- (void)adjustContentInset { + UIEdgeInsets adjustedContentInsets = UIEdgeInsetsZero; + UIViewController *viewController = [self rootViewController]; + BOOL automaticallyAdjustContentInset; + adjustedContentInsets = self.safeAreaInsets; - if (_automaticallyAdjustContentInsetHolder) { - automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue; - } else { + if (_automaticallyAdjustContentInsetHolder) { + automaticallyAdjustContentInset = _automaticallyAdjustContentInsetHolder.boolValue; + } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets; + automaticallyAdjustContentInset = viewController.automaticallyAdjustsScrollViewInsets; #pragma clang diagnostic pop - } + } - self.safeMapViewContentInsets = adjustedContentInsets; - if ( ! automaticallyAdjustContentInset) - { - return; - } + self.safeMapViewContentInsets = adjustedContentInsets; + if (!automaticallyAdjustContentInset) { + return; + } - self.contentInset = adjustedContentInsets; + self.contentInset = adjustedContentInsets; } - (UIViewController *)rootViewController { - // We could crawl all the way up the responder chain using - // -viewControllerForLayoutGuides, but an intervening view means that any - // manual contentInset should not be overridden; something other than the - // top and bottom bars may be influencing the manual inset. - UIViewController *viewController; - if ([self.nextResponder isKindOfClass:[UIViewController class]]) - { - // This map view is the content view of a view controller. - viewController = (UIViewController *)self.nextResponder; - } - else if ([self.superview.nextResponder isKindOfClass:[UIViewController class]]) - { - // This map view is an immediate child of a view controller’s content view. - viewController = (UIViewController *)self.superview.nextResponder; - } - return viewController; + // We could crawl all the way up the responder chain using + // -viewControllerForLayoutGuides, but an intervening view means that any + // manual contentInset should not be overridden; something other than the + // top and bottom bars may be influencing the manual inset. + UIViewController *viewController; + if ([self.nextResponder isKindOfClass:[UIViewController class]]) { + // This map view is the content view of a view controller. + viewController = (UIViewController *)self.nextResponder; + } else if ([self.superview.nextResponder isKindOfClass:[UIViewController class]]) { + // This map view is an immediate child of a view controller’s content view. + viewController = (UIViewController *)self.superview.nextResponder; + } + return viewController; } - (void)setAutomaticallyAdjustsContentInset:(BOOL)automaticallyAdjustsContentInset { - MLNLogDebug(@"Setting automaticallyAdjustsContentInset: %@", MLNStringFromBOOL(automaticallyAdjustsContentInset)); - _automaticallyAdjustContentInsetHolder = [NSNumber numberWithBool:automaticallyAdjustsContentInset]; + MLNLogDebug(@"Setting automaticallyAdjustsContentInset: %@", + MLNStringFromBOOL(automaticallyAdjustsContentInset)); + _automaticallyAdjustContentInsetHolder = + [NSNumber numberWithBool:automaticallyAdjustsContentInset]; } - (BOOL)automaticallyAdjustsContentInset { - return _automaticallyAdjustContentInsetHolder.boolValue; + return _automaticallyAdjustContentInsetHolder.boolValue; +} + +- (void)setContentInset:(UIEdgeInsets)contentInset { + [self setContentInset:contentInset animated:NO completionHandler:nil]; +} + +- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated { + [self setContentInset:contentInset animated:animated completionHandler:nil]; } - (void)setContentInset:(UIEdgeInsets)contentInset -{ - [self setContentInset:contentInset animated:NO completionHandler:nil]; -} - -- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated -{ - [self setContentInset:contentInset animated:animated completionHandler:nil]; -} - -- (void)setContentInset:(UIEdgeInsets)contentInset animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting contentInset: %@ animated:", NSStringFromUIEdgeInsets(contentInset), MLNStringFromBOOL(animated)); - if (UIEdgeInsetsEqualToEdgeInsets(contentInset, self.contentInset)) - { - if (completion) { - completion(); - } - return; + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting contentInset: %@ animated:", NSStringFromUIEdgeInsets(contentInset), + MLNStringFromBOOL(animated)); + if (UIEdgeInsetsEqualToEdgeInsets(contentInset, self.contentInset)) { + if (completion) { + completion(); } + return; + } - if (self.userTrackingMode == MLNUserTrackingModeNone) - { - // Don’t call -setCenterCoordinate:, which resets the user tracking mode. - [self _setCenterCoordinate:self.centerCoordinate edgePadding:contentInset zoomLevel:self.zoomLevel direction:self.direction duration:animated ? MLNAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion]; - _contentInset = contentInset; - } - else - { - _contentInset = contentInset; - [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion]; - } + if (self.userTrackingMode == MLNUserTrackingModeNone) { + // Don’t call -setCenterCoordinate:, which resets the user tracking mode. + [self _setCenterCoordinate:self.centerCoordinate + edgePadding:contentInset + zoomLevel:self.zoomLevel + direction:self.direction + duration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil + completionHandler:completion]; + _contentInset = contentInset; + } else { + _contentInset = contentInset; + [self didUpdateLocationWithUserTrackingAnimated:animated completionHandler:completion]; + } - // Compass, logo and attribution button constraints needs to be updated.z - [self installConstraints]; + // Compass, logo and attribution button constraints needs to be updated.z + [self installConstraints]; } /// Returns the frame of inset content within the map view. -- (CGRect)contentFrame -{ - return UIEdgeInsetsInsetRect(self.bounds, self.contentInset); +- (CGRect)contentFrame { + return UIEdgeInsetsInsetRect(self.bounds, self.contentInset); } /// Returns the center point of the inset content within the map view. -- (CGPoint)contentCenter -{ - CGRect contentFrame = self.contentFrame; - return CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame)); +- (CGPoint)contentCenter { + CGRect contentFrame = self.contentFrame; + return CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame)); } // MARK: - Pending completion blocks -- (void)processPendingBlocks -{ - NSArray *blocks = self.pendingCompletionBlocks; - self.pendingCompletionBlocks = [NSMutableArray array]; +- (void)processPendingBlocks { + NSArray *blocks = self.pendingCompletionBlocks; + self.pendingCompletionBlocks = [NSMutableArray array]; - for (dispatch_block_t block in blocks) - { - block(); - } + for (dispatch_block_t block in blocks) { + block(); + } } -- (BOOL)scheduleTransitionCompletion:(dispatch_block_t)block -{ - // Only add a block if the display link (that calls processPendingBlocks) is - // running, otherwise fall back to calling immediately. - if (self.isDisplayLinkActive) - { - [self willChangeValueForKey:@"pendingCompletionBlocks"]; - [self.pendingCompletionBlocks addObject:block]; - [self didChangeValueForKey:@"pendingCompletionBlocks"]; - return YES; - } +- (BOOL)scheduleTransitionCompletion:(dispatch_block_t)block { + // Only add a block if the display link (that calls processPendingBlocks) is + // running, otherwise fall back to calling immediately. + if (self.isDisplayLinkActive) { + [self willChangeValueForKey:@"pendingCompletionBlocks"]; + [self.pendingCompletionBlocks addObject:block]; + [self didChangeValueForKey:@"pendingCompletionBlocks"]; + return YES; + } - return NO; + return NO; } // MARK: - Life Cycle - -- (void)updateFromDisplayLink:(CADisplayLink *)displayLink -{ - // CADisplayLink's call interval closely matches the that defined by, - // preferredFramesPerSecond, however it is NOT called on the vsync and - // can fire some time after the vsync, and the duration can often exceed - // the expected period. - // - // The `timestamp` property should represent (or be very close to) the vsync, - // so for any kind of frame rate measurement, it can be important to record - // the time upon entry to this method. - // - // This start time, coupled with the `targetTimestamp` gives you a measure - // of how long you have to do work before the next vsync. - // - // Note that CADisplayLink's duration property is interval between vsyncs at - // the device's natural frequency (60, 120). Instead, for the duration of a - // frame, use the two timestamps instead. This is especially important if - // you have set preferredFramesPerSecond to something other than the default. - // - // │ remaining duration ┃ - // │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶┃ - // ┌ ─ ─ ─ ─ ─ ┼───────────────────────╋───────────────────────────────────┳─────── - // │ ┃ ┃ - // │ │ ┃ ┃ - // │ ┃ ┃ - // ▼ │ ▼ ▼ - // timestamp │ target - // (vsync?) │ timestamp - // │ - // ▼ - // display link - // start time +- (void)updateFromDisplayLink:(CADisplayLink *)displayLink { + // CADisplayLink's call interval closely matches the that defined by, + // preferredFramesPerSecond, however it is NOT called on the vsync and + // can fire some time after the vsync, and the duration can often exceed + // the expected period. + // + // The `timestamp` property should represent (or be very close to) the vsync, + // so for any kind of frame rate measurement, it can be important to record + // the time upon entry to this method. + // + // This start time, coupled with the `targetTimestamp` gives you a measure + // of how long you have to do work before the next vsync. + // + // Note that CADisplayLink's duration property is interval between vsyncs at + // the device's natural frequency (60, 120). Instead, for the duration of a + // frame, use the two timestamps instead. This is especially important if + // you have set preferredFramesPerSecond to something other than the default. + // + // │ remaining duration ┃ + // │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶┃ + // ┌ ─ ─ ─ ─ ─ ┼───────────────────────╋───────────────────────────────────┳─────── + // │ ┃ ┃ + // │ │ ┃ ┃ + // │ ┃ ┃ + // ▼ │ ▼ ▼ + // timestamp │ target + // (vsync?) │ timestamp + // │ + // ▼ + // display link + // start time - MLNAssertIsMainThread(); + MLNAssertIsMainThread(); - // Not "visible" - this isn't a full definition of visibility, but if - // the map view doesn't have a window then it *cannot* be visible. - if (!self.window) { - return; - } - - // Mismatched display link - if (displayLink && displayLink != _displayLink) { - return; - } - - // Check to ensure rendering doesn't occur in the background - if (([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) && - ![self supportsBackgroundRendering]) - { - return; - } - - if (self.needsDisplayRefresh || (self.pendingCompletionBlocks.count > 0)) - { - // UIView update logic has moved into `renderSync` above, which now gets - // triggered by a call to setNeedsDisplay. - // See MLNMapViewOpenGLImpl::display() for more details - _mbglView->display(); - } - - // TODO: Fix - if (self.experimental_enableFrameRateMeasurement) - { - CFTimeInterval now = CACurrentMediaTime(); - - self.frameTime = now - _displayLink.timestamp; - _frameDurations += self.frameTime; - - _frameCount++; - - CFTimeInterval elapsed = now - _frameCounterStartTime; - - if (elapsed >= 1.0) { - self.averageFrameRate = _frameCount / elapsed; - self.averageFrameTime = (_frameDurations / _frameCount) * 1000; - - _frameCount = 0; - _frameDurations = 0; - _frameCounterStartTime = now; - } + // Not "visible" - this isn't a full definition of visibility, but if + // the map view doesn't have a window then it *cannot* be visible. + if (!self.window) { + return; + } + + // Mismatched display link + if (displayLink && displayLink != _displayLink) { + return; + } + + // Check to ensure rendering doesn't occur in the background + if (([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) && + ![self supportsBackgroundRendering]) { + return; + } + + if (self.needsDisplayRefresh || (self.pendingCompletionBlocks.count > 0)) { + // UIView update logic has moved into `renderSync` above, which now gets + // triggered by a call to setNeedsDisplay. + // See MLNMapViewOpenGLImpl::display() for more details + _mbglView->display(); + } + + // TODO: Fix + if (self.experimental_enableFrameRateMeasurement) { + CFTimeInterval now = CACurrentMediaTime(); + + self.frameTime = now - _displayLink.timestamp; + _frameDurations += self.frameTime; + + _frameCount++; + + CFTimeInterval elapsed = now - _frameCounterStartTime; + + if (elapsed >= 1.0) { + self.averageFrameRate = _frameCount / elapsed; + self.averageFrameTime = (_frameDurations / _frameCount) * 1000; + + _frameCount = 0; + _frameDurations = 0; + _frameCounterStartTime = now; } + } } -- (void)setNeedsRerender -{ - MLNAssertIsMainThread(); +- (void)setNeedsRerender { + MLNAssertIsMainThread(); - self.needsDisplayRefresh = YES; + self.needsDisplayRefresh = YES; } -- (void)willTerminate -{ - MLNAssertIsMainThread(); +- (void)willTerminate { + MLNAssertIsMainThread(); - if ( ! self.dormant) - { - [self.displayLink invalidate]; - self.displayLink = nil; - - self.dormant = YES; - - if (_rendererFrontend) { - _rendererFrontend->reduceMemoryUse(); - } - - _mbglView->deleteView(); - } - - [self destroyCoreObjects]; -} - -- (UIScreen *)windowScreen { - UIScreen *screen; - -#ifdef SUPPORT_UIWINDOWSCENE - if (@available(iOS 13.0, *)) { - if (self.window.windowScene) { - screen = self.window.windowScene.screen; - } - } -#endif - - // Fallback if there's no windowScene - if (!screen) { - screen = self.window.screen; - } - - return screen; -} - -- (BOOL)isVisible -{ - // "Visible" is not strictly true here - for example, the view hierarchy is not - // currently observed (e.g. looking at a parent's or the window's hidden - // status. - // This does NOT take application state into account - UIScreen *screen = [self windowScreen]; - return (!self.isHidden && screen); -} - -- (void)validateDisplayLink -{ - BOOL isVisible = self.superview && self.window; - if (isVisible && ! _displayLink) - { - if (_mbglMap && self.mbglMap.getMapOptions().constrainMode() == mbgl::ConstrainMode::None) - { - self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly); - } - - _displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(updateFromDisplayLink:)]; - [self updateDisplayLinkPreferredFramesPerSecond]; - [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - self.needsDisplayRefresh = YES; - [self updateFromDisplayLink:_displayLink]; - } - else if ( ! isVisible && _displayLink) - { - [_displayLink invalidate]; - _displayLink = nil; - [self processPendingBlocks]; - } -} - -- (void)updateDisplayLinkPreferredFramesPerSecond -{ - if (!_displayLink) - { - return; - } - - NSInteger newFrameRate; - if (_preferredFramesPerSecond == MLNMapViewPreferredFramesPerSecondDefault) - { - // On legacy devices that cannot maintain a reasonable frame rate, set - // a lower limit to avoid jank. - newFrameRate = UIDevice.currentDevice.mgl_isLegacyDevice ? MLNMapViewPreferredFramesPerSecondLowPower : MLNMapViewPreferredFramesPerSecondMaximum; - } - else - { - newFrameRate = _preferredFramesPerSecond; - } - - if (@available(iOS 10.0, *)) - { - _displayLink.preferredFramesPerSecond = newFrameRate; - } - else - { - // CADisplayLink.frameInterval does not support more than 60 FPS (and - // no device that supports >60 FPS ever supported iOS 9). - NSInteger maximumFrameRate = 60; - - // `0` is an alias for maximum frame rate. - newFrameRate = newFrameRate ?: maximumFrameRate; - - _displayLink.preferredFramesPerSecond = maximumFrameRate / MIN(newFrameRate, maximumFrameRate); - } -} - -- (void)setPreferredFramesPerSecond:(MLNMapViewPreferredFramesPerSecond)preferredFramesPerSecond -{ - MLNLogDebug(@"Setting preferredFramesPerSecond: %ld", preferredFramesPerSecond); - if (_preferredFramesPerSecond == preferredFramesPerSecond) - { - return; - } - - _preferredFramesPerSecond = preferredFramesPerSecond; - [self updateDisplayLinkPreferredFramesPerSecond]; -} - -- (void)updatePresentsWithTransaction -{ - BOOL hasEnoughViewAnnotations = (self.annotationContainerView.annotationViews.count > MLNPresentsWithTransactionAnnotationCount); - BOOL hasAnAnchoredCallout = [self hasAnAnchoredAnnotationCalloutView]; - - _enablePresentsWithTransaction = (hasEnoughViewAnnotations || hasAnAnchoredCallout); - - // If the map is visible, change the layer property too - if (self.window) { - _mbglView->setPresentsWithTransaction(_enablePresentsWithTransaction); - } -} - -- (void)willMoveToWindow:(UIWindow *)newWindow { - [super willMoveToWindow:newWindow]; - [self refreshSupportedInterfaceOrientationsWithWindow:newWindow]; - - if (!newWindow) - { - // See https://github.com/mapbox/mapbox-gl-native/issues/14232 - // In iOS 12.2, CAEAGLLayer.presentsWithTransaction can cause dramatic - // slow down. The exact cause of this is unknown, but this work around - // appears to lessen the effects. - _mbglView->setPresentsWithTransaction(NO); - } - - // Changing windows regardless of whether it's a new one, or the map is being - // removed from the hierarchy - [self destroyDisplayLink]; - - if (self.window) { -#ifdef SUPPORT_UIWINDOWSCENE - if (@available(iOS 13.0, *)) - { - [self.window removeObserver:self forKeyPath:@"windowScene" context:windowScreenContext]; - } - else -#endif - { - [self.window removeObserver:self forKeyPath:@"screen" context:windowScreenContext]; - } - } -} - -- (void)didMoveToWindow -{ - [super didMoveToWindow]; - MLNLogDebug(@"[%p] window=%p", self, self.window); - - if (self.window) - { - // See above comment - [self resumeRenderingIfNecessary]; - [self updatePresentsWithTransaction]; - -#ifdef SUPPORT_UIWINDOWSCENE - if (@available(iOS 13.0, *)) - { - [self.window addObserver:self forKeyPath:@"windowScene" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:windowScreenContext]; - } - else -#endif - { - [self.window addObserver:self forKeyPath:@"screen" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:windowScreenContext]; - } - } -} - -- (void)didMoveToSuperview -{ - [self validateDisplayLink]; - if (self.superview) - { - [self installConstraints]; - } - [super didMoveToSuperview]; -} - -- (void)refreshSupportedInterfaceOrientationsWithWindow:(UIWindow *)window { - - // "The system intersects the view controller'€™s supported orientations with - // the app's supported orientations (as determined by the Info.plist file or - // the app delegate's application:supportedInterfaceOrientationsForWindow: - // method) and the device's supported orientations to determine whether to rotate. - - UIApplication *application = [UIApplication sharedApplication]; - - if (window && [application.delegate respondsToSelector:@selector(application:supportedInterfaceOrientationsForWindow:)]) { - self.applicationSupportedInterfaceOrientations = [application.delegate application:application supportedInterfaceOrientationsForWindow:window]; - return; - } - - // If no delegate method, check the application's plist. - static UIInterfaceOrientationMask orientationMask = UIInterfaceOrientationMaskAll; - - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - // No application delegate - NSArray *orientations = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"]; - - // Application's info plist provided supported orientations. - if (orientations.count > 0) { - orientationMask = 0; - - NSDictionary *lookup = - @{ - @"UIInterfaceOrientationPortrait" : @(UIInterfaceOrientationMaskPortrait), - @"UIInterfaceOrientationPortraitUpsideDown" : @(UIInterfaceOrientationMaskPortraitUpsideDown), - @"UIInterfaceOrientationLandscapeLeft" : @(UIInterfaceOrientationMaskLandscapeLeft), - @"UIInterfaceOrientationLandscapeRight" : @(UIInterfaceOrientationMaskLandscapeRight) - }; - - for (NSString *orientation in orientations) { - UIInterfaceOrientationMask mask = ((NSNumber*)lookup[orientation]).unsignedIntegerValue; - orientationMask |= mask; - } - } - }); - - self.applicationSupportedInterfaceOrientations = orientationMask; -} - -- (void)deviceOrientationDidChange:(__unused NSNotification *)notification -{ - UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation]; - - // The docs for `UIViewController.supportedInterfaceOrientations` states: - // - // When the user changes the device orientation, the system calls this method - // on the root view controller or the topmost presented view controller that - // fills the window. If the view controller supports the new orientation, the - // window and view controller are rotated to the new orientation. This method - // is only called if the view controller'€™s shouldAutorotate method returns YES. - // - // We want to match similar behaviour. However, it may be preferable to look - // at the owning view controller (in cases where the map view may be covered - // by another view. - - UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController]; - - if (![viewController shouldAutorotate]) { - return; - } - - if ((self.currentOrientation == (UIInterfaceOrientation)deviceOrientation) && - (self.currentOrientation != UIInterfaceOrientationUnknown)) { - return; - } - - // "The system intersects the view controller'€™s supported orientations with - // the app's supported orientations (as determined by the Info.plist file or - // the app delegate's application:supportedInterfaceOrientationsForWindow: - // method) and the device's supported orientations to determine whether to rotate. - - UIInterfaceOrientationMask supportedOrientations = viewController.supportedInterfaceOrientations; - supportedOrientations &= self.applicationSupportedInterfaceOrientations; - - // Interface orientations are defined by device orientations - UIInterfaceOrientationMask interfaceOrientation = 1 << deviceOrientation; - UIInterfaceOrientationMask validOrientation = interfaceOrientation & UIInterfaceOrientationMaskAll; - - if (!(validOrientation & supportedOrientations)) { - return; - } - - self.currentOrientation = (UIInterfaceOrientation)deviceOrientation; - - // Q. Do we need to re-layout if we're just going from Portrait -> Portrait - // Upside Down (or from Left to Right)? - [self setNeedsLayout]; -} - -- (void)stopDisplayLink -{ - MLNLogDebug(@"[%p]", self); - self.displayLink.paused = YES; - self.needsDisplayRefresh = NO; - [self processPendingBlocks]; -} - -- (void)createDisplayLink -{ - MLNLogDebug(@"[%p]", self); - - // Create and start the display link in a *paused* state - MLNAssert(!self.displayLinkScreen, @""); - MLNAssert(!self.displayLink, @""); - MLNAssert(self.window, @""); - MLNAssert(self.window.screen, @""); - - self.displayLinkScreen = self.window.screen; - self.displayLink = [self.window.screen displayLinkWithTarget:self selector:@selector(updateFromDisplayLink:)]; - self.displayLink.paused = YES; - - [self updateDisplayLinkPreferredFramesPerSecond]; - - [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; - - if (_mbglMap && self.mbglMap.getMapOptions().constrainMode() == mbgl::ConstrainMode::None) - { - self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly); - } -} - -- (void)destroyDisplayLink -{ - MLNLogDebug(@"[%p]", self); + if (!self.dormant) { [self.displayLink invalidate]; self.displayLink = nil; - self.displayLinkScreen = nil; - self.needsDisplayRefresh = NO; - [self processPendingBlocks]; -} - -- (void)startDisplayLink -{ - MLNLogDebug(@"[%p]", self); - MLNAssert(self.displayLink, @""); - MLNAssert([self isVisible], @"Display link should only be started when allowed"); - - self.displayLink.paused = NO; - [self setNeedsRerender]; - [self updateFromDisplayLink:self.displayLink]; -} - -// MARK: - Application lifecycle -- (void)willResignActive:(NSNotification *)notification -{ - MLNAssertIsMainThread(); - MLNLogDebug(@"[%p]", self); - - // Going from active to inactive states. This could be because a system dialog - // has been displayed, control center, or the app is headed into the background - - if (self.supportsBackgroundRendering) { - return; - } - - // We want to pause the rendering - [self stopDisplayLink]; - - // For OpenGL this calls glFinish as recommended in - // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1 - // reduceMemoryUse(), calls performCleanup(), which calls glFinish - if (_rendererFrontend) - { - _rendererFrontend->reduceMemoryUse(); - } -} - -- (void)didEnterBackground:(NSNotification *)notification -{ - MLNAssertIsMainThread(); - MLNAssert(!self.dormant, @"Should not be dormant heading into background"); - MLNLogDebug(@"[%p] dormant=%d", self, self.dormant); - - // See comment in `supportsBackgroundRendering` above. - if (self.supportsBackgroundRendering) { - return; - } - - // We now want to stop rendering. - // We want to pause the rendering - [self stopDisplayLink]; - - // For OpenGL this calls glFinish as recommended in - // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1 - // reduceMemoryUse(), calls performCleanup(), which calls glFinish - if (_rendererFrontend) - { - _rendererFrontend->reduceMemoryUse(); - } - - // We now completely remove the display link, and the renderable resource. - // Although the method below is called `deleteView` this does NOT delete the - // GLKView, instead releasing the memory hungry resources. - [self destroyDisplayLink]; - [self processPendingBlocks]; - _mbglView->deleteView(); self.dormant = YES; - // Handle non-rendering issues. - [self validateLocationServices]; -} - -- (void)willEnterForeground:(NSNotification *)notification -{ - MLNLogDebug(@"[%p] dormant=%d", self, self.dormant); - - // We're transitioning from Background to Inactive states - - if (self.supportsBackgroundRendering) { - return; + if (_rendererFrontend) { + _rendererFrontend->reduceMemoryUse(); } - // Reverse the process of going into the background - _mbglView->createView(); + _mbglView->deleteView(); + } - // A display link needs the window's screen, so create it if we can - UIScreen *screen = [self windowScreen]; - - if (screen) { - [self createDisplayLink]; - - // If we can render during the inactive state, start the display link now - if (self.isVisible) { - [self startDisplayLink]; - } - } - self.dormant = NO; - - [self validateLocationServices]; + [self destroyCoreObjects]; } -- (void)didBecomeActive:(NSNotification *)notification -{ - MLNLogDebug(@"[%p] DL.paused=<%p>.paused=%d", self, self.displayLink, self.displayLink.paused); +- (UIScreen *)windowScreen { + UIScreen *screen; - // Most times, we should already have a display link created at this point, - // which may or may not be running. However, at the start of the application, - // it's possible to have a situation where the display link hasn't been created. +#ifdef SUPPORT_UIWINDOWSCENE + if (@available(iOS 13.0, *)) { + if (self.window.windowScene) { + screen = self.window.windowScene.screen; + } + } +#endif + + // Fallback if there's no windowScene + if (!screen) { + screen = self.window.screen; + } + + return screen; +} + +- (BOOL)isVisible { + // "Visible" is not strictly true here - for example, the view hierarchy is not + // currently observed (e.g. looking at a parent's or the window's hidden + // status. + // This does NOT take application state into account + UIScreen *screen = [self windowScreen]; + return (!self.isHidden && screen); +} + +- (void)validateDisplayLink { + BOOL isVisible = self.superview && self.window; + if (isVisible && !_displayLink) { + if (_mbglMap && self.mbglMap.getMapOptions().constrainMode() == mbgl::ConstrainMode::None) { + self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly); + } + + _displayLink = [self.window.screen displayLinkWithTarget:self + selector:@selector(updateFromDisplayLink:)]; + [self updateDisplayLinkPreferredFramesPerSecond]; + [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + self.needsDisplayRefresh = YES; + [self updateFromDisplayLink:_displayLink]; + } else if (!isVisible && _displayLink) { + [_displayLink invalidate]; + _displayLink = nil; + [self processPendingBlocks]; + } +} + +- (void)updateDisplayLinkPreferredFramesPerSecond { + if (!_displayLink) { + return; + } + + NSInteger newFrameRate; + if (_preferredFramesPerSecond == MLNMapViewPreferredFramesPerSecondDefault) { + // On legacy devices that cannot maintain a reasonable frame rate, set + // a lower limit to avoid jank. + newFrameRate = UIDevice.currentDevice.mgl_isLegacyDevice + ? MLNMapViewPreferredFramesPerSecondLowPower + : MLNMapViewPreferredFramesPerSecondMaximum; + } else { + newFrameRate = _preferredFramesPerSecond; + } + + if (@available(iOS 10.0, *)) { + _displayLink.preferredFramesPerSecond = newFrameRate; + } else { + // CADisplayLink.frameInterval does not support more than 60 FPS (and + // no device that supports >60 FPS ever supported iOS 9). + NSInteger maximumFrameRate = 60; + + // `0` is an alias for maximum frame rate. + newFrameRate = newFrameRate ?: maximumFrameRate; + + _displayLink.preferredFramesPerSecond = maximumFrameRate / MIN(newFrameRate, maximumFrameRate); + } +} + +- (void)setPreferredFramesPerSecond:(MLNMapViewPreferredFramesPerSecond)preferredFramesPerSecond { + MLNLogDebug(@"Setting preferredFramesPerSecond: %ld", preferredFramesPerSecond); + if (_preferredFramesPerSecond == preferredFramesPerSecond) { + return; + } + + _preferredFramesPerSecond = preferredFramesPerSecond; + [self updateDisplayLinkPreferredFramesPerSecond]; +} + +- (void)updatePresentsWithTransaction { + BOOL hasEnoughViewAnnotations = (self.annotationContainerView.annotationViews.count > + MLNPresentsWithTransactionAnnotationCount); + BOOL hasAnAnchoredCallout = [self hasAnAnchoredAnnotationCalloutView]; + + _enablePresentsWithTransaction = (hasEnoughViewAnnotations || hasAnAnchoredCallout); + + // If the map is visible, change the layer property too + if (self.window) { + _mbglView->setPresentsWithTransaction(_enablePresentsWithTransaction); + } +} + +- (void)willMoveToWindow:(UIWindow *)newWindow { + [super willMoveToWindow:newWindow]; + [self refreshSupportedInterfaceOrientationsWithWindow:newWindow]; + + if (!newWindow) { + // See https://github.com/mapbox/mapbox-gl-native/issues/14232 + // In iOS 12.2, CAEAGLLayer.presentsWithTransaction can cause dramatic + // slow down. The exact cause of this is unknown, but this work around + // appears to lessen the effects. + _mbglView->setPresentsWithTransaction(NO); + } + + // Changing windows regardless of whether it's a new one, or the map is being + // removed from the hierarchy + [self destroyDisplayLink]; + + if (self.window) { +#ifdef SUPPORT_UIWINDOWSCENE + if (@available(iOS 13.0, *)) { + [self.window removeObserver:self forKeyPath:@"windowScene" context:windowScreenContext]; + } else +#endif + { + [self.window removeObserver:self forKeyPath:@"screen" context:windowScreenContext]; + } + } +} + +- (void)didMoveToWindow { + [super didMoveToWindow]; + MLNLogDebug(@"[%p] window=%p", self, self.window); + + if (self.window) { + // See above comment [self resumeRenderingIfNecessary]; + [self updatePresentsWithTransaction]; + +#ifdef SUPPORT_UIWINDOWSCENE + if (@available(iOS 13.0, *)) { + [self.window addObserver:self + forKeyPath:@"windowScene" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:windowScreenContext]; + } else +#endif + { + [self.window addObserver:self + forKeyPath:@"screen" + options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld + context:windowScreenContext]; + } + } +} + +- (void)didMoveToSuperview { + [self validateDisplayLink]; + if (self.superview) { + [self installConstraints]; + } + [super didMoveToSuperview]; +} + +- (void)refreshSupportedInterfaceOrientationsWithWindow:(UIWindow *)window { + // "The system intersects the view controller'€™s supported orientations with + // the app's supported orientations (as determined by the Info.plist file or + // the app delegate's application:supportedInterfaceOrientationsForWindow: + // method) and the device's supported orientations to determine whether to rotate. + + UIApplication *application = [UIApplication sharedApplication]; + + if (window && [application.delegate respondsToSelector:@selector + (application:supportedInterfaceOrientationsForWindow:)]) { + self.applicationSupportedInterfaceOrientations = [application.delegate application:application + supportedInterfaceOrientationsForWindow:window]; + return; + } + + // If no delegate method, check the application's plist. + static UIInterfaceOrientationMask orientationMask = UIInterfaceOrientationMaskAll; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // No application delegate + NSArray *orientations = + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UISupportedInterfaceOrientations"]; + + // Application's info plist provided supported orientations. + if (orientations.count > 0) { + orientationMask = 0; + + NSDictionary *lookup = @{ + @"UIInterfaceOrientationPortrait" : @(UIInterfaceOrientationMaskPortrait), + @"UIInterfaceOrientationPortraitUpsideDown" : + @(UIInterfaceOrientationMaskPortraitUpsideDown), + @"UIInterfaceOrientationLandscapeLeft" : @(UIInterfaceOrientationMaskLandscapeLeft), + @"UIInterfaceOrientationLandscapeRight" : @(UIInterfaceOrientationMaskLandscapeRight) + }; + + for (NSString *orientation in orientations) { + UIInterfaceOrientationMask mask = ((NSNumber *)lookup[orientation]).unsignedIntegerValue; + orientationMask |= mask; + } + } + }); + + self.applicationSupportedInterfaceOrientations = orientationMask; +} + +- (void)deviceOrientationDidChange:(__unused NSNotification *)notification { + UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation]; + + // The docs for `UIViewController.supportedInterfaceOrientations` states: + // + // When the user changes the device orientation, the system calls this method + // on the root view controller or the topmost presented view controller that + // fills the window. If the view controller supports the new orientation, the + // window and view controller are rotated to the new orientation. This method + // is only called if the view controller'€™s shouldAutorotate method returns YES. + // + // We want to match similar behaviour. However, it may be preferable to look + // at the owning view controller (in cases where the map view may be covered + // by another view. + + UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController]; + + if (![viewController shouldAutorotate]) { + return; + } + + if ((self.currentOrientation == (UIInterfaceOrientation)deviceOrientation) && + (self.currentOrientation != UIInterfaceOrientationUnknown)) { + return; + } + + // "The system intersects the view controller'€™s supported orientations with + // the app's supported orientations (as determined by the Info.plist file or + // the app delegate's application:supportedInterfaceOrientationsForWindow: + // method) and the device's supported orientations to determine whether to rotate. + + UIInterfaceOrientationMask supportedOrientations = viewController.supportedInterfaceOrientations; + supportedOrientations &= self.applicationSupportedInterfaceOrientations; + + // Interface orientations are defined by device orientations + UIInterfaceOrientationMask interfaceOrientation = 1 << deviceOrientation; + UIInterfaceOrientationMask validOrientation = + interfaceOrientation & UIInterfaceOrientationMaskAll; + + if (!(validOrientation & supportedOrientations)) { + return; + } + + self.currentOrientation = (UIInterfaceOrientation)deviceOrientation; + + // Q. Do we need to re-layout if we're just going from Portrait -> Portrait + // Upside Down (or from Left to Right)? + [self setNeedsLayout]; +} + +- (void)stopDisplayLink { + MLNLogDebug(@"[%p]", self); + self.displayLink.paused = YES; + self.needsDisplayRefresh = NO; + [self processPendingBlocks]; +} + +- (void)createDisplayLink { + MLNLogDebug(@"[%p]", self); + + // Create and start the display link in a *paused* state + MLNAssert(!self.displayLinkScreen, @""); + MLNAssert(!self.displayLink, @""); + MLNAssert(self.window, @""); + MLNAssert(self.window.screen, @""); + + self.displayLinkScreen = self.window.screen; + self.displayLink = [self.window.screen displayLinkWithTarget:self + selector:@selector(updateFromDisplayLink:)]; + self.displayLink.paused = YES; + + [self updateDisplayLinkPreferredFramesPerSecond]; + + [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + + if (_mbglMap && self.mbglMap.getMapOptions().constrainMode() == mbgl::ConstrainMode::None) { + self.mbglMap.setConstrainMode(mbgl::ConstrainMode::HeightOnly); + } +} + +- (void)destroyDisplayLink { + MLNLogDebug(@"[%p]", self); + [self.displayLink invalidate]; + self.displayLink = nil; + self.displayLinkScreen = nil; + self.needsDisplayRefresh = NO; + [self processPendingBlocks]; +} + +- (void)startDisplayLink { + MLNLogDebug(@"[%p]", self); + MLNAssert(self.displayLink, @""); + MLNAssert([self isVisible], @"Display link should only be started when allowed"); + + self.displayLink.paused = NO; + [self setNeedsRerender]; + [self updateFromDisplayLink:self.displayLink]; +} + +// MARK: - Application lifecycle +- (void)willResignActive:(NSNotification *)notification { + MLNAssertIsMainThread(); + MLNLogDebug(@"[%p]", self); + + // Going from active to inactive states. This could be because a system dialog + // has been displayed, control center, or the app is headed into the background + + if (self.supportsBackgroundRendering) { + return; + } + + // We want to pause the rendering + [self stopDisplayLink]; + + // For OpenGL this calls glFinish as recommended in + // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1 + // reduceMemoryUse(), calls performCleanup(), which calls glFinish + if (_rendererFrontend) { + _rendererFrontend->reduceMemoryUse(); + } +} + +- (void)didEnterBackground:(NSNotification *)notification { + MLNAssertIsMainThread(); + MLNAssert(!self.dormant, @"Should not be dormant heading into background"); + MLNLogDebug(@"[%p] dormant=%d", self, self.dormant); + + // See comment in `supportsBackgroundRendering` above. + if (self.supportsBackgroundRendering) { + return; + } + + // We now want to stop rendering. + // We want to pause the rendering + [self stopDisplayLink]; + + // For OpenGL this calls glFinish as recommended in + // https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/ImplementingaMultitasking-awareOpenGLESApplication/ImplementingaMultitasking-awareOpenGLESApplication.html#//apple_ref/doc/uid/TP40008793-CH5-SW1 + // reduceMemoryUse(), calls performCleanup(), which calls glFinish + if (_rendererFrontend) { + _rendererFrontend->reduceMemoryUse(); + } + + // We now completely remove the display link, and the renderable resource. + // Although the method below is called `deleteView` this does NOT delete the + // GLKView, instead releasing the memory hungry resources. + [self destroyDisplayLink]; + [self processPendingBlocks]; + _mbglView->deleteView(); + + self.dormant = YES; + + // Handle non-rendering issues. + [self validateLocationServices]; +} + +- (void)willEnterForeground:(NSNotification *)notification { + MLNLogDebug(@"[%p] dormant=%d", self, self.dormant); + + // We're transitioning from Background to Inactive states + + if (self.supportsBackgroundRendering) { + return; + } + + // Reverse the process of going into the background + _mbglView->createView(); + + // A display link needs the window's screen, so create it if we can + UIScreen *screen = [self windowScreen]; + + if (screen) { + [self createDisplayLink]; + + // If we can render during the inactive state, start the display link now + if (self.isVisible) { + [self startDisplayLink]; + } + } + self.dormant = NO; + + [self validateLocationServices]; +} + +- (void)didBecomeActive:(NSNotification *)notification { + MLNLogDebug(@"[%p] DL.paused=<%p>.paused=%d", self, self.displayLink, self.displayLink.paused); + + // Most times, we should already have a display link created at this point, + // which may or may not be running. However, at the start of the application, + // it's possible to have a situation where the display link hasn't been created. + [self resumeRenderingIfNecessary]; } // MARK: - GL / display link wake/sleep - (EAGLContext *)context { - return _mbglView->getEAGLContext(); + return _mbglView->getEAGLContext(); } -- (BOOL)supportsBackgroundRendering -{ - // Note: The following comment may be out of date with Metal enabled builds where - // OpenGL is being emulated with Metal. - // - // If this view targets an external display, such as AirPlay or CarPlay, we - // can safely continue to render OpenGL content without tripping - // gpus_ReturnNotPermittedKillClient in libGPUSupportMercury, because the - // external connection keeps the application from truly receding to the - // background. - UIScreen *screen = [self windowScreen]; +- (BOOL)supportsBackgroundRendering { + // Note: The following comment may be out of date with Metal enabled builds where + // OpenGL is being emulated with Metal. + // + // If this view targets an external display, such as AirPlay or CarPlay, we + // can safely continue to render OpenGL content without tripping + // gpus_ReturnNotPermittedKillClient in libGPUSupportMercury, because the + // external connection keeps the application from truly receding to the + // background. + UIScreen *screen = [self windowScreen]; - BOOL supportsBackgroundRendering = (screen && (screen != [UIScreen mainScreen])); - MLNLogDebug(@"supportsBackgroundRendering=%d",supportsBackgroundRendering); - return supportsBackgroundRendering; + BOOL supportsBackgroundRendering = (screen && (screen != [UIScreen mainScreen])); + MLNLogDebug(@"supportsBackgroundRendering=%d", supportsBackgroundRendering); + return supportsBackgroundRendering; } - (void)resumeRenderingIfNecessary { - MLNLogDebug(@"[%p] DL.paused=<%p>.paused=%d", self, self.displayLink, self.displayLink.paused); + MLNLogDebug(@"[%p] DL.paused=<%p>.paused=%d", self, self.displayLink, self.displayLink.paused); - // Most times, we should already have a display link created at this point, - // which may or may not be running. However, at the start of the application, - // it's possible to have a situation where the display link hasn't been created. + // Most times, we should already have a display link created at this point, + // which may or may not be running. However, at the start of the application, + // it's possible to have a situation where the display link hasn't been created. - // Reverse the process of going into the background - if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { - if (self.dormant) { - _mbglView->createView(); - self.dormant = NO; - } - - // Check display link, if necessary - if (!self.displayLink) { - if ([self windowScreen]) { - [self createDisplayLink]; - } - } + // Reverse the process of going into the background + if ([UIApplication sharedApplication].applicationState != UIApplicationStateBackground) { + if (self.dormant) { + _mbglView->createView(); + self.dormant = NO; } - // Start the display link if we need to - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive || [UIApplication sharedApplication].applicationState == UIApplicationStateInactive) { - - BOOL mapViewVisible = self.isVisible; - if (self.displayLink) { - if (mapViewVisible && self.displayLink.isPaused) { - [self startDisplayLink]; - } - else if (!mapViewVisible && !self.displayLink.isPaused) { - // Unlikely scenario - [self stopDisplayLink]; - } - } + // Check display link, if necessary + if (!self.displayLink) { + if ([self windowScreen]) { + [self createDisplayLink]; + } } + } + + // Start the display link if we need to + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive || + [UIApplication sharedApplication].applicationState == UIApplicationStateInactive) { + BOOL mapViewVisible = self.isVisible; + if (self.displayLink) { + if (mapViewVisible && self.displayLink.isPaused) { + [self startDisplayLink]; + } else if (!mapViewVisible && !self.displayLink.isPaused) { + // Unlikely scenario + [self stopDisplayLink]; + } + } + } } - (BOOL)isDisplayLinkActive { - MLNLogDebug(@"[%p]", self); - return (self.displayLink && !self.displayLink.isPaused); + MLNLogDebug(@"[%p]", self); + return (self.displayLink && !self.displayLink.isPaused); } -- (void)setHidden:(BOOL)hidden -{ - super.hidden = hidden; - _displayLink.paused = ![self isVisible]; +- (void)setHidden:(BOOL)hidden { + super.hidden = hidden; + _displayLink.paused = ![self isVisible]; - if (hidden) - { - [self processPendingBlocks]; - } + if (hidden) { + [self processPendingBlocks]; + } } -- (void)tintColorDidChange -{ - for (UIView *subview in self.subviews) [self updateTintColorForView:subview]; +- (void)tintColorDidChange { + for (UIView *subview in self.subviews) [self updateTintColorForView:subview]; } -- (void)updateTintColorForView:(UIView *)view -{ - // Don't update: - // - annotation views - // - attribution button (handled automatically) - if ([view isEqual:self.annotationContainerView] || [view isEqual:self.attributionButton]) return; +- (void)updateTintColorForView:(UIView *)view { + // Don't update: + // - annotation views + // - attribution button (handled automatically) + if ([view isEqual:self.annotationContainerView] || [view isEqual:self.attributionButton]) return; - if ([view respondsToSelector:@selector(setTintColor:)]) view.tintColor = self.tintColor; + if ([view respondsToSelector:@selector(setTintColor:)]) view.tintColor = self.tintColor; - for (UIView *subview in view.subviews) [self updateTintColorForView:subview]; + for (UIView *subview in view.subviews) [self updateTintColorForView:subview]; } - (BOOL)canBecomeFirstResponder { - return YES; + return YES; } // MARK: - Gestures - -- (void)touchesBegan:(__unused NSSet *)touches withEvent:(__unused UIEvent *)event -{ - if (!self.zoomEnabled && !self.pitchEnabled && !self.rotateEnabled && !self.scrollEnabled) - { - return; - }; +- (void)touchesBegan:(__unused NSSet *)touches withEvent:(__unused UIEvent *)event { + if (!self.zoomEnabled && !self.pitchEnabled && !self.rotateEnabled && !self.scrollEnabled) { + return; + }; - self.mbglMap.setGestureInProgress(false); - if (self.userTrackingState == MLNUserTrackingStateBegan) - { - [self setUserTrackingMode:MLNUserTrackingModeNone animated:NO completionHandler:nil]; - } + self.mbglMap.setGestureInProgress(false); + if (self.userTrackingState == MLNUserTrackingStateBegan) { + [self setUserTrackingMode:MLNUserTrackingModeNone animated:NO completionHandler:nil]; + } - [self cancelTransitions]; + [self cancelTransitions]; } - (void)notifyGestureDidBegin { - BOOL animated = NO; + BOOL animated = NO; - [self cameraWillChangeAnimated:animated]; - self.mbglMap.setGestureInProgress(true); - _changeDelimiterSuppressionDepth++; + [self cameraWillChangeAnimated:animated]; + self.mbglMap.setGestureInProgress(true); + _changeDelimiterSuppressionDepth++; } - (void)notifyGestureDidEndWithDrift:(BOOL)drift { - _changeDelimiterSuppressionDepth--; - MLNAssert(_changeDelimiterSuppressionDepth >= 0, - @"Unbalanced change delimiter suppression/unsuppression"); - if (_changeDelimiterSuppressionDepth == 0) { - self.mbglMap.setGestureInProgress(false); - } - if ( ! drift) - { - BOOL animated = NO; - [self cameraDidChangeAnimated:animated]; - } + _changeDelimiterSuppressionDepth--; + MLNAssert(_changeDelimiterSuppressionDepth >= 0, + @"Unbalanced change delimiter suppression/unsuppression"); + if (_changeDelimiterSuppressionDepth == 0) { + self.mbglMap.setGestureInProgress(false); + } + if (!drift) { + BOOL animated = NO; + [self cameraDidChangeAnimated:animated]; + } } - (BOOL)isSuppressingChangeDelimiters { - return _changeDelimiterSuppressionDepth > 0; + return _changeDelimiterSuppressionDepth > 0; } -- (BOOL)_shouldChangeFromCamera:(nonnull MLNMapCamera *)oldCamera toCamera:(nonnull MLNMapCamera *)newCamera -{ - // Check delegates first - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)]) - { - return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask]; - } - else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]) - { - return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera]; - } - else - { - return YES; - } +- (BOOL)_shouldChangeFromCamera:(nonnull MLNMapCamera *)oldCamera + toCamera:(nonnull MLNMapCamera *)newCamera { + // Check delegates first + if ([self.delegate respondsToSelector:@selector(mapView: + shouldChangeFromCamera:toCamera:reason:)]) { + return [self.delegate mapView:self + shouldChangeFromCamera:oldCamera + toCamera:newCamera + reason:self.cameraChangeReasonBitmask]; + } else if ([self.delegate respondsToSelector:@selector(mapView: + shouldChangeFromCamera:toCamera:)]) { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera]; + } else { + return YES; + } } -- (void)handlePanGesture:(UIPanGestureRecognizer *)pan -{ - if ( ! self.isScrollEnabled) return; +- (void)handlePanGesture:(UIPanGestureRecognizer *)pan { + if (!self.isScrollEnabled) return; - [self cancelTransitions]; + [self cancelTransitions]; - MLNMapCamera *oldCamera = self.camera; + MLNMapCamera *oldCamera = self.camera; - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGesturePan; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGesturePan; - if (pan.state == UIGestureRecognizerStateBegan) - { - self.userTrackingMode = MLNUserTrackingModeNone; + if (pan.state == UIGestureRecognizerStateBegan) { + self.userTrackingMode = MLNUserTrackingModeNone; - [self notifyGestureDidBegin]; - } - else if (pan.state == UIGestureRecognizerStateChanged) - { - CGPoint delta = [pan translationInView:pan.view]; - MLNMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan]; + [self notifyGestureDidBegin]; + } else if (pan.state == UIGestureRecognizerStateChanged) { + CGPoint delta = [pan translationInView:pan.view]; + MLNMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan]; - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - switch(self.panScrollingMode){ - case MLNPanScrollingModeVertical: - self.mbglMap.moveBy({ 0, delta.y }); - break; - case MLNPanScrollingModeHorizontal: - self.mbglMap.moveBy({ delta.x, 0 }); - break; - default: - self.mbglMap.moveBy({ delta.x, delta.y }); - } + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + switch (self.panScrollingMode) { + case MLNPanScrollingModeVertical: + self.mbglMap.moveBy({0, delta.y}); + break; + case MLNPanScrollingModeHorizontal: + self.mbglMap.moveBy({delta.x, 0}); + break; + default: + self.mbglMap.moveBy({delta.x, delta.y}); + } - [pan setTranslation:CGPointZero inView:pan.view]; - } - - [self cameraIsChanging]; - } - else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) - { - CGPoint velocity = [pan velocityInView:pan.view]; - if (self.decelerationRate == MLNMapViewDecelerationRateImmediate || sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) < 100) - { - // Not enough velocity to overcome friction - velocity = CGPointZero; - } - - BOOL drift = ! CGPointEqualToPoint(velocity, CGPointZero); - if (drift) - { - CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4); - MLNMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - switch(self.panScrollingMode){ - case MLNPanScrollingModeVertical: - self.mbglMap.moveBy({ 0, offset.y }, MLNDurationFromTimeInterval(self.decelerationRate)); - break; - case MLNPanScrollingModeHorizontal: - self.mbglMap.moveBy({ offset.x, 0 }, MLNDurationFromTimeInterval(self.decelerationRate)); - break; - default: - self.mbglMap.moveBy({ offset.x, offset.y }, MLNDurationFromTimeInterval(self.decelerationRate)); - } - } - } - - [self notifyGestureDidEndWithDrift:drift]; + [pan setTranslation:CGPointZero inView:pan.view]; } + [self cameraIsChanging]; + } else if (pan.state == UIGestureRecognizerStateEnded || + pan.state == UIGestureRecognizerStateCancelled) { + CGPoint velocity = [pan velocityInView:pan.view]; + if (self.decelerationRate == MLNMapViewDecelerationRateImmediate || + sqrtf(velocity.x * velocity.x + velocity.y * velocity.y) < 100) { + // Not enough velocity to overcome friction + velocity = CGPointZero; + } + + BOOL drift = !CGPointEqualToPoint(velocity, CGPointZero); + if (drift) { + CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, + velocity.y * self.decelerationRate / 4); + MLNMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + switch (self.panScrollingMode) { + case MLNPanScrollingModeVertical: + self.mbglMap.moveBy({0, offset.y}, MLNDurationFromTimeInterval(self.decelerationRate)); + break; + case MLNPanScrollingModeHorizontal: + self.mbglMap.moveBy({offset.x, 0}, MLNDurationFromTimeInterval(self.decelerationRate)); + break; + default: + self.mbglMap.moveBy({offset.x, offset.y}, + MLNDurationFromTimeInterval(self.decelerationRate)); + } + } + } + + [self notifyGestureDidEndWithDrift:drift]; + } } -- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch -{ - if ( ! self.isZoomEnabled) return; +- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch { + if (!self.isZoomEnabled) return; - [self cancelTransitions]; + [self cancelTransitions]; - CGPoint centerPoint = [self anchorPointForGesture:pinch]; + CGPoint centerPoint = [self anchorPointForGesture:pinch]; + if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { + if (pinch.numberOfTouches != 1 || pinch.state == UIGestureRecognizerStateEnded) { + centerPoint = [self contentCenter]; + } + } + MLNMapCamera *oldCamera = self.camera; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGesturePinch; + + if (pinch.state == UIGestureRecognizerStateBegan) { + self.scale = powf(2, [self zoomLevel]); + + if (abs(pinch.velocity) > abs(self.rotate.velocity)) { + self.isZooming = YES; + } + [self notifyGestureDidBegin]; + } else if (pinch.state == UIGestureRecognizerStateChanged) { + // Zoom limiting happens at the core level. + CGFloat newScale = self.scale * pinch.scale; + double newZoom = log2(newScale); + + // Calculates the final camera zoom, has no effect within current map camera. + MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom + aroundAnchorPoint:centerPoint]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor( + mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y})); + + // The gesture recognizer only reports the gesture’s current center + // point, so use the previous center point to anchor the transition. + // If the number of touches has changed, the remembered center point is + // meaningless. + if (self.userTrackingMode == MLNUserTrackingModeNone && + pinch.numberOfTouches == _previousPinchNumberOfTouches) { + self.mbglMap.moveBy({centerPoint.x - _previousPinchCenterPoint.x, + centerPoint.y - _previousPinchCenterPoint.y}); + } + } + [self cameraIsChanging]; + } else if (pinch.state == UIGestureRecognizerStateEnded || + pinch.state == UIGestureRecognizerStateCancelled) { + CGFloat velocity = pinch.velocity; + if (isnan(velocity)) { + // UIPinchGestureRecognizer sometimes returns NaN for the velocity + velocity = 0; + } + if (velocity > -0.5 && velocity < 3) { + velocity = 0; + } + + NSTimeInterval duration = (velocity > 0 ? 1 : 0.25) * self.decelerationRate; + + CGFloat scale = self.scale * pinch.scale; + CGFloat newScale = scale; + if (velocity >= 0) { + newScale += scale * velocity * duration * 0.1; + } else { + newScale += scale / (velocity * duration) * 0.1; + } + + if (newScale <= 0 || log2(newScale) < *self.mbglMap.getBounds().minZoom) { + velocity = 0; + } + + BOOL drift = velocity && duration; + + // Calculates the final camera zoom, this has no effect within current map camera. + double zoom = log2(newScale); + MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; + + if (![self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + drift = NO; + } else { + if (drift) { + self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(zoom).withAnchor( + mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y}), + MLNDurationFromTimeInterval(duration)); + } + } + + self.isZooming = NO; + [self notifyGestureDidEndWithDrift:drift]; + [self unrotateIfNeededForGesture]; + } + + _previousPinchCenterPoint = centerPoint; + _previousPinchNumberOfTouches = pinch.numberOfTouches; +} + +- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate { + if (!self.isRotateEnabled) return; + + [self cancelTransitions]; + + CGPoint centerPoint = [self anchorPointForGesture:rotate]; + if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { + centerPoint = [self contentCenter]; + } + MLNMapCamera *oldCamera = self.camera; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureRotate; + + if ([[NSUserDefaults standardUserDefaults] objectForKey:MLNRotationThresholdWhileZoomingKey]) { + self.rotationThresholdWhileZooming = [[[NSUserDefaults standardUserDefaults] + objectForKey:MLNRotationThresholdWhileZoomingKey] floatValue]; + } + // Check whether a zoom triggered by a pinch gesture is occurring and if the rotation threshold + // has been met. + if (MLNDegreesFromRadians(self.rotationBeforeThresholdMet) < self.rotationThresholdWhileZooming && + self.isZooming && !self.isRotating) { + self.rotationBeforeThresholdMet += fabs(rotate.rotation); if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { - if (pinch.numberOfTouches != 1 || pinch.state == UIGestureRecognizerStateEnded) { - centerPoint = [self contentCenter]; - } + self.rotationBeforeThresholdMet = 0; } - MLNMapCamera *oldCamera = self.camera; + rotate.rotation = 0; + return; + } - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGesturePinch; + if (rotate.state == UIGestureRecognizerStateBegan || !self.isRotating) { + self.angle = MLNRadiansFromDegrees(*self.mbglMap.getCameraOptions().bearing) * -1; - if (pinch.state == UIGestureRecognizerStateBegan) - { - self.scale = powf(2, [self zoomLevel]); - - if (abs(pinch.velocity) > abs(self.rotate.velocity)) { - self.isZooming = YES; - } - [self notifyGestureDidBegin]; - } - else if (pinch.state == UIGestureRecognizerStateChanged) - { - // Zoom limiting happens at the core level. - CGFloat newScale = self.scale * pinch.scale; - double newZoom = log2(newScale); - - // Calculates the final camera zoom, has no effect within current map camera. - MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withZoom(newZoom) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); - - // The gesture recognizer only reports the gesture’s current center - // point, so use the previous center point to anchor the transition. - // If the number of touches has changed, the remembered center point is - // meaningless. - if (self.userTrackingMode == MLNUserTrackingModeNone && pinch.numberOfTouches == _previousPinchNumberOfTouches) - { - self.mbglMap.moveBy({centerPoint.x - _previousPinchCenterPoint.x, centerPoint.y - _previousPinchCenterPoint.y}); - } - } - [self cameraIsChanging]; - } - else if (pinch.state == UIGestureRecognizerStateEnded || pinch.state == UIGestureRecognizerStateCancelled) - { - CGFloat velocity = pinch.velocity; - if (isnan(velocity)) - { - // UIPinchGestureRecognizer sometimes returns NaN for the velocity - velocity = 0; - } - if (velocity > -0.5 && velocity < 3) - { - velocity = 0; - } - - NSTimeInterval duration = (velocity > 0 ? 1 : 0.25) * self.decelerationRate; - - CGFloat scale = self.scale * pinch.scale; - CGFloat newScale = scale; - if (velocity >= 0) - { - newScale += scale * velocity * duration * 0.1; - } - else - { - newScale += scale / (velocity * duration) * 0.1; - } - - if (newScale <= 0 || log2(newScale) < *self.mbglMap.getBounds().minZoom) - { - velocity = 0; - } - - BOOL drift = velocity && duration; - - // Calculates the final camera zoom, this has no effect within current map camera. - double zoom = log2(newScale); - MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; - - if ( ! [self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - drift = NO; - } - else - { - if (drift) - { - self.mbglMap.easeTo(mbgl::CameraOptions() - .withZoom(zoom) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), - MLNDurationFromTimeInterval(duration)); - } - } - - self.isZooming = NO; - [self notifyGestureDidEndWithDrift:drift]; - [self unrotateIfNeededForGesture]; + self.isRotating = YES; + if (self.userTrackingMode != MLNUserTrackingModeNone) { + self.userTrackingMode = MLNUserTrackingModeFollow; } - _previousPinchCenterPoint = centerPoint; - _previousPinchNumberOfTouches = pinch.numberOfTouches; -} + self.shouldTriggerHapticFeedbackForCompass = NO; + [self notifyGestureDidBegin]; + } + if (rotate.state == UIGestureRecognizerStateChanged) { + CGFloat newDegrees = MLNDegreesFromRadians(self.angle + rotate.rotation) * -1; -- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate -{ - if ( ! self.isRotateEnabled) return; - - [self cancelTransitions]; - - CGPoint centerPoint = [self anchorPointForGesture:rotate]; - if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { - centerPoint = [self contentCenter]; - } - MLNMapCamera *oldCamera = self.camera; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureRotate; - - if ([[NSUserDefaults standardUserDefaults] objectForKey:MLNRotationThresholdWhileZoomingKey]) { - self.rotationThresholdWhileZooming = [[[NSUserDefaults standardUserDefaults] objectForKey:MLNRotationThresholdWhileZoomingKey] floatValue]; - } - // Check whether a zoom triggered by a pinch gesture is occurring and if the rotation threshold has been met. - if (MLNDegreesFromRadians(self.rotationBeforeThresholdMet) < self.rotationThresholdWhileZooming && self.isZooming && !self.isRotating) { - self.rotationBeforeThresholdMet += fabs(rotate.rotation); - if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { - self.rotationBeforeThresholdMet = 0; - } - rotate.rotation = 0; - return; + // constrain to +/-30 degrees when merely rotating like Apple does + // + if (!self.isRotationAllowed && std::abs(self.pinch.scale) < 10) { + newDegrees = fminf(newDegrees, 30); + newDegrees = fmaxf(newDegrees, -30); } - if (rotate.state == UIGestureRecognizerStateBegan || ! self.isRotating) - { - self.angle = MLNRadiansFromDegrees(*self.mbglMap.getCameraOptions().bearing) * -1; + MLNMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees + aroundAnchorPoint:centerPoint]; - self.isRotating = YES; - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - self.userTrackingMode = MLNUserTrackingModeFollow; - } + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + self.mbglMap.jumpTo(mbgl::CameraOptions() + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y})); + } + + [self cameraIsChanging]; + + // Trigger a light haptic feedback event when the user rotates to due north. + if (@available(iOS 10.0, *)) { + if (self.isHapticFeedbackEnabled && fabs(newDegrees) <= 1 && + self.shouldTriggerHapticFeedbackForCompass) { + UIImpactFeedbackGenerator *hapticFeedback = + [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; + [hapticFeedback impactOccurred]; self.shouldTriggerHapticFeedbackForCompass = NO; - [self notifyGestureDidBegin]; + } else if (fabs(newDegrees) > 1) { + self.shouldTriggerHapticFeedbackForCompass = YES; + } } - if (rotate.state == UIGestureRecognizerStateChanged) - { - CGFloat newDegrees = MLNDegreesFromRadians(self.angle + rotate.rotation) * -1; - - // constrain to +/-30 degrees when merely rotating like Apple does - // - if ( ! self.isRotationAllowed && std::abs(self.pinch.scale) < 10) - { - newDegrees = fminf(newDegrees, 30); - newDegrees = fmaxf(newDegrees, -30); - } - - MLNMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y})); - } - - [self cameraIsChanging]; - - // Trigger a light haptic feedback event when the user rotates to due north. - if (@available(iOS 10.0, *)) - { - if (self.isHapticFeedbackEnabled && fabs(newDegrees) <= 1 && self.shouldTriggerHapticFeedbackForCompass) - { - UIImpactFeedbackGenerator *hapticFeedback = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight]; - [hapticFeedback impactOccurred]; - - self.shouldTriggerHapticFeedbackForCompass = NO; - } - else if (fabs(newDegrees) > 1) - { - self.shouldTriggerHapticFeedbackForCompass = YES; - } - } + } else if ((rotate.state == UIGestureRecognizerStateEnded || + rotate.state == UIGestureRecognizerStateCancelled)) { + self.rotationBeforeThresholdMet = 0; + if (!self.isRotating) { + return; } - else if ((rotate.state == UIGestureRecognizerStateEnded || rotate.state == UIGestureRecognizerStateCancelled)) - { - self.rotationBeforeThresholdMet = 0; - if (! self.isRotating) { return; } - self.isRotating = NO; + self.isRotating = NO; - CGFloat velocity = rotate.velocity; - CGFloat decelerationRate = self.decelerationRate; - if (decelerationRate != MLNMapViewDecelerationRateImmediate && fabs(velocity) > 3) - { - CGFloat radians = self.angle + rotate.rotation; - CGFloat newRadians = radians + velocity * decelerationRate * 0.1; - CGFloat newDegrees = MLNDegreesFromRadians(newRadians) * -1; + CGFloat velocity = rotate.velocity; + CGFloat decelerationRate = self.decelerationRate; + if (decelerationRate != MLNMapViewDecelerationRateImmediate && fabs(velocity) > 3) { + CGFloat radians = self.angle + rotate.rotation; + CGFloat newRadians = radians + velocity * decelerationRate * 0.1; + CGFloat newDegrees = MLNDegreesFromRadians(newRadians) * -1; - MLNMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; + MLNMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees + aroundAnchorPoint:centerPoint]; - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.easeTo(mbgl::CameraOptions() - .withBearing(newDegrees) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }), - MLNDurationFromTimeInterval(decelerationRate)); + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + self.mbglMap.easeTo(mbgl::CameraOptions() + .withBearing(newDegrees) + .withAnchor(mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y}), + MLNDurationFromTimeInterval(decelerationRate)); - [self notifyGestureDidEndWithDrift:YES]; - __weak MLNMapView *weakSelf = self; + [self notifyGestureDidEndWithDrift:YES]; + __weak MLNMapView *weakSelf = self; - [self animateWithDelay:decelerationRate animations:^ - { - [weakSelf unrotateIfNeededForGesture]; - }]; - } - } - else - { - [self notifyGestureDidEndWithDrift:NO]; - [self unrotateIfNeededForGesture]; - } + [self animateWithDelay:decelerationRate + animations:^{ + [weakSelf unrotateIfNeededForGesture]; + }]; + } + } else { + [self notifyGestureDidEndWithDrift:NO]; + [self unrotateIfNeededForGesture]; } + } } -- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap -{ - if (singleTap.state != UIGestureRecognizerStateRecognized) return; +- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap { + if (singleTap.state != UIGestureRecognizerStateRecognized) return; - if (self.mapViewProxyAccessibilityElement.accessibilityElementIsFocused) - { - id nextElement; - if (_userLocationAnnotationIsSelected) - { - nextElement = self.userLocationAnnotationView; - } - else - { - if (_selectedAnnotationTag != MLNAnnotationTagNotFound) { - nextElement = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag).accessibilityElement; - } - } - [self deselectAnnotation:self.selectedAnnotation animated:YES]; - UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement); + if (self.mapViewProxyAccessibilityElement.accessibilityElementIsFocused) { + id nextElement; + if (_userLocationAnnotationIsSelected) { + nextElement = self.userLocationAnnotationView; + } else { + if (_selectedAnnotationTag != MLNAnnotationTagNotFound) { + nextElement = + _annotationContextsByAnnotationTag.at(_selectedAnnotationTag).accessibilityElement; + } + } + [self deselectAnnotation:self.selectedAnnotation animated:YES]; + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement); - return; - } + return; + } - id annotation = [self annotationForGestureRecognizer:singleTap persistingResults:YES]; - if (annotation) - { - CGPoint calloutPoint = [singleTap locationInView:self]; - CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint]; - [self selectAnnotation:annotation moveIntoView:YES animateSelection:YES calloutPositioningRect:positionRect completionHandler:nil]; - } - else if (self.selectedAnnotation) - { - [self deselectAnnotation:self.selectedAnnotation animated:YES]; - } + id annotation = [self annotationForGestureRecognizer:singleTap + persistingResults:YES]; + if (annotation) { + CGPoint calloutPoint = [singleTap locationInView:self]; + CGRect positionRect = [self positioningRectForAnnotation:annotation + defaultCalloutPoint:calloutPoint]; + [self selectAnnotation:annotation + moveIntoView:YES + animateSelection:YES + calloutPositioningRect:positionRect + completionHandler:nil]; + } else if (self.selectedAnnotation) { + [self deselectAnnotation:self.selectedAnnotation animated:YES]; + } } /** @@ -2536,1022 +2495,961 @@ public: This is used when a gesture is recognized, and to check if the gesture should be recognized. @param singleTap An in progress tap gesture recognizer. - @param persist True to remember the cycleable set of annotations. @see annotationTagAtPoint:persistingResults + @param persist True to remember the cycleable set of annotations. @see + annotationTagAtPoint:persistingResults */ -- (nullable id )annotationForGestureRecognizer:(UITapGestureRecognizer*)singleTap persistingResults:(BOOL)persist -{ - CGPoint tapPoint = [singleTap locationInView:self]; +- (nullable id)annotationForGestureRecognizer:(UITapGestureRecognizer *)singleTap + persistingResults:(BOOL)persist { + CGPoint tapPoint = [singleTap locationInView:self]; - if (self.userLocationVisible) - { - CGPoint tapPointForUserLocation; - if (self.userLocationAnnotationView.hitTestLayer == self.userLocationAnnotationView.layer.presentationLayer) - { - tapPointForUserLocation = tapPoint; - } - else - { - // Get the tap point within the custom hit test layer. - tapPointForUserLocation = [singleTap locationInView:self.userLocationAnnotationView]; - } - - CALayer *hitLayer = [self.userLocationAnnotationView.hitTestLayer hitTest:tapPointForUserLocation]; - - if (hitLayer) - { - if ( ! _userLocationAnnotationIsSelected) - { - return self.userLocation; - } - return nil; - } + if (self.userLocationVisible) { + CGPoint tapPointForUserLocation; + if (self.userLocationAnnotationView.hitTestLayer == + self.userLocationAnnotationView.layer.presentationLayer) { + tapPointForUserLocation = tapPoint; + } else { + // Get the tap point within the custom hit test layer. + tapPointForUserLocation = [singleTap locationInView:self.userLocationAnnotationView]; } - MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint persistingResults:persist]; - if (hitAnnotationTag != MLNAnnotationTagNotFound) - { - if (hitAnnotationTag != _selectedAnnotationTag) - { - id annotation = [self annotationWithTag:hitAnnotationTag]; - MLNAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", hitAnnotationTag); - return annotation; - } - } + CALayer *hitLayer = + [self.userLocationAnnotationView.hitTestLayer hitTest:tapPointForUserLocation]; - return nil; + if (hitLayer) { + if (!_userLocationAnnotationIsSelected) { + return self.userLocation; + } + return nil; + } + } + + MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:tapPoint + persistingResults:persist]; + if (hitAnnotationTag != MLNAnnotationTagNotFound) { + if (hitAnnotationTag != _selectedAnnotationTag) { + id annotation = [self annotationWithTag:hitAnnotationTag]; + MLNAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", + hitAnnotationTag); + return annotation; + } + } + + return nil; } -- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap -{ - if (doubleTap.state != UIGestureRecognizerStateRecognized) return; +- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap { + if (doubleTap.state != UIGestureRecognizerStateRecognized) return; - if ( ! self.isZoomEnabled) return; + if (!self.isZoomEnabled) return; - [self cancelTransitions]; + [self cancelTransitions]; - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureZoomIn; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureZoomIn; + + MLNMapCamera *oldCamera = self.camera; + + double newZoom = round(self.zoomLevel) + 1.0; + + CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; + + MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); + self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), + MLNDurationFromTimeInterval(MLNAnimationDuration)); + + __weak MLNMapView *weakSelf = self; + + [self animateWithDelay:MLNAnimationDuration + animations:^{ + [weakSelf unrotateIfNeededForGesture]; + }]; + } else { + [self unrotateIfNeededForGesture]; + } +} + +- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap { + if (twoFingerTap.state != UIGestureRecognizerStateRecognized) return; + + if (!self.isZoomEnabled) return; + + if ([self zoomLevel] == *self.mbglMap.getBounds().minZoom) return; + + [self cancelTransitions]; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureZoomOut; + + MLNMapCamera *oldCamera = self.camera; + + double newZoom = round(self.zoomLevel) - 1.0; + + CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; + + MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); + self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor(center), + MLNDurationFromTimeInterval(MLNAnimationDuration)); + + __weak MLNMapView *weakSelf = self; + + [self animateWithDelay:MLNAnimationDuration + animations:^{ + [weakSelf unrotateIfNeededForGesture]; + }]; + } +} + +- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom { + if (!self.isZoomEnabled) return; + + [self cancelTransitions]; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureOneFingerZoom; + + if (quickZoom.state == UIGestureRecognizerStateBegan) { + self.scale = powf(2, [self zoomLevel]); + + self.quickZoomStart = [quickZoom locationInView:quickZoom.view].y; + + [self notifyGestureDidBegin]; + } else if (quickZoom.state == UIGestureRecognizerStateChanged) { + CGFloat distance = [quickZoom locationInView:quickZoom.view].y - self.quickZoomStart; + + if (self.isQuickZoomReversed) distance = -distance; + + CGFloat newZoom = MAX(log2f(self.scale) + (distance / 75), *self.mbglMap.getBounds().minZoom); + + if ([self zoomLevel] == newZoom) return; + + CGPoint centerPoint = [self anchorPointForGesture:quickZoom]; MLNMapCamera *oldCamera = self.camera; + MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom + aroundAnchorPoint:centerPoint]; - double newZoom = round(self.zoomLevel) + 1.0; - - CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; - - MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); - self.mbglMap.easeTo(mbgl::CameraOptions() - .withZoom(newZoom) - .withAnchor(center), - MLNDurationFromTimeInterval(MLNAnimationDuration)); - - __weak MLNMapView *weakSelf = self; - - [self animateWithDelay:MLNAnimationDuration animations:^ - { - [weakSelf unrotateIfNeededForGesture]; - }]; - } - else - { - [self unrotateIfNeededForGesture]; + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor( + mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y})); } + + [self cameraIsChanging]; + } else if (quickZoom.state == UIGestureRecognizerStateEnded || + quickZoom.state == UIGestureRecognizerStateCancelled) { + [self notifyGestureDidEndWithDrift:NO]; + [self unrotateIfNeededForGesture]; + } } -- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap -{ - if (twoFingerTap.state != UIGestureRecognizerStateRecognized) return; +- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag { + if (!self.isPitchEnabled) return; - if ( ! self.isZoomEnabled) return; + [self cancelTransitions]; - if ([self zoomLevel] == *self.mbglMap.getBounds().minZoom) return; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureTilt; + static CGFloat initialPitch; - [self cancelTransitions]; + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) { + CGPoint midPoint = [twoFingerDrag translationInView:twoFingerDrag.view]; + // In the following if and for the first execution middlePoint + // will be equal to dragGestureMiddlePoint and the resulting + // gestureSlopeAngle will be 0º causing a small delay, + // initializing dragGestureMiddlePoint with the current midPoint + // but substracting one point from 'y' forces an initial 90º angle + // making the gesture avoid the delay + self.dragGestureMiddlePoint = CGPointMake(midPoint.x, midPoint.y - 1); + initialPitch = *self.mbglMap.getCameraOptions().pitch; + [self notifyGestureDidBegin]; + } - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureZoomOut; - - MLNMapCamera *oldCamera = self.camera; - - double newZoom = round(self.zoomLevel) - 1.0; - - CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; - - MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); - self.mbglMap.easeTo(mbgl::CameraOptions() - .withZoom(newZoom) - .withAnchor(center), - MLNDurationFromTimeInterval(MLNAnimationDuration)); - - __weak MLNMapView *weakSelf = self; - - [self animateWithDelay:MLNAnimationDuration animations:^ - { - [weakSelf unrotateIfNeededForGesture]; - }]; + if (twoFingerDrag.state == UIGestureRecognizerStateBegan || + twoFingerDrag.state == UIGestureRecognizerStateChanged) { + if (twoFingerDrag.numberOfTouches != 2) { + twoFingerDrag.state = UIGestureRecognizerStateEnded; + return; } + + CGPoint leftTouchPoint = [twoFingerDrag locationOfTouch:0 inView:twoFingerDrag.view]; + CGPoint rightTouchPoint = [twoFingerDrag locationOfTouch:1 inView:twoFingerDrag.view]; + CLLocationDegrees fingerSlopeAngle = [self angleBetweenPoints:leftTouchPoint + endPoint:rightTouchPoint]; + + CGPoint middlePoint = [twoFingerDrag translationInView:twoFingerDrag.view]; + + CLLocationDegrees gestureSlopeAngle = [self angleBetweenPoints:self.dragGestureMiddlePoint + endPoint:middlePoint]; + self.dragGestureMiddlePoint = middlePoint; + if (fabs(fingerSlopeAngle) < MLNHorizontalTiltToleranceDegrees && + fabs(gestureSlopeAngle) > 60.0) { + CGFloat gestureDistance = middlePoint.y; + CGFloat slowdown = 2.0; + + CGFloat pitchNew = initialPitch - (gestureDistance / slowdown); + + CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag]; + + MLNMapCamera *oldCamera = self.camera; + MLNMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { + self.mbglMap.jumpTo(mbgl::CameraOptions().withPitch(pitchNew).withAnchor( + mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y})); + } + + [self cameraIsChanging]; + } + + } else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || + twoFingerDrag.state == UIGestureRecognizerStateCancelled) { + [self notifyGestureDidEndWithDrift:NO]; + [self unrotateIfNeededForGesture]; + self.dragGestureMiddlePoint = CGPointZero; + } } -- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom -{ - if ( ! self.isZoomEnabled) return; +- (MLNMapCamera *)cameraByPanningWithTranslation:(CGPoint)endPoint + panGesture:(UIPanGestureRecognizer *)pan { + MLNMapCamera *panCamera = [self.camera copy]; - [self cancelTransitions]; + CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); + CGPoint endCameraPoint = CGPointMake(centerPoint.x - endPoint.x, centerPoint.y - endPoint.y); + CLLocationCoordinate2D panCoordinate = [self convertPoint:endCameraPoint + toCoordinateFromView:pan.view]; - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureOneFingerZoom; + panCamera.centerCoordinate = panCoordinate; - if (quickZoom.state == UIGestureRecognizerStateBegan) - { - self.scale = powf(2, [self zoomLevel]); - - self.quickZoomStart = [quickZoom locationInView:quickZoom.view].y; - - [self notifyGestureDidBegin]; - } - else if (quickZoom.state == UIGestureRecognizerStateChanged) - { - CGFloat distance = [quickZoom locationInView:quickZoom.view].y - self.quickZoomStart; - - if (self.isQuickZoomReversed) distance = - distance; - - CGFloat newZoom = MAX(log2f(self.scale) + (distance / 75), *self.mbglMap.getBounds().minZoom); - - if ([self zoomLevel] == newZoom) return; - - CGPoint centerPoint = [self anchorPointForGesture:quickZoom]; - - MLNMapCamera *oldCamera = self.camera; - MLNMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withZoom(newZoom) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); - } - - [self cameraIsChanging]; - } - else if (quickZoom.state == UIGestureRecognizerStateEnded || quickZoom.state == UIGestureRecognizerStateCancelled) - { - [self notifyGestureDidEndWithDrift:NO]; - [self unrotateIfNeededForGesture]; - } + return panCamera; } -- (void)handleTwoFingerDragGesture:(UIPanGestureRecognizer *)twoFingerDrag -{ - if ( ! self.isPitchEnabled) return; +- (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint { + mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate{anchorPoint.x, anchorPoint.y}; + mbgl::EdgeInsets padding = + mbgl::EdgeInsets(anchor.y, anchor.x, self.size.height - anchor.y, self.size.width - anchor.x); + mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(padding); - [self cancelTransitions]; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonGestureTilt; - static CGFloat initialPitch; - - if (twoFingerDrag.state == UIGestureRecognizerStateBegan) - { - CGPoint midPoint = [twoFingerDrag translationInView:twoFingerDrag.view]; - // In the following if and for the first execution middlePoint - // will be equal to dragGestureMiddlePoint and the resulting - // gestureSlopeAngle will be 0º causing a small delay, - // initializing dragGestureMiddlePoint with the current midPoint - // but substracting one point from 'y' forces an initial 90º angle - // making the gesture avoid the delay - self.dragGestureMiddlePoint = CGPointMake(midPoint.x, midPoint.y-1); - initialPitch = *self.mbglMap.getCameraOptions().pitch; - [self notifyGestureDidBegin]; - } - - if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged) - { - if (twoFingerDrag.numberOfTouches != 2) - { - twoFingerDrag.state = UIGestureRecognizerStateEnded; - return; - } - - CGPoint leftTouchPoint = [twoFingerDrag locationOfTouch:0 inView:twoFingerDrag.view]; - CGPoint rightTouchPoint = [twoFingerDrag locationOfTouch:1 inView:twoFingerDrag.view]; - CLLocationDegrees fingerSlopeAngle = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint]; - - CGPoint middlePoint = [twoFingerDrag translationInView:twoFingerDrag.view]; - - CLLocationDegrees gestureSlopeAngle = [self angleBetweenPoints:self.dragGestureMiddlePoint endPoint:middlePoint]; - self.dragGestureMiddlePoint = middlePoint; - if (fabs(fingerSlopeAngle) < MLNHorizontalTiltToleranceDegrees && fabs(gestureSlopeAngle) > 60.0 ) { - - CGFloat gestureDistance = middlePoint.y; - CGFloat slowdown = 2.0; - - CGFloat pitchNew = initialPitch - (gestureDistance / slowdown); - - CGPoint centerPoint = [self anchorPointForGesture:twoFingerDrag]; - - MLNMapCamera *oldCamera = self.camera; - MLNMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; - - if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) - { - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withPitch(pitchNew) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); - } - - [self cameraIsChanging]; - - } - - - } - else if (twoFingerDrag.state == UIGestureRecognizerStateEnded || twoFingerDrag.state == UIGestureRecognizerStateCancelled) - { - [self notifyGestureDidEndWithDrift:NO]; - [self unrotateIfNeededForGesture]; - self.dragGestureMiddlePoint = CGPointZero; - } + currentCameraOptions.zoom = mbgl::util::clamp(zoom, self.minimumZoomLevel, self.maximumZoomLevel); + currentCameraOptions.anchor = anchor; + MLNCoordinateBounds bounds = + MLNCoordinateBoundsFromLatLngBounds(self.mbglMap.latLngBoundsForCamera(currentCameraOptions)); + return [self cameraThatFitsCoordinateBounds:bounds]; } -- (MLNMapCamera *)cameraByPanningWithTranslation:(CGPoint)endPoint panGesture:(UIPanGestureRecognizer *)pan -{ - MLNMapCamera *panCamera = [self.camera copy]; +- (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees + aroundAnchorPoint:(CGPoint)anchorPoint { + mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(); - CGPoint centerPoint = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); - CGPoint endCameraPoint = CGPointMake(centerPoint.x - endPoint.x, centerPoint.y - endPoint.y); - CLLocationCoordinate2D panCoordinate = [self convertPoint:endCameraPoint toCoordinateFromView:pan.view]; + MLNMapCamera *camera; - panCamera.centerCoordinate = panCoordinate; + mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate{anchorPoint.x, anchorPoint.y}; + currentCameraOptions.bearing = degrees; + currentCameraOptions.anchor = anchor; + camera = [self cameraForCameraOptions:currentCameraOptions]; - return panCamera; + return camera; } -- (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint -{ - mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y }; - mbgl::EdgeInsets padding = mbgl::EdgeInsets(anchor.y, anchor.x, self.size.height - anchor.y, self.size.width - anchor.x); - mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(padding); +- (MLNMapCamera *)cameraByTiltingToPitch:(CGFloat)pitch { + mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(); - currentCameraOptions.zoom = mbgl::util::clamp(zoom, self.minimumZoomLevel, self.maximumZoomLevel); - currentCameraOptions.anchor = anchor; - MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(self.mbglMap.latLngBoundsForCamera(currentCameraOptions)); + MLNMapCamera *camera; - return [self cameraThatFitsCoordinateBounds:bounds]; -} + currentCameraOptions.pitch = pitch; + camera = [self cameraForCameraOptions:currentCameraOptions]; -- (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint -{ - mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(); - - MLNMapCamera *camera; - - mbgl::ScreenCoordinate anchor = mbgl::ScreenCoordinate { anchorPoint.x, anchorPoint.y }; - currentCameraOptions.bearing = degrees; - currentCameraOptions.anchor = anchor; - camera = [self cameraForCameraOptions:currentCameraOptions]; - - return camera; -} - -- (MLNMapCamera *)cameraByTiltingToPitch:(CGFloat)pitch -{ - mbgl::CameraOptions currentCameraOptions = self.mbglMap.getCameraOptions(); - - MLNMapCamera *camera; - - currentCameraOptions.pitch = pitch; - camera = [self cameraForCameraOptions:currentCameraOptions]; - - return camera; + return camera; } - (CGPoint)anchorPointForGesture:(UIGestureRecognizer *)gesture { - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - return self.userLocationAnnotationViewCenter; - } + if (self.userTrackingMode != MLNUserTrackingModeNone) { + return self.userLocationAnnotationViewCenter; + } - // Special case for two-finger drag and quickzoom - if ([gesture isKindOfClass:[UIPanGestureRecognizer class]] || [gesture isKindOfClass:[UILongPressGestureRecognizer class]]) - { - return self.contentCenter; - } + // Special case for two-finger drag and quickzoom + if ([gesture isKindOfClass:[UIPanGestureRecognizer class]] || + [gesture isKindOfClass:[UILongPressGestureRecognizer class]]) { + return self.contentCenter; + } - return [gesture locationInView:gesture.view]; + return [gesture locationInView:gesture.view]; } -- (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap -{ - if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)]) - { - MLNAssert([tap.view isKindOfClass:[UIControl class]], @"Tapped view %@ is not a UIControl", tap.view); - id selectedAnnotation = self.selectedAnnotation; - MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); - [self.delegate mapView:self annotation:selectedAnnotation - calloutAccessoryControlTapped:(UIControl *)tap.view]; - } +- (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap { + if ([self.delegate respondsToSelector:@selector(mapView: + annotation:calloutAccessoryControlTapped:)]) { + MLNAssert([tap.view isKindOfClass:[UIControl class]], @"Tapped view %@ is not a UIControl", + tap.view); + id selectedAnnotation = self.selectedAnnotation; + MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); + [self.delegate mapView:self + annotation:selectedAnnotation + calloutAccessoryControlTapped:(UIControl *)tap.view]; + } } -- (BOOL)calloutViewShouldHighlight:(__unused MLNCompactCalloutView *)calloutView -{ - return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]; +- (BOOL)calloutViewShouldHighlight:(__unused MLNCompactCalloutView *)calloutView { + return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]; } -- (void)calloutViewClicked:(__unused MLNSMCalloutView *)calloutView -{ - if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]) - { - id selectedAnnotation = self.selectedAnnotation; - MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); - [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation]; - } +- (void)calloutViewClicked:(__unused MLNSMCalloutView *)calloutView { + if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]) { + id selectedAnnotation = self.selectedAnnotation; + MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); + [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation]; + } } -- (void)calloutViewTapped:(__unused MLNCompactCalloutView *)calloutView -{ - if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]) - { - id selectedAnnotation = self.selectedAnnotation; - MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); - [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation]; - } +- (void)calloutViewTapped:(__unused MLNCompactCalloutView *)calloutView { + if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)]) { + id selectedAnnotation = self.selectedAnnotation; + MLNAssert(selectedAnnotation, @"Selected annotation should not be nil."); + [self.delegate mapView:self tapOnCalloutForAnnotation:selectedAnnotation]; + } } -- (void)calloutViewDidAppear:(UIView *)calloutView -{ - UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, calloutView); +- (void)calloutViewDidAppear:(UIView *)calloutView { + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil); + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, calloutView); - [self updatePresentsWithTransaction]; + [self updatePresentsWithTransaction]; - // TODO: Add sibling disappear method + // TODO: Add sibling disappear method } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - if (gestureRecognizer == _twoFingerDrag) - { - UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + if (gestureRecognizer == _twoFingerDrag) { + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; - if (panGesture.minimumNumberOfTouches == 2) - { - CGPoint leftTouchPoint = [panGesture locationOfTouch:0 inView:panGesture.view]; - CGPoint rightTouchPoint = [panGesture locationOfTouch:1 inView:panGesture.view]; + if (panGesture.minimumNumberOfTouches == 2) { + CGPoint leftTouchPoint = [panGesture locationOfTouch:0 inView:panGesture.view]; + CGPoint rightTouchPoint = [panGesture locationOfTouch:1 inView:panGesture.view]; - CLLocationDegrees degrees = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint]; - if (fabs(degrees) > MLNHorizontalTiltToleranceDegrees) { - return NO; - } - } + CLLocationDegrees degrees = [self angleBetweenPoints:leftTouchPoint endPoint:rightTouchPoint]; + if (fabs(degrees) > MLNHorizontalTiltToleranceDegrees) { + return NO; + } } - else if (gestureRecognizer == _singleTapGestureRecognizer) - { - // Gesture will be recognized if it could deselect an annotation - if(!self.selectedAnnotation) - { - id annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; - if (!annotation) { - return NO; - } - } + } else if (gestureRecognizer == _singleTapGestureRecognizer) { + // Gesture will be recognized if it could deselect an annotation + if (!self.selectedAnnotation) { + id annotation = + [self annotationForGestureRecognizer:(UITapGestureRecognizer *)gestureRecognizer + persistingResults:NO]; + if (!annotation) { + return NO; + } } - else if (gestureRecognizer == _pan) - { - if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { - if (self.isZooming || self.isRotating) { - return NO; - } - } + } else if (gestureRecognizer == _pan) { + if (self.anchorRotateOrZoomGesturesToCenterCoordinate) { + if (self.isZooming || self.isRotating) { + return NO; + } } - return YES; + } + return YES; } -- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer -{ - NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ]; - return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]); +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + shouldRecognizeSimultaneouslyWithGestureRecognizer: + (UIGestureRecognizer *)otherGestureRecognizer { + NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ]; + return ([validSimultaneousGestures containsObject:gestureRecognizer] && + [validSimultaneousGestures containsObject:otherGestureRecognizer]); } -- (CLLocationDegrees)angleBetweenPoints:(CGPoint)originPoint endPoint:(CGPoint)endPoint -{ - if (originPoint.x > endPoint.x) { - CGPoint swap = originPoint; - originPoint = endPoint; - endPoint = swap; - } +- (CLLocationDegrees)angleBetweenPoints:(CGPoint)originPoint endPoint:(CGPoint)endPoint { + if (originPoint.x > endPoint.x) { + CGPoint swap = originPoint; + originPoint = endPoint; + endPoint = swap; + } - CGFloat x = (endPoint.x - originPoint.x); - CGFloat y = (endPoint.y - originPoint.y); + CGFloat x = (endPoint.x - originPoint.x); + CGFloat y = (endPoint.y - originPoint.y); - CGFloat angleInRadians = atan2(y, x); - CLLocationDegrees angleInDegrees = MLNDegreesFromRadians(angleInRadians); + CGFloat angleInRadians = atan2(y, x); + CLLocationDegrees angleInDegrees = MLNDegreesFromRadians(angleInRadians); - return angleInDegrees; + return angleInDegrees; } // MARK: - Attribution - -- (void)showAttribution:(id)sender -{ - BOOL shouldShowVersion = [sender isKindOfClass:[UILongPressGestureRecognizer class]]; - if (shouldShowVersion) - { - UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender; - if (longPress.state != UIGestureRecognizerStateBegan) - { - return; - } +- (void)showAttribution:(id)sender { + BOOL shouldShowVersion = [sender isKindOfClass:[UILongPressGestureRecognizer class]]; + if (shouldShowVersion) { + UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender; + if (longPress.state != UIGestureRecognizerStateBegan) { + return; } + } - NSString *actionSheetTitle = NSLocalizedStringWithDefaultValue(@"SDK_NAME", nil, nil, @"MapLibre Native iOS", @"Action sheet title"); - UIAlertController *attributionController = [UIAlertController alertControllerWithTitle:actionSheetTitle - message:nil - preferredStyle:UIAlertControllerStyleActionSheet]; - NSString *version = [NSBundle mgl_frameworkInfoDictionary][@"CFBundleShortVersionString"]; - if (shouldShowVersion && version != nil && ![version isEqualToString:@""]) - { - attributionController.title = [actionSheetTitle stringByAppendingFormat:@" %@", version]; - } + NSString *actionSheetTitle = NSLocalizedStringWithDefaultValue( + @"SDK_NAME", nil, nil, @"MapLibre Native iOS", @"Action sheet title"); + UIAlertController *attributionController = + [UIAlertController alertControllerWithTitle:actionSheetTitle + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + NSString *version = [NSBundle mgl_frameworkInfoDictionary][@"CFBundleShortVersionString"]; + if (shouldShowVersion && version != nil && ![version isEqualToString:@""]) { + attributionController.title = [actionSheetTitle stringByAppendingFormat:@" %@", version]; + } - NSArray *attributionInfos = [self.style attributionInfosWithFontSize:[UIFont buttonFontSize] linkColor:nil]; - for (MLNAttributionInfo *attributionInfo in attributionInfos) - { - UIAlertAction *action = [UIAlertAction actionWithTitle:[attributionInfo.title.string mgl_titleCasedStringWithLocale:[NSLocale currentLocale]] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * _Nonnull actionBlock) { - NSURL *url = attributionInfo.URL; - if (url) - { - [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; - } - }]; - [attributionController addAction:action]; - } + NSArray *attributionInfos = [self.style attributionInfosWithFontSize:[UIFont buttonFontSize] + linkColor:nil]; + for (MLNAttributionInfo *attributionInfo in attributionInfos) { + UIAlertAction *action = + [UIAlertAction actionWithTitle:[attributionInfo.title.string + mgl_titleCasedStringWithLocale:[NSLocale currentLocale]] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull actionBlock) { + NSURL *url = attributionInfo.URL; + if (url) { + [[UIApplication sharedApplication] openURL:url + options:@{} + completionHandler:nil]; + } + }]; + [attributionController addAction:action]; + } - NSString *cancelTitle = NSLocalizedStringWithDefaultValue(@"CANCEL", nil, nil, @"Cancel", @"Title of button for dismissing attribution action sheet"); - UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelTitle - style:UIAlertActionStyleCancel - handler:NULL]; - [attributionController addAction:cancelAction]; + NSString *cancelTitle = NSLocalizedStringWithDefaultValue( + @"CANCEL", nil, nil, @"Cancel", @"Title of button for dismissing attribution action sheet"); + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelTitle + style:UIAlertActionStyleCancel + handler:NULL]; + [attributionController addAction:cancelAction]; - attributionController.popoverPresentationController.sourceView = self; - attributionController.popoverPresentationController.sourceRect = self.attributionButton.frame; + attributionController.popoverPresentationController.sourceView = self; + attributionController.popoverPresentationController.sourceRect = self.attributionButton.frame; - UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController]; - [viewController presentViewController:attributionController animated:YES completion:NULL]; - self.attributionController = attributionController; + UIViewController *viewController = [self.window.rootViewController mgl_topMostViewController]; + [viewController presentViewController:attributionController animated:YES completion:NULL]; + self.attributionController = attributionController; } // MARK: - Properties - static void *windowScreenContext = &windowScreenContext; -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if ([keyPath isEqualToString:@"coordinate"] && [object conformsToProtocol:@protocol(MLNAnnotation)] && ![object isKindOfClass:[MLNMultiPoint class]]) - { - id annotation = object; - MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinate key path of a non-multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) - { - const mbgl::Point point = MLNPointFromLocationCoordinate2D(annotation.coordinate); +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + if ([keyPath isEqualToString:@"coordinate"] && + [object conformsToProtocol:@protocol(MLNAnnotation)] && + ![object isKindOfClass:[MLNMultiPoint class]]) { + id annotation = object; + MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; + // We can get here because a subclass registered itself as an observer + // of the coordinate key path of a non-multipoint annotation but failed + // to handle the change. This check deters us from treating the + // subclass’s context as an annotation tag. If the context happens to + // match a valid annotation tag, the annotation will be unnecessarily + // but safely updated. + if (annotation == [self annotationWithTag:annotationTag]) { + const mbgl::Point point = MLNPointFromLocationCoordinate2D(annotation.coordinate); - if (annotationTag != MLNAnnotationTagNotFound) { - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - if (annotationContext.annotationView) - { - // Redundantly move the associated annotation view outside the scope of the animation-less transaction block in -updateAnnotationViews. - annotationContext.annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]; - } - - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - NSString *symbolName = annotationImage.styleIconIdentifier; - - // Update the annotation’s backing geometry to match the annotation model object. Any associated annotation view is also moved by side effect. However, -updateAnnotationViews disables the view’s animation actions, because it can’t distinguish between moves due to the viewport changing and moves due to the annotation’s coordinate changing. - self.mbglMap.updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, symbolName.UTF8String }); - [self updateCalloutView]; - } + if (annotationTag != MLNAnnotationTagNotFound) { + MLNAnnotationContext &annotationContext = + _annotationContextsByAnnotationTag.at(annotationTag); + if (annotationContext.annotationView) { + // Redundantly move the associated annotation view outside the scope of the animation-less + // transaction block in -updateAnnotationViews. + annotationContext.annotationView.center = + [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]; } + + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + NSString *symbolName = annotationImage.styleIconIdentifier; + + // Update the annotation’s backing geometry to match the annotation model object. Any + // associated annotation view is also moved by side effect. However, -updateAnnotationViews + // disables the view’s animation actions, because it can’t distinguish between moves due to + // the viewport changing and moves due to the annotation’s coordinate changing. + self.mbglMap.updateAnnotation(annotationTag, + mbgl::SymbolAnnotation{point, symbolName.UTF8String}); + [self updateCalloutView]; + } } - else if ([keyPath isEqualToString:@"coordinates"] && [object isKindOfClass:[MLNMultiPoint class]]) - { - MLNMultiPoint *annotation = object; - MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinates key path of a multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) - { - // Update the annotation’s backing geometry to match the annotation model object. - self.mbglMap.updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); - [self updateCalloutView]; - } + } else if ([keyPath isEqualToString:@"coordinates"] && + [object isKindOfClass:[MLNMultiPoint class]]) { + MLNMultiPoint *annotation = object; + MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; + // We can get here because a subclass registered itself as an observer + // of the coordinates key path of a multipoint annotation but failed + // to handle the change. This check deters us from treating the + // subclass’s context as an annotation tag. If the context happens to + // match a valid annotation tag, the annotation will be unnecessarily + // but safely updated. + if (annotation == [self annotationWithTag:annotationTag]) { + // Update the annotation’s backing geometry to match the annotation model object. + self.mbglMap.updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); + [self updateCalloutView]; } - else if (context == windowScreenContext) - { - if ([keyPath isEqualToString:@"screen"] || - [keyPath isEqualToString:@"windowScene"]) { - [self destroyDisplayLink]; - [self didBecomeActive:nil]; - } + } else if (context == windowScreenContext) { + if ([keyPath isEqualToString:@"screen"] || [keyPath isEqualToString:@"windowScene"]) { + [self destroyDisplayLink]; + [self didBecomeActive:nil]; } + } } -+ (NSSet *)keyPathsForValuesAffectingZoomEnabled -{ - return [NSSet setWithObject:@"allowsZooming"]; ++ (NSSet *)keyPathsForValuesAffectingZoomEnabled { + return [NSSet setWithObject:@"allowsZooming"]; } -+ (NSSet *)keyPathsForValuesAffectingScrollEnabled -{ - return [NSSet setWithObject:@"allowsScrolling"]; ++ (NSSet *)keyPathsForValuesAffectingScrollEnabled { + return [NSSet setWithObject:@"allowsScrolling"]; } -+ (NSSet *)keyPathsForValuesAffectingRotateEnabled -{ - return [NSSet setWithObject:@"allowsRotating"]; ++ (NSSet *)keyPathsForValuesAffectingRotateEnabled { + return [NSSet setWithObject:@"allowsRotating"]; } -+ (NSSet *)keyPathsForValuesAffectingPitchEnabled -{ - return [NSSet setWithObject:@"allowsTilting"]; ++ (NSSet *)keyPathsForValuesAffectingPitchEnabled { + return [NSSet setWithObject:@"allowsTilting"]; } -- (MLNMapDebugMaskOptions)debugMask -{ - if (!_mbglMap) - { - NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); - return self.residualDebugMask; - } +- (MLNMapDebugMaskOptions)debugMask { + if (!_mbglMap) { + NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); + return self.residualDebugMask; + } - mbgl::MapDebugOptions options = self.mbglMap.getDebug(); - MLNMapDebugMaskOptions mask = 0; - if (options & mbgl::MapDebugOptions::TileBorders) - { - mask |= MLNMapDebugTileBoundariesMask; - } - if (options & mbgl::MapDebugOptions::ParseStatus) - { - mask |= MLNMapDebugTileInfoMask; - } - if (options & mbgl::MapDebugOptions::Timestamps) - { - mask |= MLNMapDebugTimestampsMask; - } - if (options & mbgl::MapDebugOptions::Collision) - { - mask |= MLNMapDebugCollisionBoxesMask; - } - if (options & mbgl::MapDebugOptions::Overdraw) - { - mask |= MLNMapDebugOverdrawVisualizationMask; - } - return mask; + mbgl::MapDebugOptions options = self.mbglMap.getDebug(); + MLNMapDebugMaskOptions mask = 0; + if (options & mbgl::MapDebugOptions::TileBorders) { + mask |= MLNMapDebugTileBoundariesMask; + } + if (options & mbgl::MapDebugOptions::ParseStatus) { + mask |= MLNMapDebugTileInfoMask; + } + if (options & mbgl::MapDebugOptions::Timestamps) { + mask |= MLNMapDebugTimestampsMask; + } + if (options & mbgl::MapDebugOptions::Collision) { + mask |= MLNMapDebugCollisionBoxesMask; + } + if (options & mbgl::MapDebugOptions::Overdraw) { + mask |= MLNMapDebugOverdrawVisualizationMask; + } + return mask; } -- (void)setDebugMask:(MLNMapDebugMaskOptions)debugMask -{ - if (!_mbglMap) - { - return; - } +- (void)setDebugMask:(MLNMapDebugMaskOptions)debugMask { + if (!_mbglMap) { + return; + } - mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug; - if (debugMask & MLNMapDebugTileBoundariesMask) - { - options |= mbgl::MapDebugOptions::TileBorders; - } - if (debugMask & MLNMapDebugTileInfoMask) - { - options |= mbgl::MapDebugOptions::ParseStatus; - } - if (debugMask & MLNMapDebugTimestampsMask) - { - options |= mbgl::MapDebugOptions::Timestamps; - } - if (debugMask & MLNMapDebugCollisionBoxesMask) - { - options |= mbgl::MapDebugOptions::Collision; - } - if (debugMask & MLNMapDebugOverdrawVisualizationMask) - { - options |= mbgl::MapDebugOptions::Overdraw; - } - self.mbglMap.setDebug(options); + mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug; + if (debugMask & MLNMapDebugTileBoundariesMask) { + options |= mbgl::MapDebugOptions::TileBorders; + } + if (debugMask & MLNMapDebugTileInfoMask) { + options |= mbgl::MapDebugOptions::ParseStatus; + } + if (debugMask & MLNMapDebugTimestampsMask) { + options |= mbgl::MapDebugOptions::Timestamps; + } + if (debugMask & MLNMapDebugCollisionBoxesMask) { + options |= mbgl::MapDebugOptions::Collision; + } + if (debugMask & MLNMapDebugOverdrawVisualizationMask) { + options |= mbgl::MapDebugOptions::Overdraw; + } + self.mbglMap.setDebug(options); } -- (void)resetNorth -{ - MLNLogInfo(@"Resetting the map rotation to a northern heading — a direction of 0 degrees."); - [self resetNorthAnimated:YES]; +- (void)resetNorth { + MLNLogInfo(@"Resetting the map rotation to a northern heading — a direction of 0 degrees."); + [self resetNorthAnimated:YES]; } -- (void)resetNorthAnimated:(BOOL)animated -{ - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonResetNorth; +- (void)resetNorthAnimated:(BOOL)animated { + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonResetNorth; - [self setDirection:0 animated:animated]; + [self setDirection:0 animated:animated]; } -- (void)resetPosition -{ - MLNLogInfo(@"Resetting the map to the current style’s default viewport."); - auto camera = self.mbglMap.getStyle().getDefaultCamera(); +- (void)resetPosition { + MLNLogInfo(@"Resetting the map to the current style’s default viewport."); + auto camera = self.mbglMap.getStyle().getDefaultCamera(); - double pitch = camera.pitch ? *camera.pitch : 0.0; - double bearing = camera.bearing ? *camera.bearing : 0.0; - double zoom = camera.zoom ? *camera.zoom : 0.0; - mbgl::LatLng center = camera.center ? *camera.center : mbgl::LatLng(); + double pitch = camera.pitch ? *camera.pitch : 0.0; + double bearing = camera.bearing ? *camera.bearing : 0.0; + double zoom = camera.zoom ? *camera.zoom : 0.0; + mbgl::LatLng center = camera.center ? *camera.center : mbgl::LatLng(); - CLLocationDirection heading = mbgl::util::wrap(bearing, 0., 360.); - CLLocationDistance altitude = MLNAltitudeForZoomLevel(zoom, pitch, 0, self.frame.size); - self.camera = [MLNMapCamera cameraLookingAtCenterCoordinate:MLNLocationCoordinate2DFromLatLng(center) - altitude:altitude - pitch:pitch - heading:heading]; + CLLocationDirection heading = mbgl::util::wrap(bearing, 0., 360.); + CLLocationDistance altitude = MLNAltitudeForZoomLevel(zoom, pitch, 0, self.frame.size); + self.camera = + [MLNMapCamera cameraLookingAtCenterCoordinate:MLNLocationCoordinate2DFromLatLng(center) + altitude:altitude + pitch:pitch + heading:heading]; } -- (void)setZoomEnabled:(BOOL)zoomEnabled -{ - MLNLogDebug(@"Setting zoomEnabled: %@", MLNStringFromBOOL(zoomEnabled)); - _zoomEnabled = zoomEnabled; - self.pinch.enabled = zoomEnabled; - self.doubleTap.enabled = zoomEnabled; - self.quickZoom.enabled = zoomEnabled; - self.twoFingerTap.enabled = zoomEnabled; +- (void)setZoomEnabled:(BOOL)zoomEnabled { + MLNLogDebug(@"Setting zoomEnabled: %@", MLNStringFromBOOL(zoomEnabled)); + _zoomEnabled = zoomEnabled; + self.pinch.enabled = zoomEnabled; + self.doubleTap.enabled = zoomEnabled; + self.quickZoom.enabled = zoomEnabled; + self.twoFingerTap.enabled = zoomEnabled; } -- (void)setQuickZoomReversed:(BOOL)quickZoomReversed -{ - MLNLogDebug(@"Setting quickZoomReversed: %@", MLNStringFromBOOL(quickZoomReversed)); - _quickZoomReversed = quickZoomReversed; +- (void)setQuickZoomReversed:(BOOL)quickZoomReversed { + MLNLogDebug(@"Setting quickZoomReversed: %@", MLNStringFromBOOL(quickZoomReversed)); + _quickZoomReversed = quickZoomReversed; } -- (void)setScrollEnabled:(BOOL)scrollEnabled -{ - MLNLogDebug(@"Setting scrollEnabled: %@", MLNStringFromBOOL(scrollEnabled)); - _scrollEnabled = scrollEnabled; - self.pan.enabled = scrollEnabled; +- (void)setScrollEnabled:(BOOL)scrollEnabled { + MLNLogDebug(@"Setting scrollEnabled: %@", MLNStringFromBOOL(scrollEnabled)); + _scrollEnabled = scrollEnabled; + self.pan.enabled = scrollEnabled; } -- (void)setRotateEnabled:(BOOL)rotateEnabled -{ - MLNLogDebug(@"Setting rotateEnabled: %@", MLNStringFromBOOL(rotateEnabled)); - _rotateEnabled = rotateEnabled; - self.rotate.enabled = rotateEnabled; +- (void)setRotateEnabled:(BOOL)rotateEnabled { + MLNLogDebug(@"Setting rotateEnabled: %@", MLNStringFromBOOL(rotateEnabled)); + _rotateEnabled = rotateEnabled; + self.rotate.enabled = rotateEnabled; } -- (void)setPitchEnabled:(BOOL)pitchEnabled -{ - MLNLogDebug(@"Setting pitchEnabled: %@", MLNStringFromBOOL(pitchEnabled)); - _pitchEnabled = pitchEnabled; - self.twoFingerDrag.enabled = pitchEnabled; +- (void)setPitchEnabled:(BOOL)pitchEnabled { + MLNLogDebug(@"Setting pitchEnabled: %@", MLNStringFromBOOL(pitchEnabled)); + _pitchEnabled = pitchEnabled; + self.twoFingerDrag.enabled = pitchEnabled; } -- (void)setShowsScale:(BOOL)showsScale -{ - MLNLogDebug(@"Setting showsScale: %@", MLNStringFromBOOL(showsScale)); - _showsScale = showsScale; - self.scaleBar.hidden = !showsScale; +- (void)setShowsScale:(BOOL)showsScale { + MLNLogDebug(@"Setting showsScale: %@", MLNStringFromBOOL(showsScale)); + _showsScale = showsScale; + self.scaleBar.hidden = !showsScale; - if (showsScale) - { - [self updateScaleBar]; - } + if (showsScale) { + [self updateScaleBar]; + } } -- (void)setShowsLogoView:(BOOL)showsLogoView -{ - MLNLogDebug(@"Setting showsLogoView: %@", MLNStringFromBOOL(showsLogoView)); - _showsLogoView = showsLogoView; - self.logoView.hidden = !showsLogoView; +- (void)setShowsLogoView:(BOOL)showsLogoView { + MLNLogDebug(@"Setting showsLogoView: %@", MLNStringFromBOOL(showsLogoView)); + _showsLogoView = showsLogoView; + self.logoView.hidden = !showsLogoView; } -- (void)setShowsCompassView:(BOOL)showsCompassView -{ - MLNLogDebug(@"Setting showsCompassView: %@", MLNStringFromBOOL(showsCompassView)); - _showsCompassView = showsCompassView; - self.compassView.hidden = !showsCompassView; +- (void)setShowsCompassView:(BOOL)showsCompassView { + MLNLogDebug(@"Setting showsCompassView: %@", MLNStringFromBOOL(showsCompassView)); + _showsCompassView = showsCompassView; + self.compassView.hidden = !showsCompassView; } -- (void)setShowsAttributionButton:(BOOL)showsAttributionButton -{ - MLNLogDebug(@"Setting showsAttributionButton: %@", MLNStringFromBOOL(showsAttributionButton)); - _showsAttributionButton = showsAttributionButton; - self.attributionButton.hidden = !showsAttributionButton; +- (void)setShowsAttributionButton:(BOOL)showsAttributionButton { + MLNLogDebug(@"Setting showsAttributionButton: %@", MLNStringFromBOOL(showsAttributionButton)); + _showsAttributionButton = showsAttributionButton; + self.attributionButton.hidden = !showsAttributionButton; } - (void)setScaleBarShouldShowDarkStyles:(BOOL)scaleBarShouldShowDarkStyles { - - _scaleBarShouldShowDarkStyles = scaleBarShouldShowDarkStyles; - [(MLNScaleBar *)self.scaleBar setShouldShowDarkStyles:scaleBarShouldShowDarkStyles]; - + _scaleBarShouldShowDarkStyles = scaleBarShouldShowDarkStyles; + [(MLNScaleBar *)self.scaleBar setShouldShowDarkStyles:scaleBarShouldShowDarkStyles]; } - (void)setScaleBarUsesMetricSystem:(BOOL)scaleBarUsesMetricSystem { - - _scaleBarUsesMetricSystem = scaleBarUsesMetricSystem; - [(MLNScaleBar *)self.scaleBar setUsesMetricSystem:scaleBarUsesMetricSystem]; - + _scaleBarUsesMetricSystem = scaleBarUsesMetricSystem; + [(MLNScaleBar *)self.scaleBar setUsesMetricSystem:scaleBarUsesMetricSystem]; } -- (void)setPrefetchesTiles:(BOOL)prefetchesTiles -{ - self.mbglMap.setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0); +- (void)setPrefetchesTiles:(BOOL)prefetchesTiles { + self.mbglMap.setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0); } -- (BOOL)prefetchesTiles -{ - return self.mbglMap.getPrefetchZoomDelta() > 0 ? YES : NO; +- (BOOL)prefetchesTiles { + return self.mbglMap.getPrefetchZoomDelta() > 0 ? YES : NO; } -- (void)setTileCacheEnabled:(BOOL)enabled -{ - _rendererFrontend->setTileCacheEnabled(enabled); +- (void)setTileCacheEnabled:(BOOL)enabled { + _rendererFrontend->setTileCacheEnabled(enabled); } -- (BOOL)tileCacheEnabled -{ - return _rendererFrontend->getTileCacheEnabled(); +- (BOOL)tileCacheEnabled { + return _rendererFrontend->getTileCacheEnabled(); } -- (void)setTileLodMinRadius:(double)tileLodMinRadius -{ - _mbglMap->setTileLodMinRadius(tileLodMinRadius); +- (void)setTileLodMinRadius:(double)tileLodMinRadius { + _mbglMap->setTileLodMinRadius(tileLodMinRadius); } -- (double)tileLodMinRadius -{ - return _mbglMap->getTileLodMinRadius(); +- (double)tileLodMinRadius { + return _mbglMap->getTileLodMinRadius(); } -- (void)setTileLodScale:(double)tileLodScale -{ - _mbglMap->setTileLodScale(tileLodScale); +- (void)setTileLodScale:(double)tileLodScale { + _mbglMap->setTileLodScale(tileLodScale); } -- (double)tileLodScale -{ - return _mbglMap->getTileLodScale(); +- (double)tileLodScale { + return _mbglMap->getTileLodScale(); } --(void)setTileLodPitchThreshold:(double)tileLodPitchThreshold -{ - _mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold); +- (void)setTileLodPitchThreshold:(double)tileLodPitchThreshold { + _mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold); } --(double)tileLodPitchThreshold -{ - return _mbglMap->getTileLodPitchThreshold(); +- (double)tileLodPitchThreshold { + return _mbglMap->getTileLodPitchThreshold(); } --(void)setTileLodZoomShift:(double)tileLodZoomShift -{ - _mbglMap->setTileLodZoomShift(tileLodZoomShift); +- (void)setTileLodZoomShift:(double)tileLodZoomShift { + _mbglMap->setTileLodZoomShift(tileLodZoomShift); } --(double)tileLodZoomShift -{ - return _mbglMap->getTileLodZoomShift(); +- (double)tileLodZoomShift { + return _mbglMap->getTileLodZoomShift(); } --(void)setFrustumOffset:(UIEdgeInsets)frustumOffset -{ - _mbglMap->setFrustumOffset(MLNEdgeInsetsFromNSEdgeInsets(frustumOffset)); +- (void)setFrustumOffset:(UIEdgeInsets)frustumOffset { + _mbglMap->setFrustumOffset(MLNEdgeInsetsFromNSEdgeInsets(frustumOffset)); } --(UIEdgeInsets)frustumOffset -{ - return NSEdgeInsetsFromMLNEdgeInsets(_mbglMap->getFrustumOffset()); +- (UIEdgeInsets)frustumOffset { + return NSEdgeInsetsFromMLNEdgeInsets(_mbglMap->getFrustumOffset()); } // MARK: - Accessibility - -- (NSString *)accessibilityValue -{ - NSMutableArray *facts = [NSMutableArray array]; +- (NSString *)accessibilityValue { + NSMutableArray *facts = [NSMutableArray array]; - double zoomLevel = round(self.zoomLevel + 1); - [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ZOOM", nil, nil, @"Zoom %dx.", @"Map accessibility value; {zoom level}"), (int)zoomLevel]]; + double zoomLevel = round(self.zoomLevel + 1); + [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue( + @"MAP_A11Y_VALUE_ZOOM", nil, nil, @"Zoom %dx.", + @"Map accessibility value; {zoom level}"), + (int)zoomLevel]]; - NSInteger annotationCount = self.accessibilityAnnotationCount; - if (annotationCount) { - [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ANNOTATIONS", nil, nil, @"%ld annotation(s) visible.", @"Map accessibility value; {number of visible annotations}"), (long)self.accessibilityAnnotationCount]]; + NSInteger annotationCount = self.accessibilityAnnotationCount; + if (annotationCount) { + [facts addObject:[NSString stringWithFormat: + NSLocalizedStringWithDefaultValue( + @"MAP_A11Y_VALUE_ANNOTATIONS", nil, nil, + @"%ld annotation(s) visible.", + @"Map accessibility value; {number of visible annotations}"), + (long)self.accessibilityAnnotationCount]]; + } + + NSArray *placeFeatures = self.visiblePlaceFeatures; + if (placeFeatures.count) { + NSMutableArray *placesArray = [NSMutableArray arrayWithCapacity:placeFeatures.count]; + NSMutableSet *placesSet = [NSMutableSet setWithCapacity:placeFeatures.count]; + for (id placeFeature in placeFeatures.reverseObjectEnumerator) { + NSString *name = [placeFeature attributeForKey:@"name"]; + if (![placesSet containsObject:name]) { + [placesArray addObject:name]; + [placesSet addObject:name]; + } + if (placesArray.count >= 3) { + break; + } } + NSString *placesString = [placesArray + componentsJoinedByString:NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, + @", ", @"List separator")]; + [facts addObject:[NSString + stringWithFormat:NSLocalizedStringWithDefaultValue( + @"MAP_A11Y_VALUE_PLACES", nil, nil, + @"Places visible: %@.", + @"Map accessibility value; {list of visible places}"), + placesString]]; + } - NSArray *placeFeatures = self.visiblePlaceFeatures; - if (placeFeatures.count) { - NSMutableArray *placesArray = [NSMutableArray arrayWithCapacity:placeFeatures.count]; - NSMutableSet *placesSet = [NSMutableSet setWithCapacity:placeFeatures.count]; - for (id placeFeature in placeFeatures.reverseObjectEnumerator) { - NSString *name = [placeFeature attributeForKey:@"name"]; - if (![placesSet containsObject:name]) { - [placesArray addObject:name]; - [placesSet addObject:name]; - } - if (placesArray.count >= 3) { - break; - } - } - NSString *placesString = [placesArray componentsJoinedByString:NSLocalizedStringWithDefaultValue(@"LIST_SEPARATOR", nil, nil, @", ", @"List separator")]; - [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_PLACES", nil, nil, @"Places visible: %@.", @"Map accessibility value; {list of visible places}"), placesString]]; - } + NSArray *roadFeatures = self.visibleRoadFeatures; + if (roadFeatures.count) { + [facts + addObject:[NSString + stringWithFormat:NSLocalizedStringWithDefaultValue( + @"MAP_A11Y_VALUE_ROADS", nil, nil, + @"%ld road(s) visible.", + @"Map accessibility value; {number of visible roads}"), + roadFeatures.count]]; + } - NSArray *roadFeatures = self.visibleRoadFeatures; - if (roadFeatures.count) { - [facts addObject:[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"MAP_A11Y_VALUE_ROADS", nil, nil, @"%ld road(s) visible.", @"Map accessibility value; {number of visible roads}"), roadFeatures.count]]; - } - - NSString *value = [facts componentsJoinedByString:@" "]; - return value; + NSString *value = [facts componentsJoinedByString:@" "]; + return value; } -- (NSArray> *)visiblePlaceFeatures -{ - if (!_visiblePlaceFeatures) - { - NSArray *placeStyleLayerIdentifiers = [self.style.placeStyleLayers valueForKey:@"identifier"]; - _visiblePlaceFeatures = [self visibleFeaturesInRect:self.bounds inStyleLayersWithIdentifiers:[NSSet setWithArray:placeStyleLayerIdentifiers]]; - } - return _visiblePlaceFeatures; +- (NSArray> *)visiblePlaceFeatures { + if (!_visiblePlaceFeatures) { + NSArray *placeStyleLayerIdentifiers = [self.style.placeStyleLayers valueForKey:@"identifier"]; + _visiblePlaceFeatures = + [self visibleFeaturesInRect:self.bounds + inStyleLayersWithIdentifiers:[NSSet setWithArray:placeStyleLayerIdentifiers]]; + } + return _visiblePlaceFeatures; } -- (NSArray> *)visibleRoadFeatures -{ - if (!_visibleRoadFeatures) - { - NSArray *roadStyleLayerIdentifiers = [self.style.roadStyleLayers valueForKey:@"identifier"]; - _visibleRoadFeatures = [self visibleFeaturesInRect:self.bounds inStyleLayersWithIdentifiers:[NSSet setWithArray:roadStyleLayerIdentifiers]]; - } - return _visibleRoadFeatures; +- (NSArray> *)visibleRoadFeatures { + if (!_visibleRoadFeatures) { + NSArray *roadStyleLayerIdentifiers = [self.style.roadStyleLayers valueForKey:@"identifier"]; + _visibleRoadFeatures = + [self visibleFeaturesInRect:self.bounds + inStyleLayersWithIdentifiers:[NSSet setWithArray:roadStyleLayerIdentifiers]]; + } + return _visibleRoadFeatures; } -- (CGRect)accessibilityFrame -{ - CGRect frame = [super accessibilityFrame]; - UIViewController *viewController = self.mgl_viewControllerForLayoutGuides; - if (viewController) - { +- (CGRect)accessibilityFrame { + CGRect frame = [super accessibilityFrame]; + UIViewController *viewController = self.mgl_viewControllerForLayoutGuides; + if (viewController) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat topInset = viewController.topLayoutGuide.length; - frame.origin.y += topInset; - frame.size.height -= topInset + viewController.bottomLayoutGuide.length; + CGFloat topInset = viewController.topLayoutGuide.length; + frame.origin.y += topInset; + frame.size.height -= topInset + viewController.bottomLayoutGuide.length; #pragma clang diagnostic pop - } - return frame; + } + return frame; } -- (UIBezierPath *)accessibilityPath -{ - UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.accessibilityFrame]; +- (UIBezierPath *)accessibilityPath { + UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.accessibilityFrame]; - // Exclude any visible annotation callout view. - if (self.calloutViewForSelectedAnnotation) - { - UIBezierPath *calloutViewPath = [UIBezierPath bezierPathWithRect:self.calloutViewForSelectedAnnotation.frame]; - [path appendPath:calloutViewPath]; - } + // Exclude any visible annotation callout view. + if (self.calloutViewForSelectedAnnotation) { + UIBezierPath *calloutViewPath = + [UIBezierPath bezierPathWithRect:self.calloutViewForSelectedAnnotation.frame]; + [path appendPath:calloutViewPath]; + } - return path; + return path; } -- (NSInteger)accessibilityElementCount -{ - if (self.calloutViewForSelectedAnnotation) - { - return 2 /* calloutViewForSelectedAnnotation, mapViewProxyAccessibilityElement */; - } - return !!self.userLocationAnnotationView + self.accessibilityAnnotationCount + self.visiblePlaceFeatures.count + self.visibleRoadFeatures.count + 2 /* compass, attributionButton */; +- (NSInteger)accessibilityElementCount { + if (self.calloutViewForSelectedAnnotation) { + return 2 /* calloutViewForSelectedAnnotation, mapViewProxyAccessibilityElement */; + } + return !!self.userLocationAnnotationView + self.accessibilityAnnotationCount + + self.visiblePlaceFeatures.count + self.visibleRoadFeatures.count + + 2 /* compass, attributionButton */; } -- (NSInteger)accessibilityAnnotationCount -{ - std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; - return visibleAnnotations.size(); +- (NSInteger)accessibilityAnnotationCount { + std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; + return visibleAnnotations.size(); } -- (id)accessibilityElementAtIndex:(NSInteger)index -{ - if (self.calloutViewForSelectedAnnotation) - { - if (index == 0) - { - return self.calloutViewForSelectedAnnotation; - } - if (index == 1) - { - self.mapViewProxyAccessibilityElement.accessibilityFrame = self.accessibilityFrame; - self.mapViewProxyAccessibilityElement.accessibilityPath = self.accessibilityPath; - return self.mapViewProxyAccessibilityElement; - } - return nil; +- (id)accessibilityElementAtIndex:(NSInteger)index { + if (self.calloutViewForSelectedAnnotation) { + if (index == 0) { + return self.calloutViewForSelectedAnnotation; } - - // Compass - NSInteger compassIndex = 0; - if (index == compassIndex) - { - return self.compassView; + if (index == 1) { + self.mapViewProxyAccessibilityElement.accessibilityFrame = self.accessibilityFrame; + self.mapViewProxyAccessibilityElement.accessibilityPath = self.accessibilityPath; + return self.mapViewProxyAccessibilityElement; } - - // User location annotation - NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView); - if (NSLocationInRange(index, userLocationAnnotationRange)) - { - return self.userLocationAnnotationView; - } - - CGPoint centerPoint = self.contentCenter; - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } - - // Visible annotations - std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; - NSRange visibleAnnotationRange = NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size()); - if (NSLocationInRange(index, visibleAnnotationRange)) - { - std::sort(visibleAnnotations.begin(), visibleAnnotations.end()); - std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { - CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; - CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; - CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self]; - CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return deltaA < deltaB; - }); - - NSUInteger annotationIndex = index - visibleAnnotationRange.location; - MLNAnnotationTag annotationTag = visibleAnnotations[annotationIndex]; - MLNAssert(annotationTag != MLNAnnotationTagNotFound, @"Can’t get accessibility element for nonexistent or invisible annotation at index %li.", (long)index); - return [self accessibilityElementForAnnotationWithTag:annotationTag]; - } - - // Visible place features - NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures; - NSRange visiblePlaceFeatureRange = NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count); - if (NSLocationInRange(index, visiblePlaceFeatureRange)) - { - visiblePlaceFeatures = [visiblePlaceFeatures sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, id _Nonnull featureB) { - CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; - CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return [@(deltaA) compare:@(deltaB)]; - }]; - - id feature = visiblePlaceFeatures[index - visiblePlaceFeatureRange.location]; - return [self accessibilityElementForPlaceFeature:feature]; - } - - // Visible road features - NSArray *visibleRoadFeatures = self.visibleRoadFeatures; - NSRange visibleRoadFeatureRange = NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count); - if (NSLocationInRange(index, visibleRoadFeatureRange)) - { - visibleRoadFeatures = [visibleRoadFeatures sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, id _Nonnull featureB) { - CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; - CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return [@(deltaA) compare:@(deltaB)]; - }]; - - id feature = visibleRoadFeatures[index - visibleRoadFeatureRange.location]; - return [self accessibilityElementForRoadFeature:feature]; - } - - // Attribution button - NSInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange); - if (index == attributionButtonIndex) - { - return self.attributionButton; - } - - MLNAssert(NO, @"Index %ld not in recognized accessibility element ranges. " - @"User location annotation range: %@; visible annotation range: %@; " - @"visible place feature range: %@; visible road feature range: %@.", - (long)index, NSStringFromRange(userLocationAnnotationRange), - NSStringFromRange(visibleAnnotationRange), NSStringFromRange(visiblePlaceFeatureRange), - NSStringFromRange(visibleRoadFeatureRange)); return nil; + } + + // Compass + NSInteger compassIndex = 0; + if (index == compassIndex) { + return self.compassView; + } + + // User location annotation + NSRange userLocationAnnotationRange = + NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView); + if (NSLocationInRange(index, userLocationAnnotationRange)) { + return self.userLocationAnnotationView; + } + + CGPoint centerPoint = self.contentCenter; + if (self.userTrackingMode != MLNUserTrackingModeNone) { + centerPoint = self.userLocationAnnotationViewCenter; + } + + // Visible annotations + std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; + NSRange visibleAnnotationRange = + NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size()); + if (NSLocationInRange(index, visibleAnnotationRange)) { + std::sort(visibleAnnotations.begin(), visibleAnnotations.end()); + std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), + [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { + CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; + CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; + CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self]; + CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return deltaA < deltaB; + }); + + NSUInteger annotationIndex = index - visibleAnnotationRange.location; + MLNAnnotationTag annotationTag = visibleAnnotations[annotationIndex]; + MLNAssert( + annotationTag != MLNAnnotationTagNotFound, + @"Can’t get accessibility element for nonexistent or invisible annotation at index %li.", + (long)index); + return [self accessibilityElementForAnnotationWithTag:annotationTag]; + } + + // Visible place features + NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures; + NSRange visiblePlaceFeatureRange = + NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count); + if (NSLocationInRange(index, visiblePlaceFeatureRange)) { + visiblePlaceFeatures = [visiblePlaceFeatures + sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, + id _Nonnull featureB) { + CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; + CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return [@(deltaA) compare:@(deltaB)]; + }]; + + id feature = visiblePlaceFeatures[index - visiblePlaceFeatureRange.location]; + return [self accessibilityElementForPlaceFeature:feature]; + } + + // Visible road features + NSArray *visibleRoadFeatures = self.visibleRoadFeatures; + NSRange visibleRoadFeatureRange = + NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count); + if (NSLocationInRange(index, visibleRoadFeatureRange)) { + visibleRoadFeatures = [visibleRoadFeatures + sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, + id _Nonnull featureB) { + CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; + CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return [@(deltaA) compare:@(deltaB)]; + }]; + + id feature = visibleRoadFeatures[index - visibleRoadFeatureRange.location]; + return [self accessibilityElementForRoadFeature:feature]; + } + + // Attribution button + NSInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange); + if (index == attributionButtonIndex) { + return self.attributionButton; + } + + MLNAssert(NO, + @"Index %ld not in recognized accessibility element ranges. " + @"User location annotation range: %@; visible annotation range: %@; " + @"visible place feature range: %@; visible road feature range: %@.", + (long)index, NSStringFromRange(userLocationAnnotationRange), + NSStringFromRange(visibleAnnotationRange), NSStringFromRange(visiblePlaceFeatureRange), + NSStringFromRange(visibleRoadFeatureRange)); + return nil; } /** @@ -3559,51 +3457,51 @@ static void *windowScreenContext = &windowScreenContext; @param annotationTag Tag of the annotation represented by the accessibility element to return. */ -- (id)accessibilityElementForAnnotationWithTag:(MLNAnnotationTag)annotationTag -{ - MLNAssert(_annotationContextsByAnnotationTag.count(annotationTag), @"Missing annotation for tag %llu.", annotationTag); - auto annotationContextIt = _annotationContextsByAnnotationTag.find(annotationTag); - if (annotationContextIt == _annotationContextsByAnnotationTag.end()) { - return nil; - } +- (id)accessibilityElementForAnnotationWithTag:(MLNAnnotationTag)annotationTag { + MLNAssert(_annotationContextsByAnnotationTag.count(annotationTag), + @"Missing annotation for tag %llu.", annotationTag); + auto annotationContextIt = _annotationContextsByAnnotationTag.find(annotationTag); + if (annotationContextIt == _annotationContextsByAnnotationTag.end()) { + return nil; + } - MLNAnnotationContext &annotationContext = annotationContextIt->second; - id annotation = annotationContext.annotation; + MLNAnnotationContext &annotationContext = annotationContextIt->second; + id annotation = annotationContext.annotation; - // Let the annotation view serve as its own accessibility element. - MLNAnnotationView *annotationView = annotationContext.annotationView; - if (annotationView && annotationView.superview) - { - return annotationView; - } + // Let the annotation view serve as its own accessibility element. + MLNAnnotationView *annotationView = annotationContext.annotationView; + if (annotationView && annotationView.superview) { + return annotationView; + } - // Lazily create an accessibility element for the found annotation. - if ( ! annotationContext.accessibilityElement) - { - annotationContext.accessibilityElement = [[MLNAnnotationAccessibilityElement alloc] initWithAccessibilityContainer:self tag:annotationTag]; - } + // Lazily create an accessibility element for the found annotation. + if (!annotationContext.accessibilityElement) { + annotationContext.accessibilityElement = + [[MLNAnnotationAccessibilityElement alloc] initWithAccessibilityContainer:self + tag:annotationTag]; + } - // Update the accessibility element. - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - CGRect annotationFrame = [self frameOfImage:annotationImage.image centeredAtCoordinate:annotation.coordinate]; - CGPoint annotationFrameCenter = CGPointMake(CGRectGetMidX(annotationFrame), CGRectGetMidY(annotationFrame)); - CGRect minimumFrame = CGRectInset({ annotationFrameCenter, CGSizeZero }, - -MLNAnnotationAccessibilityElementMinimumSize.width / 2, - -MLNAnnotationAccessibilityElementMinimumSize.height / 2); - annotationFrame = CGRectUnion(annotationFrame, minimumFrame); - CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); - annotationContext.accessibilityElement.accessibilityFrame = screenRect; + // Update the accessibility element. + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + CGRect annotationFrame = [self frameOfImage:annotationImage.image + centeredAtCoordinate:annotation.coordinate]; + CGPoint annotationFrameCenter = + CGPointMake(CGRectGetMidX(annotationFrame), CGRectGetMidY(annotationFrame)); + CGRect minimumFrame = CGRectInset({annotationFrameCenter, CGSizeZero}, + -MLNAnnotationAccessibilityElementMinimumSize.width / 2, + -MLNAnnotationAccessibilityElementMinimumSize.height / 2); + annotationFrame = CGRectUnion(annotationFrame, minimumFrame); + CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); + annotationContext.accessibilityElement.accessibilityFrame = screenRect; - if ([annotation respondsToSelector:@selector(title)]) - { - annotationContext.accessibilityElement.accessibilityLabel = annotation.title; - } - if ([annotation respondsToSelector:@selector(subtitle)]) - { - annotationContext.accessibilityElement.accessibilityValue = annotation.subtitle; - } + if ([annotation respondsToSelector:@selector(title)]) { + annotationContext.accessibilityElement.accessibilityLabel = annotation.title; + } + if ([annotation respondsToSelector:@selector(subtitle)]) { + annotationContext.accessibilityElement.accessibilityValue = annotation.subtitle; + } - return annotationContext.accessibilityElement; + return annotationContext.accessibilityElement; } /** @@ -3611,28 +3509,32 @@ static void *windowScreenContext = &windowScreenContext; @param feature The place feature represented by the accessibility element. */ -- (id)accessibilityElementForPlaceFeature:(id )feature -{ - if (!_featureAccessibilityElements) - { - _featureAccessibilityElements = [NSMutableSet set]; - } +- (id)accessibilityElementForPlaceFeature:(id)feature { + if (!_featureAccessibilityElements) { + _featureAccessibilityElements = [NSMutableSet set]; + } - MLNFeatureAccessibilityElement *element = [_featureAccessibilityElements objectsPassingTest:^BOOL(MLNFeatureAccessibilityElement * _Nonnull testElement, BOOL * _Nonnull stop) { - return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && [testElement.feature.identifier isEqual:feature.identifier]; - }].anyObject; - if (!element) - { - element = [[MLNPlaceFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature]; - } - CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self]; - CGRect annotationFrame = CGRectInset({center, CGSizeZero}, -MLNAnnotationAccessibilityElementMinimumSize.width / 2, -MLNAnnotationAccessibilityElementMinimumSize.width / 2); - CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); - element.accessibilityFrame = screenRect; + MLNFeatureAccessibilityElement *element = + [_featureAccessibilityElements objectsPassingTest:^BOOL( + MLNFeatureAccessibilityElement *_Nonnull testElement, + BOOL *_Nonnull stop) { + return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && + [testElement.feature.identifier isEqual:feature.identifier]; + }].anyObject; + if (!element) { + element = [[MLNPlaceFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self + feature:feature]; + } + CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self]; + CGRect annotationFrame = + CGRectInset({center, CGSizeZero}, -MLNAnnotationAccessibilityElementMinimumSize.width / 2, + -MLNAnnotationAccessibilityElementMinimumSize.width / 2); + CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); + element.accessibilityFrame = screenRect; - [_featureAccessibilityElements addObject:element]; + [_featureAccessibilityElements addObject:element]; - return element; + return element; } /** @@ -3640,1650 +3542,1648 @@ static void *windowScreenContext = &windowScreenContext; @param feature The road feature represented by the accessibility element. */ -- (id)accessibilityElementForRoadFeature:(id )feature -{ - if (!_featureAccessibilityElements) - { - _featureAccessibilityElements = [NSMutableSet set]; - } +- (id)accessibilityElementForRoadFeature:(id)feature { + if (!_featureAccessibilityElements) { + _featureAccessibilityElements = [NSMutableSet set]; + } - MLNFeatureAccessibilityElement *element = [_featureAccessibilityElements objectsPassingTest:^BOOL(MLNFeatureAccessibilityElement * _Nonnull testElement, BOOL * _Nonnull stop) { - return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && [testElement.feature.identifier isEqual:feature.identifier]; - }].anyObject; - if (!element) - { - element = [[MLNRoadFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self feature:feature]; - } + MLNFeatureAccessibilityElement *element = + [_featureAccessibilityElements objectsPassingTest:^BOOL( + MLNFeatureAccessibilityElement *_Nonnull testElement, + BOOL *_Nonnull stop) { + return testElement.feature.identifier && ![testElement.feature.identifier isEqual:@0] && + [testElement.feature.identifier isEqual:feature.identifier]; + }].anyObject; + if (!element) { + element = [[MLNRoadFeatureAccessibilityElement alloc] initWithAccessibilityContainer:self + feature:feature]; + } - UIBezierPath *path; - if ([feature isKindOfClass:[MLNPointFeature class]]) - { - CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self]; - CGRect annotationFrame = CGRectInset({center, CGSizeZero}, -MLNAnnotationAccessibilityElementMinimumSize.width / 2, -MLNAnnotationAccessibilityElementMinimumSize.width / 2); - CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); - element.accessibilityFrame = screenRect; - } - else if ([feature isKindOfClass:[MLNPolylineFeature class]]) - { - path = [self pathOfPolyline:(MLNPolyline *)feature]; - } - else if ([feature isKindOfClass:[MLNMultiPolylineFeature class]]) - { - path = [UIBezierPath bezierPath]; - for (MLNPolyline *polyline in [(MLNMultiPolylineFeature *)feature polylines]) - { - [path appendPath:[self pathOfPolyline:polyline]]; - } + UIBezierPath *path; + if ([feature isKindOfClass:[MLNPointFeature class]]) { + CGPoint center = [self convertCoordinate:feature.coordinate toPointToView:self]; + CGRect annotationFrame = + CGRectInset({center, CGSizeZero}, -MLNAnnotationAccessibilityElementMinimumSize.width / 2, + -MLNAnnotationAccessibilityElementMinimumSize.width / 2); + CGRect screenRect = UIAccessibilityConvertFrameToScreenCoordinates(annotationFrame, self); + element.accessibilityFrame = screenRect; + } else if ([feature isKindOfClass:[MLNPolylineFeature class]]) { + path = [self pathOfPolyline:(MLNPolyline *)feature]; + } else if ([feature isKindOfClass:[MLNMultiPolylineFeature class]]) { + path = [UIBezierPath bezierPath]; + for (MLNPolyline *polyline in [(MLNMultiPolylineFeature *)feature polylines]) { + [path appendPath:[self pathOfPolyline:polyline]]; } + } - if (path) - { - CGPathRef strokedCGPath = CGPathCreateCopyByStrokingPath(path.CGPath, NULL, MLNAnnotationAccessibilityElementMinimumSize.width, kCGLineCapButt, kCGLineJoinMiter, 0); - UIBezierPath *strokedPath = [UIBezierPath bezierPathWithCGPath:strokedCGPath]; - CGPathRelease(strokedCGPath); - UIBezierPath *screenPath = UIAccessibilityConvertPathToScreenCoordinates(strokedPath, self); - element.accessibilityPath = screenPath; - } + if (path) { + CGPathRef strokedCGPath = CGPathCreateCopyByStrokingPath( + path.CGPath, NULL, MLNAnnotationAccessibilityElementMinimumSize.width, kCGLineCapButt, + kCGLineJoinMiter, 0); + UIBezierPath *strokedPath = [UIBezierPath bezierPathWithCGPath:strokedCGPath]; + CGPathRelease(strokedCGPath); + UIBezierPath *screenPath = UIAccessibilityConvertPathToScreenCoordinates(strokedPath, self); + element.accessibilityPath = screenPath; + } - [_featureAccessibilityElements addObject:element]; + [_featureAccessibilityElements addObject:element]; - return element; + return element; } -- (UIBezierPath *)pathOfPolyline:(MLNPolyline *)polyline -{ - CLLocationCoordinate2D *coordinates = polyline.coordinates; - NSUInteger pointCount = polyline.pointCount; - UIBezierPath *path = [UIBezierPath bezierPath]; - for (NSUInteger i = 0; i < pointCount; i++) - { - CGPoint point = [self convertCoordinate:coordinates[i] toPointToView:self]; - if (i) - { - [path addLineToPoint:point]; - } - else - { - [path moveToPoint:point]; - } +- (UIBezierPath *)pathOfPolyline:(MLNPolyline *)polyline { + CLLocationCoordinate2D *coordinates = polyline.coordinates; + NSUInteger pointCount = polyline.pointCount; + UIBezierPath *path = [UIBezierPath bezierPath]; + for (NSUInteger i = 0; i < pointCount; i++) { + CGPoint point = [self convertCoordinate:coordinates[i] toPointToView:self]; + if (i) { + [path addLineToPoint:point]; + } else { + [path moveToPoint:point]; } - return path; + } + return path; } -- (NSInteger)indexOfAccessibilityElement:(id)element -{ - if (self.calloutViewForSelectedAnnotation) - { - return [@[self.calloutViewForSelectedAnnotation, self.mapViewProxyAccessibilityElement] - indexOfObject:element]; - } +- (NSInteger)indexOfAccessibilityElement:(id)element { + if (self.calloutViewForSelectedAnnotation) { + return [@[ self.calloutViewForSelectedAnnotation, self.mapViewProxyAccessibilityElement ] + indexOfObject:element]; + } - // Compass - NSUInteger compassIndex = 0; - if (element == self.compassView) - { - return compassIndex; - } + // Compass + NSUInteger compassIndex = 0; + if (element == self.compassView) { + return compassIndex; + } - // User location annotation - NSRange userLocationAnnotationRange = NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView); - if (element == self.userLocationAnnotationView) - { - return userLocationAnnotationRange.location; - } + // User location annotation + NSRange userLocationAnnotationRange = + NSMakeRange(compassIndex + 1, !!self.userLocationAnnotationView); + if (element == self.userLocationAnnotationView) { + return userLocationAnnotationRange.location; + } - CGPoint centerPoint = self.contentCenter; - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } + CGPoint centerPoint = self.contentCenter; + if (self.userTrackingMode != MLNUserTrackingModeNone) { + centerPoint = self.userLocationAnnotationViewCenter; + } - // Visible annotations - std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; - NSRange visibleAnnotationRange = NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size()); - MLNAnnotationTag tag = MLNAnnotationTagNotFound; - if ([element isKindOfClass:[MLNAnnotationView class]]) - { - id annotation = [(MLNAnnotationView *)element annotation]; - tag = [self annotationTagForAnnotation:annotation]; - } - else if ([element isKindOfClass:[MLNAnnotationAccessibilityElement class]]) - { - tag = [(MLNAnnotationAccessibilityElement *)element tag]; - } + // Visible annotations + std::vector visibleAnnotations = [self annotationTagsInRect:self.bounds]; + NSRange visibleAnnotationRange = + NSMakeRange(NSMaxRange(userLocationAnnotationRange), visibleAnnotations.size()); + MLNAnnotationTag tag = MLNAnnotationTagNotFound; + if ([element isKindOfClass:[MLNAnnotationView class]]) { + id annotation = [(MLNAnnotationView *)element annotation]; + tag = [self annotationTagForAnnotation:annotation]; + } else if ([element isKindOfClass:[MLNAnnotationAccessibilityElement class]]) { + tag = [(MLNAnnotationAccessibilityElement *)element tag]; + } - if (tag != MLNAnnotationTagNotFound) - { - std::sort(visibleAnnotations.begin(), visibleAnnotations.end()); - std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { - CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; - CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; - CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self]; - CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return deltaA < deltaB; - }); + if (tag != MLNAnnotationTagNotFound) { + std::sort(visibleAnnotations.begin(), visibleAnnotations.end()); + std::sort(visibleAnnotations.begin(), visibleAnnotations.end(), + [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { + CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; + CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; + CGPoint pointA = [self convertCoordinate:coordinateA toPointToView:self]; + CGPoint pointB = [self convertCoordinate:coordinateB toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return deltaA < deltaB; + }); - auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(), tag); - if (foundElement == visibleAnnotations.end()) - { - return NSNotFound; - } - return visibleAnnotationRange.location + std::distance(visibleAnnotations.begin(), foundElement); + auto foundElement = std::find(visibleAnnotations.begin(), visibleAnnotations.end(), tag); + if (foundElement == visibleAnnotations.end()) { + return NSNotFound; } + return visibleAnnotationRange.location + + std::distance(visibleAnnotations.begin(), foundElement); + } - // Visible place features - NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures; - NSRange visiblePlaceFeatureRange = NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count); - if ([element isKindOfClass:[MLNPlaceFeatureAccessibilityElement class]]) - { - visiblePlaceFeatures = [visiblePlaceFeatures sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, id _Nonnull featureB) { - CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; - CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return [@(deltaA) compare:@(deltaB)]; + // Visible place features + NSArray *visiblePlaceFeatures = self.visiblePlaceFeatures; + NSRange visiblePlaceFeatureRange = + NSMakeRange(NSMaxRange(visibleAnnotationRange), visiblePlaceFeatures.count); + if ([element isKindOfClass:[MLNPlaceFeatureAccessibilityElement class]]) { + visiblePlaceFeatures = [visiblePlaceFeatures + sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, + id _Nonnull featureB) { + CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; + CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return [@(deltaA) compare:@(deltaB)]; }]; - id feature = [(MLNPlaceFeatureAccessibilityElement *)element feature]; - NSUInteger featureIndex = [visiblePlaceFeatures indexOfObject:feature]; - if (featureIndex == NSNotFound) - { - featureIndex = [visiblePlaceFeatures indexOfObjectPassingTest:^BOOL (id _Nonnull visibleFeature, NSUInteger idx, BOOL * _Nonnull stop) { - return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && [visibleFeature.identifier isEqual:feature.identifier]; - }]; - } - if (featureIndex == NSNotFound) - { - return NSNotFound; - } - return visiblePlaceFeatureRange.location + featureIndex; + id feature = [(MLNPlaceFeatureAccessibilityElement *)element feature]; + NSUInteger featureIndex = [visiblePlaceFeatures indexOfObject:feature]; + if (featureIndex == NSNotFound) { + featureIndex = [visiblePlaceFeatures + indexOfObjectPassingTest:^BOOL(id _Nonnull visibleFeature, NSUInteger idx, + BOOL *_Nonnull stop) { + return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && + [visibleFeature.identifier isEqual:feature.identifier]; + }]; } + if (featureIndex == NSNotFound) { + return NSNotFound; + } + return visiblePlaceFeatureRange.location + featureIndex; + } - // Visible road features - NSArray *visibleRoadFeatures = self.visibleRoadFeatures; - NSRange visibleRoadFeatureRange = NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count); - if ([element isKindOfClass:[MLNRoadFeatureAccessibilityElement class]]) - { - visibleRoadFeatures = [visibleRoadFeatures sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, id _Nonnull featureB) { - CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; - CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; - CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); - CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); - return [@(deltaA) compare:@(deltaB)]; + // Visible road features + NSArray *visibleRoadFeatures = self.visibleRoadFeatures; + NSRange visibleRoadFeatureRange = + NSMakeRange(NSMaxRange(visiblePlaceFeatureRange), visibleRoadFeatures.count); + if ([element isKindOfClass:[MLNRoadFeatureAccessibilityElement class]]) { + visibleRoadFeatures = [visibleRoadFeatures + sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull featureA, + id _Nonnull featureB) { + CGPoint pointA = [self convertCoordinate:featureA.coordinate toPointToView:self]; + CGPoint pointB = [self convertCoordinate:featureB.coordinate toPointToView:self]; + CGFloat deltaA = hypot(pointA.x - centerPoint.x, pointA.y - centerPoint.y); + CGFloat deltaB = hypot(pointB.x - centerPoint.x, pointB.y - centerPoint.y); + return [@(deltaA) compare:@(deltaB)]; }]; - id feature = [(MLNRoadFeatureAccessibilityElement *)element feature]; - NSUInteger featureIndex = [visibleRoadFeatures indexOfObject:feature]; - if (featureIndex == NSNotFound) - { - featureIndex = [visibleRoadFeatures indexOfObjectPassingTest:^BOOL (id _Nonnull visibleFeature, NSUInteger idx, BOOL * _Nonnull stop) { - return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && [visibleFeature.identifier isEqual:feature.identifier]; - }]; - } - if (featureIndex == NSNotFound) - { - return NSNotFound; - } - return visibleRoadFeatureRange.location + featureIndex; + id feature = [(MLNRoadFeatureAccessibilityElement *)element feature]; + NSUInteger featureIndex = [visibleRoadFeatures indexOfObject:feature]; + if (featureIndex == NSNotFound) { + featureIndex = [visibleRoadFeatures + indexOfObjectPassingTest:^BOOL(id _Nonnull visibleFeature, NSUInteger idx, + BOOL *_Nonnull stop) { + return visibleFeature.identifier && ![visibleFeature.identifier isEqual:@0] && + [visibleFeature.identifier isEqual:feature.identifier]; + }]; } - - // Attribution button - NSUInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange); - if (element == self.attributionButton) - { - return attributionButtonIndex; + if (featureIndex == NSNotFound) { + return NSNotFound; } + return visibleRoadFeatureRange.location + featureIndex; + } - return NSNotFound; + // Attribution button + NSUInteger attributionButtonIndex = NSMaxRange(visibleRoadFeatureRange); + if (element == self.attributionButton) { + return attributionButtonIndex; + } + + return NSNotFound; } -- (MLNMapViewProxyAccessibilityElement *)mapViewProxyAccessibilityElement -{ - if ( ! _mapViewProxyAccessibilityElement) - { - _mapViewProxyAccessibilityElement = [[MLNMapViewProxyAccessibilityElement alloc] initWithAccessibilityContainer:self]; - } - return _mapViewProxyAccessibilityElement; +- (MLNMapViewProxyAccessibilityElement *)mapViewProxyAccessibilityElement { + if (!_mapViewProxyAccessibilityElement) { + _mapViewProxyAccessibilityElement = + [[MLNMapViewProxyAccessibilityElement alloc] initWithAccessibilityContainer:self]; + } + return _mapViewProxyAccessibilityElement; } -- (void)accessibilityIncrement -{ - // Swipe up to zoom out. - [self accessibilityScaleBy:0.5]; +- (void)accessibilityIncrement { + // Swipe up to zoom out. + [self accessibilityScaleBy:0.5]; } -- (void)accessibilityDecrement -{ - // Swipe down to zoom in. - [self accessibilityScaleBy:2]; +- (void)accessibilityDecrement { + // Swipe down to zoom in. + [self accessibilityScaleBy:2]; } -- (void)accessibilityScaleBy:(double)scaleFactor -{ - CGPoint centerPoint = self.contentCenter; - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - centerPoint = self.userLocationAnnotationViewCenter; - } - double newZoom = round(self.zoomLevel) + log2(scaleFactor); - self.mbglMap.jumpTo(mbgl::CameraOptions() - .withZoom(newZoom) - .withAnchor(mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y })); - [self unrotateIfNeededForGesture]; +- (void)accessibilityScaleBy:(double)scaleFactor { + CGPoint centerPoint = self.contentCenter; + if (self.userTrackingMode != MLNUserTrackingModeNone) { + centerPoint = self.userLocationAnnotationViewCenter; + } + double newZoom = round(self.zoomLevel) + log2(scaleFactor); + self.mbglMap.jumpTo(mbgl::CameraOptions().withZoom(newZoom).withAnchor( + mbgl::ScreenCoordinate{centerPoint.x, centerPoint.y})); + [self unrotateIfNeededForGesture]; - _accessibilityValueAnnouncementIsPending = YES; + _accessibilityValueAnnouncementIsPending = YES; } // MARK: - Geography - -+ (NSSet *)keyPathsForValuesAffectingCenterCoordinate -{ - return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; ++ (NSSet *)keyPathsForValuesAffectingCenterCoordinate { + return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; } -- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated -{ - MLNLogDebug(@"Setting centerCoordinate: %@ animated: %@", MLNStringFromCLLocationCoordinate2D(coordinate), MLNStringFromBOOL(animated)); - [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated]; +- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated { + MLNLogDebug(@"Setting centerCoordinate: %@ animated: %@", + MLNStringFromCLLocationCoordinate2D(coordinate), MLNStringFromBOOL(animated)); + [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated]; +} + +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate { + MLNLogDebug(@"Setting centerCoordinate: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate)); + [self setCenterCoordinate:centerCoordinate animated:NO]; +} + +- (CLLocationCoordinate2D)centerCoordinate { + return MLNLocationCoordinate2DFromLatLng(*self.mbglMap.getCameraOptions().center); } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate -{ - MLNLogDebug(@"Setting centerCoordinate: %@", MLNStringFromCLLocationCoordinate2D(centerCoordinate)); - [self setCenterCoordinate:centerCoordinate animated:NO]; + zoomLevel:(double)zoomLevel + animated:(BOOL)animated { + MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f animated: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate), zoomLevel, + MLNStringFromBOOL(animated)); + [self setCenterCoordinate:centerCoordinate + zoomLevel:zoomLevel + direction:self.direction + animated:animated]; } -- (CLLocationCoordinate2D)centerCoordinate -{ - return MLNLocationCoordinate2DFromLatLng(*self.mbglMap.getCameraOptions().center); +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + zoomLevel:(double)zoomLevel + direction:(CLLocationDirection)direction + animated:(BOOL)animated { + MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate), zoomLevel, direction, + MLNStringFromBOOL(animated)); + [self setCenterCoordinate:centerCoordinate + zoomLevel:zoomLevel + direction:direction + animated:animated + completionHandler:nil]; } -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated -{ - MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f animated: %@", - MLNStringFromCLLocationCoordinate2D(centerCoordinate), - zoomLevel, - MLNStringFromBOOL(animated)); - [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated]; +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + zoomLevel:(double)zoomLevel + direction:(CLLocationDirection)direction + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@ " + @"completionHandler: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate), zoomLevel, direction, + MLNStringFromBOOL(animated), completion); + self.userTrackingMode = MLNUserTrackingModeNone; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; + + [self _setCenterCoordinate:centerCoordinate + edgePadding:self.contentInset + zoomLevel:zoomLevel + direction:direction + duration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil + completionHandler:completion]; } -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated -{ - MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@", - MLNStringFromCLLocationCoordinate2D(centerCoordinate), - zoomLevel, - direction, - MLNStringFromBOOL(animated)); - [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated completionHandler:nil]; -} - -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting centerCoordinate: %@ zoomLevel: %f direction: %f animated: %@ completionHandler: %@", - MLNStringFromCLLocationCoordinate2D(centerCoordinate), - zoomLevel, - direction, - MLNStringFromBOOL(animated), - completion); - self.userTrackingMode = MLNUserTrackingModeNone; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MLNAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion]; -} - -- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate edgePadding:(UIEdgeInsets)insets zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion -{ - if (!_mbglMap) - { - if (completion) - { - completion(); - } - return; +- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + edgePadding:(UIEdgeInsets)insets + zoomLevel:(double)zoomLevel + direction:(CLLocationDirection)direction + duration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + completionHandler:(nullable void (^)(void))completion { + if (!_mbglMap) { + if (completion) { + completion(); } + return; + } - mbgl::CameraOptions cameraOptions; - cameraOptions.center = MLNLatLngFromLocationCoordinate2D(centerCoordinate); - cameraOptions.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - cameraOptions.zoom = zoomLevel; - if (direction >= 0) - { - cameraOptions.bearing = direction; + mbgl::CameraOptions cameraOptions; + cameraOptions.center = MLNLatLngFromLocationCoordinate2D(centerCoordinate); + cameraOptions.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + cameraOptions.zoom = zoomLevel; + if (direction >= 0) { + cameraOptions.bearing = direction; + } + + mbgl::AnimationOptions animationOptions; + if (duration) { + animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); + } + + dispatch_block_t pendingCompletion; + + if (completion) { + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) { + completion(); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + // Must run asynchronously after the transition is completely over. + // Otherwise, a call to -setCenterCoordinate: within the completion + // handler would reenter the completion handler’s caller. + + dispatch_async(dispatch_get_main_queue(), pendingCompletion); + }; + } + + MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; + if ([self.camera isEqualToMapCamera:camera] && + UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) { + if (pendingCompletion) { + [self animateWithDelay:duration animations:pendingCompletion]; } + return; + } - mbgl::AnimationOptions animationOptions; - if (duration) - { - animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); - animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); - } + [self cancelTransitions]; - dispatch_block_t pendingCompletion; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - if (completion) - { - __weak __typeof__(self) weakSelf = self; - - pendingCompletion = ^{ - if (![weakSelf scheduleTransitionCompletion:completion]) - { - completion(); - } - }; - - animationOptions.transitionFinishFn = [pendingCompletion]() { - // Must run asynchronously after the transition is completely over. - // Otherwise, a call to -setCenterCoordinate: within the completion - // handler would reenter the completion handler’s caller. - - dispatch_async(dispatch_get_main_queue(), pendingCompletion); - }; - } - - MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; - if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) - { - if (pendingCompletion) - { - [self animateWithDelay:duration animations:pendingCompletion]; - } - return; - } - - [self cancelTransitions]; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - self.mbglMap.easeTo(cameraOptions, animationOptions); + self.mbglMap.easeTo(cameraOptions, animationOptions); } -+ (NSSet *)keyPathsForValuesAffectingZoomLevel -{ - return [NSSet setWithObject:@"camera"]; ++ (NSSet *)keyPathsForValuesAffectingZoomLevel { + return [NSSet setWithObject:@"camera"]; } -- (double)zoomLevel -{ - return *self.mbglMap.getCameraOptions().zoom; +- (double)zoomLevel { + return *self.mbglMap.getCameraOptions().zoom; } -- (void)setZoomLevel:(double)zoomLevel -{ - MLNLogDebug(@"Setting zoomLevel: %f", zoomLevel); - [self setZoomLevel:zoomLevel animated:NO]; +- (void)setZoomLevel:(double)zoomLevel { + MLNLogDebug(@"Setting zoomLevel: %f", zoomLevel); + [self setZoomLevel:zoomLevel animated:NO]; } -- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated -{ - MLNLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MLNStringFromBOOL(animated)); - if (zoomLevel == self.zoomLevel) return; - [self cancelTransitions]; +- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated { + MLNLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MLNStringFromBOOL(animated)); + if (zoomLevel == self.zoomLevel) return; + [self cancelTransitions]; - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - CGFloat duration = animated ? MLNAnimationDuration : 0; + CGFloat duration = animated ? MLNAnimationDuration : 0; - self.mbglMap.easeTo(mbgl::CameraOptions() - .withZoom(zoomLevel), MLNDurationFromTimeInterval(duration)); + self.mbglMap.easeTo(mbgl::CameraOptions().withZoom(zoomLevel), + MLNDurationFromTimeInterval(duration)); } -- (void)setMinimumZoomLevel:(double)minimumZoomLevel -{ - MLNLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel); - self.mbglMap.setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); +- (void)setMinimumZoomLevel:(double)minimumZoomLevel { + MLNLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel); + self.mbglMap.setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); } -- (double)minimumZoomLevel -{ - return *self.mbglMap.getBounds().minZoom; +- (double)minimumZoomLevel { + return *self.mbglMap.getBounds().minZoom; } -- (void)setMaximumZoomLevel:(double)maximumZoomLevel -{ - MLNLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel); - self.mbglMap.setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel)); +- (void)setMaximumZoomLevel:(double)maximumZoomLevel { + MLNLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel); + self.mbglMap.setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel)); } -- (double)maximumZoomLevel -{ - return *self.mbglMap.getBounds().maxZoom; +- (double)maximumZoomLevel { + return *self.mbglMap.getBounds().maxZoom; } -- (void)setMaximumScreenBounds:(MLNCoordinateBounds)maximumScreenBounds -{ - mbgl::LatLng sw = {maximumScreenBounds.sw.latitude, maximumScreenBounds.sw.longitude}; - mbgl::LatLng ne = {maximumScreenBounds.ne.latitude, maximumScreenBounds.ne.longitude}; - mbgl::BoundOptions newBounds = mbgl::BoundOptions().withLatLngBounds(mbgl::LatLngBounds::hull(sw, ne)); +- (void)setMaximumScreenBounds:(MLNCoordinateBounds)maximumScreenBounds { + mbgl::LatLng sw = {maximumScreenBounds.sw.latitude, maximumScreenBounds.sw.longitude}; + mbgl::LatLng ne = {maximumScreenBounds.ne.latitude, maximumScreenBounds.ne.longitude}; + mbgl::BoundOptions newBounds = + mbgl::BoundOptions().withLatLngBounds(mbgl::LatLngBounds::hull(sw, ne)); - self.mbglMap.setBounds(newBounds); - self.mbglMap.setConstrainMode(mbgl::ConstrainMode::Screen); + self.mbglMap.setBounds(newBounds); + self.mbglMap.setConstrainMode(mbgl::ConstrainMode::Screen); } -- (MLNCoordinateBounds)maximumScreenBounds -{ - return MLNCoordinateBoundsFromLatLngBounds(*self.mbglMap.getBounds().bounds);; +- (MLNCoordinateBounds)maximumScreenBounds { + return MLNCoordinateBoundsFromLatLngBounds(*self.mbglMap.getBounds().bounds); + ; } -- (CGFloat)minimumPitch -{ - return *self.mbglMap.getBounds().minPitch; +- (CGFloat)minimumPitch { + return *self.mbglMap.getBounds().minPitch; } -- (void)setMinimumPitch:(CGFloat)minimumPitch -{ - MLNLogDebug(@"Setting minimumPitch: %f", minimumPitch); - self.mbglMap.setBounds(mbgl::BoundOptions().withMinPitch(minimumPitch)); +- (void)setMinimumPitch:(CGFloat)minimumPitch { + MLNLogDebug(@"Setting minimumPitch: %f", minimumPitch); + self.mbglMap.setBounds(mbgl::BoundOptions().withMinPitch(minimumPitch)); } -- (CGFloat)maximumPitch -{ - return *self.mbglMap.getBounds().maxPitch; +- (CGFloat)maximumPitch { + return *self.mbglMap.getBounds().maxPitch; } -- (void)setMaximumPitch:(CGFloat)maximumPitch -{ - MLNLogDebug(@"Setting maximumPitch: %f", maximumPitch); - self.mbglMap.setBounds(mbgl::BoundOptions().withMaxPitch(maximumPitch)); +- (void)setMaximumPitch:(CGFloat)maximumPitch { + MLNLogDebug(@"Setting maximumPitch: %f", maximumPitch); + self.mbglMap.setBounds(mbgl::BoundOptions().withMaxPitch(maximumPitch)); } -- (MLNCoordinateBounds)visibleCoordinateBounds -{ - return [self convertRect:self.bounds toCoordinateBoundsFromView:self]; +- (MLNCoordinateBounds)visibleCoordinateBounds { + return [self convertRect:self.bounds toCoordinateBoundsFromView:self]; +} + +- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds { + [self setVisibleCoordinateBounds:bounds animated:NO]; +} + +- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds animated:(BOOL)animated { + [self setVisibleCoordinateBounds:bounds + edgePadding:UIEdgeInsetsZero + animated:animated + completionHandler:nil]; } - (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds -{ - [self setVisibleCoordinateBounds:bounds animated:NO]; + edgePadding:(UIEdgeInsets)insets + animated:(BOOL)animated { + [self setVisibleCoordinateBounds:bounds + edgePadding:insets + animated:animated + completionHandler:nil]; } -- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds animated:(BOOL)animated -{ - [self setVisibleCoordinateBounds:bounds edgePadding:UIEdgeInsetsZero animated:animated completionHandler:nil]; +- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(UIEdgeInsets)insets + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting visibleCoordinateBounds: %@ edgePadding: %@ animated: %@", + MLNStringFromCoordinateBounds(bounds), NSStringFromUIEdgeInsets(insets), + MLNStringFromBOOL(animated)); + CLLocationCoordinate2D coordinates[] = { + {bounds.ne.latitude, bounds.sw.longitude}, + bounds.sw, + {bounds.sw.latitude, bounds.ne.longitude}, + bounds.ne, + }; + [self setVisibleCoordinates:coordinates + count:sizeof(coordinates) / sizeof(coordinates[0]) + edgePadding:insets + direction:self.direction + duration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil + completionHandler:completion]; } -- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated -{ - [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil]; +- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + edgePadding:(UIEdgeInsets)insets + animated:(BOOL)animated { + MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ animated: %@", count, + NSStringFromUIEdgeInsets(insets), MLNStringFromBOOL(animated)); + [self setVisibleCoordinates:coordinates + count:count + edgePadding:insets + direction:self.direction + duration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil]; } -- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting visibleCoordinateBounds: %@ edgePadding: %@ animated: %@", - MLNStringFromCoordinateBounds(bounds), - NSStringFromUIEdgeInsets(insets), - MLNStringFromBOOL(animated)); - CLLocationCoordinate2D coordinates[] = { - {bounds.ne.latitude, bounds.sw.longitude}, - bounds.sw, - {bounds.sw.latitude, bounds.ne.longitude}, - bounds.ne, +- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + edgePadding:(UIEdgeInsets)insets + direction:(CLLocationDirection)direction + duration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function { + MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f " + @"animationTimingFunction: %@", + count, NSStringFromUIEdgeInsets(insets), direction, duration, function); + [self setVisibleCoordinates:coordinates + count:count + edgePadding:insets + direction:direction + duration:duration + animationTimingFunction:function + completionHandler:NULL]; +} + +- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + edgePadding:(UIEdgeInsets)insets + direction:(CLLocationDirection)direction + duration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f " + @"animationTimingFunction: %@ completionHandler: %@", + count, NSStringFromUIEdgeInsets(insets), direction, duration, function, completion); + self.userTrackingMode = MLNUserTrackingModeNone; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; + + [self _setVisibleCoordinates:coordinates + count:count + edgePadding:insets + direction:direction + duration:duration + animationTimingFunction:function + completionHandler:completion]; +} + +- (void)_setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates + count:(NSUInteger)count + edgePadding:(UIEdgeInsets)insets + direction:(CLLocationDirection)direction + duration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + completionHandler:(nullable void (^)(void))completion { + if (!_mbglMap) { + if (completion) { + completion(); + } + return; + } + + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + std::vector latLngs; + latLngs.reserve(count); + for (NSUInteger i = 0; i < count; i++) { + latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude}); + } + + CLLocationDirection cameraDirection = direction >= 0 ? direction : self.direction; + + mbgl::CameraOptions cameraOptions = + self.mbglMap.cameraForLatLngs(latLngs, padding, cameraDirection); + + mbgl::AnimationOptions animationOptions; + if (duration > 0) { + animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); + } + + dispatch_block_t pendingCompletion; + + if (completion) { + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) { + completion(); + } }; - [self setVisibleCoordinates:coordinates - count:sizeof(coordinates) / sizeof(coordinates[0]) - edgePadding:insets - direction:self.direction - duration:animated ? MLNAnimationDuration : 0 - animationTimingFunction:nil - completionHandler:completion]; -} -- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated -{ - MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ animated: %@", - count, - NSStringFromUIEdgeInsets(insets), - MLNStringFromBOOL(animated)); - [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction duration:animated ? MLNAnimationDuration : 0 animationTimingFunction:nil]; -} + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); + }; + } -- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function { - MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f animationTimingFunction: %@", - count, - NSStringFromUIEdgeInsets(insets), - direction, - duration, - function); - [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:NULL]; -} - -- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting: %lu coordinates edgePadding: %@ direction: %f duration: %f animationTimingFunction: %@ completionHandler: %@", count, NSStringFromUIEdgeInsets(insets), direction, duration, function, completion); - self.userTrackingMode = MLNUserTrackingModeNone; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion]; -} - -- (void)_setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion -{ - if (!_mbglMap) - { - if (completion) - { - completion(); - } - return; + // Padding is baked in adjusted camera center (in cameraForLatLngs) and + // cameraOptions.padding at this point is (0, 0, 0, 0) and we don't need to + // check if cameraOptions.contentInsets are equal to contentInsets. + MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; + if ([self.camera isEqualToMapCamera:camera]) { + if (pendingCompletion) { + [self animateWithDelay:duration animations:pendingCompletion]; } + return; + } - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); - std::vector latLngs; - latLngs.reserve(count); - for (NSUInteger i = 0; i < count; i++) - { - latLngs.push_back({coordinates[i].latitude, coordinates[i].longitude}); - } + [self willChangeValueForKey:@"visibleCoordinateBounds"]; + [self cancelTransitions]; - CLLocationDirection cameraDirection = direction >= 0 ? direction : self.direction; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngs(latLngs, padding, cameraDirection); + self.mbglMap.easeTo(cameraOptions, animationOptions); + [self didChangeValueForKey:@"visibleCoordinateBounds"]; +} + ++ (NSSet *)keyPathsForValuesAffectingDirection { + return [NSSet setWithObject:@"camera"]; +} + +- (CLLocationDirection)direction { + return mbgl::util::wrap(*self.mbglMap.getCameraOptions().bearing, 0., 360.); +} + +- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated { + MLNLogDebug(@"Setting direction: %f animated: %@", direction, MLNStringFromBOOL(animated)); + if (!animated && !self.rotationAllowed) return; + + if (self.userTrackingMode == MLNUserTrackingModeFollowWithHeading || + self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) { + self.userTrackingMode = MLNUserTrackingModeFollow; + } + + [self _setDirection:direction center:kCLLocationCoordinate2DInvalid animated:animated]; +} + +- (void)_setDirection:(CLLocationDirection)direction + center:(CLLocationCoordinate2D)center + animated:(BOOL)animated { + if (!_mbglMap) { + return; + } + + if (direction == self.direction) return; + [self cancelTransitions]; + + CGFloat duration = animated ? MLNAnimationDuration : 0; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; + + if (self.userTrackingMode == MLNUserTrackingModeNone) { + self.mbglMap.easeTo(mbgl::CameraOptions().withBearing(direction), + MLNDurationFromTimeInterval(duration)); + } else { + CGPoint anchor = self.userLocationAnnotationViewCenter; + + mbgl::CameraOptions cameraOptions = mbgl::CameraOptions().withBearing(direction).withAnchor( + mbgl::ScreenCoordinate{anchor.x, anchor.y}); mbgl::AnimationOptions animationOptions; - if (duration > 0) - { - animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + + if (CLLocationCoordinate2DIsValid(center)) { + cameraOptions.center = MLNLatLngFromLocationCoordinate2D(center); + + if (duration) { + CAMediaTimingFunction *function = + [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); + } } - dispatch_block_t pendingCompletion; - - if (completion) - { - __weak __typeof__(self) weakSelf = self; - - pendingCompletion = ^{ - if (![weakSelf scheduleTransitionCompletion:completion]) - { - completion(); - } - }; - - animationOptions.transitionFinishFn = [pendingCompletion]() { - dispatch_async(dispatch_get_main_queue(), pendingCompletion); - }; - } - - // Padding is baked in adjusted camera center (in cameraForLatLngs) and - // cameraOptions.padding at this point is (0, 0, 0, 0) and we don't need to - // check if cameraOptions.contentInsets are equal to contentInsets. - MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; - if ([self.camera isEqualToMapCamera:camera]) - { - if (pendingCompletion) - { - [self animateWithDelay:duration animations:pendingCompletion]; - } - return; - } - - [self willChangeValueForKey:@"visibleCoordinateBounds"]; - [self cancelTransitions]; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - self.mbglMap.easeTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"visibleCoordinateBounds"]; + } } -+ (NSSet *)keyPathsForValuesAffectingDirection -{ - return [NSSet setWithObject:@"camera"]; +- (void)setDirection:(CLLocationDirection)direction { + MLNLogDebug(@"Setting direction: %f", direction); + [self setDirection:direction animated:NO]; } -- (CLLocationDirection)direction -{ - return mbgl::util::wrap(*self.mbglMap.getCameraOptions().bearing, 0., 360.); ++ (NSSet *)keyPathsForValuesAffectingPitch { + return [NSSet setWithObject:@"camera"]; } -- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated -{ - MLNLogDebug(@"Setting direction: %f animated: %@", direction, MLNStringFromBOOL(animated)); - if ( ! animated && ! self.rotationAllowed) return; - - if (self.userTrackingMode == MLNUserTrackingModeFollowWithHeading || - self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) - { - self.userTrackingMode = MLNUserTrackingModeFollow; - } - - [self _setDirection:direction center:kCLLocationCoordinate2DInvalid animated:animated]; -} - -- (void)_setDirection:(CLLocationDirection)direction center:(CLLocationCoordinate2D)center animated:(BOOL)animated -{ - if (!_mbglMap) - { - return; - } - - if (direction == self.direction) return; - [self cancelTransitions]; - - CGFloat duration = animated ? MLNAnimationDuration : 0; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - if (self.userTrackingMode == MLNUserTrackingModeNone) - { - self.mbglMap.easeTo(mbgl::CameraOptions().withBearing(direction), - MLNDurationFromTimeInterval(duration)); - } - else - { - CGPoint anchor = self.userLocationAnnotationViewCenter; - - mbgl::CameraOptions cameraOptions = mbgl::CameraOptions() - .withBearing(direction) - .withAnchor(mbgl::ScreenCoordinate { anchor.x, anchor.y }); - - mbgl::AnimationOptions animationOptions; - animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); - - if (CLLocationCoordinate2DIsValid(center)) - { - cameraOptions.center = MLNLatLngFromLocationCoordinate2D(center); - - if (duration) - { - CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; - animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); - } - } - - self.mbglMap.easeTo(cameraOptions, animationOptions); - } -} - -- (void)setDirection:(CLLocationDirection)direction -{ - MLNLogDebug(@"Setting direction: %f", direction); - [self setDirection:direction animated:NO]; -} - -+ (NSSet *)keyPathsForValuesAffectingPitch -{ - return [NSSet setWithObject:@"camera"]; -} - -+ (NSSet *)keyPathsForValuesAffectingCamera -{ - return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil]; ++ (NSSet *)keyPathsForValuesAffectingCamera { + return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", + @"direction", nil]; } - (UIEdgeInsets)cameraEdgeInsets { - mbgl::CameraOptions cameraOptions = self.mbglMap.getCameraOptions(); - return NSEdgeInsetsFromMLNEdgeInsets(cameraOptions.padding.value_or(mbgl::EdgeInsets())); + mbgl::CameraOptions cameraOptions = self.mbglMap.getCameraOptions(); + return NSEdgeInsetsFromMLNEdgeInsets(cameraOptions.padding.value_or(mbgl::EdgeInsets())); } -- (MLNMapCamera *)camera -{ - if (!_mbglMap) - { - NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); - return self.residualCamera; - } +- (MLNMapCamera *)camera { + if (!_mbglMap) { + NSAssert(self.terminated, @"_mbglMap should only be unavailable during app termination"); + return self.residualCamera; + } - return [self cameraForCameraOptions:self.mbglMap.getCameraOptions()]; + return [self cameraForCameraOptions:self.mbglMap.getCameraOptions()]; +} + +- (void)setCamera:(MLNMapCamera *)camera { + MLNLogDebug(@"Setting camera: %@", camera); + [self setCamera:camera animated:NO]; +} + +- (void)setCamera:(MLNMapCamera *)camera animated:(BOOL)animated { + MLNLogDebug(@"Setting camera: %@ animated: %@", camera, MLNStringFromBOOL(animated)); + [self setCamera:camera + withDuration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil]; } - (void)setCamera:(MLNMapCamera *)camera -{ - MLNLogDebug(@"Setting camera: %@", camera); - [self setCamera:camera animated:NO]; + withDuration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function { + MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, + function); + [self setCamera:camera + withDuration:duration + animationTimingFunction:function + completionHandler:nil]; } -- (void)setCamera:(MLNMapCamera *)camera animated:(BOOL)animated -{ - MLNLogDebug(@"Setting camera: %@ animated: %@", camera, MLNStringFromBOOL(animated)); - [self setCamera:camera withDuration:animated ? MLNAnimationDuration : 0 animationTimingFunction:nil]; +- (void)setCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ completionHandler: %@", + camera, duration, function, completion); + [self setCamera:camera + withDuration:duration + animationTimingFunction:function + edgePadding:UIEdgeInsetsZero + completionHandler:completion]; } -- (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function -{ - MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, function); - [self setCamera:camera withDuration:duration animationTimingFunction:function completionHandler:nil]; +- (void)setCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + edgePadding:(UIEdgeInsets)edgePadding + completionHandler:(nullable void (^)(void))completion { + if (!_mbglMap) { + if (completion) { + completion(); + } + return; + } + + MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ edgePadding: %@ " + @"completionHandler: %@", + camera, duration, function, NSStringFromUIEdgeInsets(edgePadding), completion); + + edgePadding = MLNEdgeInsetsInsetEdgeInset(edgePadding, self.contentInset); + + mbgl::AnimationOptions animationOptions; + if (duration > 0) { + animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); + } + + dispatch_block_t pendingCompletion; + + if (completion) { + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) { + completion(); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); + }; + } + + if ([self.camera isEqualToMapCamera:camera] && + UIEdgeInsetsEqualToEdgeInsets(_contentInset, edgePadding)) { + if (pendingCompletion) { + [self animateWithDelay:duration animations:pendingCompletion]; + } + return; + } + + [self willChangeValueForKey:@"camera"]; + [self cancelTransitions]; + + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; + + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera + edgePadding:edgePadding]; + self.mbglMap.easeTo(cameraOptions, animationOptions); + [self didChangeValueForKey:@"camera"]; } -- (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ completionHandler: %@", camera, duration, function, completion); - [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:UIEdgeInsetsZero completionHandler:completion]; +- (void)flyToCamera:(MLNMapCamera *)camera completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion); + [self flyToCamera:camera withDuration:-1 completionHandler:completion]; } -- (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { - if (!_mbglMap) - { - if (completion) - { - completion(); - } - return; - } - - MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@ edgePadding: %@ completionHandler: %@", camera, duration, function, NSStringFromUIEdgeInsets(edgePadding), completion); - - edgePadding = MLNEdgeInsetsInsetEdgeInset(edgePadding, self.contentInset); - - mbgl::AnimationOptions animationOptions; - if (duration > 0) - { - animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); - animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); - } - - dispatch_block_t pendingCompletion; - - if (completion) - { - __weak __typeof__(self) weakSelf = self; - - pendingCompletion = ^{ - if (![weakSelf scheduleTransitionCompletion:completion]) - { - completion(); - } - }; - - animationOptions.transitionFinishFn = [pendingCompletion]() { - dispatch_async(dispatch_get_main_queue(), pendingCompletion); - }; - } - - if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, edgePadding)) - { - if (pendingCompletion) - { - [self animateWithDelay:duration animations:pendingCompletion]; - } - return; - } - - [self willChangeValueForKey:@"camera"]; - [self cancelTransitions]; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; - self.mbglMap.easeTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; +- (void)flyToCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, + completion); + [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion]; } -- (void)flyToCamera:(MLNMapCamera *)camera completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion); - [self flyToCamera:camera withDuration:-1 completionHandler:completion]; +- (void)flyToCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + peakAltitude:(CLLocationDistance)peakAltitude + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", + camera, duration, peakAltitude, completion); + [self _flyToCamera:camera + edgePadding:self.contentInset + withDuration:duration + peakAltitude:peakAltitude + completionHandler:completion]; } -- (void)flyToCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, completion); - [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion]; +- (void)flyToCamera:(MLNMapCamera *)camera + edgePadding:(UIEdgeInsets)insets + withDuration:(NSTimeInterval)duration + completionHandler:(nullable void (^)(void))completion { + UIEdgeInsets finalEdgeInsets = UIEdgeInsetsMake( + self.contentInset.top + insets.top, self.contentInset.left + insets.left, + self.contentInset.bottom + insets.bottom, self.contentInset.right + insets.right); + [self _flyToCamera:camera + edgePadding:finalEdgeInsets + withDuration:duration + peakAltitude:-1 + completionHandler:completion]; } -- (void)flyToCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", camera, duration, peakAltitude, completion); - [self _flyToCamera:camera edgePadding:self.contentInset withDuration:duration peakAltitude:peakAltitude completionHandler:completion]; -} - -- (void)flyToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion { - UIEdgeInsets finalEdgeInsets = UIEdgeInsetsMake(self.contentInset.top + insets.top, - self.contentInset.left + insets.left, - self.contentInset.bottom + insets.bottom, - self.contentInset.right + insets.right); - [self _flyToCamera:camera edgePadding:finalEdgeInsets withDuration:duration peakAltitude:-1 completionHandler:completion]; -} - -- (void)_flyToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion -{ - if (!_mbglMap) - { - if (completion) - { - completion(); - } - return; +- (void)_flyToCamera:(MLNMapCamera *)camera + edgePadding:(UIEdgeInsets)insets + withDuration:(NSTimeInterval)duration + peakAltitude:(CLLocationDistance)peakAltitude + completionHandler:(nullable void (^)(void))completion { + if (!_mbglMap) { + if (completion) { + completion(); } + return; + } - mbgl::AnimationOptions animationOptions; - if (duration >= 0) - { - animationOptions.duration = MLNDurationFromTimeInterval(duration); - } - if (peakAltitude >= 0) - { - CLLocationDegrees peakLatitude = (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2; - CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2; - animationOptions.minZoom = MLNZoomLevelForAltitude(peakAltitude, peakPitch, - peakLatitude, self.frame.size); + mbgl::AnimationOptions animationOptions; + if (duration >= 0) { + animationOptions.duration = MLNDurationFromTimeInterval(duration); + } + if (peakAltitude >= 0) { + CLLocationDegrees peakLatitude = + (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2; + CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2; + animationOptions.minZoom = + MLNZoomLevelForAltitude(peakAltitude, peakPitch, peakLatitude, self.frame.size); + } + + dispatch_block_t pendingCompletion; + + if (completion) { + __weak __typeof__(self) weakSelf = self; + + pendingCompletion = ^{ + if (![weakSelf scheduleTransitionCompletion:completion]) { + completion(); + } + }; + + animationOptions.transitionFinishFn = [pendingCompletion]() { + dispatch_async(dispatch_get_main_queue(), pendingCompletion); + }; + } + if ([self.camera isEqualToMapCamera:camera] && + UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) { + if (pendingCompletion) { + [self animateWithDelay:duration animations:pendingCompletion]; } + return; + } - dispatch_block_t pendingCompletion; + [self willChangeValueForKey:@"camera"]; + [self cancelTransitions]; - if (completion) - { - __weak __typeof__(self) weakSelf = self; + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - pendingCompletion = ^{ - if (![weakSelf scheduleTransitionCompletion:completion]) - { - completion(); - } - }; - - animationOptions.transitionFinishFn = [pendingCompletion]() { - dispatch_async(dispatch_get_main_queue(), pendingCompletion); - }; - } - if ([self.camera isEqualToMapCamera:camera] && UIEdgeInsetsEqualToEdgeInsets(_contentInset, insets)) - { - if (pendingCompletion) - { - [self animateWithDelay:duration animations:pendingCompletion]; - } - return; - } - - [self willChangeValueForKey:@"camera"]; - [self cancelTransitions]; - - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonProgrammatic; - - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; - self.mbglMap.flyTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera + edgePadding:insets]; + self.mbglMap.flyTo(cameraOptions, animationOptions); + [self didChangeValueForKey:@"camera"]; } - (void)cancelTransitions { - if (!_mbglMap) - { - return; - } - self.cameraChangeReasonBitmask |= MLNCameraChangeReasonTransitionCancelled; - self.mbglMap.cancelTransitions(); - self.cameraChangeReasonBitmask &= ~MLNCameraChangeReasonTransitionCancelled; + if (!_mbglMap) { + return; + } + self.cameraChangeReasonBitmask |= MLNCameraChangeReasonTransitionCancelled; + self.mbglMap.cancelTransitions(); + self.cameraChangeReasonBitmask &= ~MLNCameraChangeReasonTransitionCancelled; +} + +- (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds { + return [self cameraThatFitsCoordinateBounds:bounds edgePadding:UIEdgeInsetsZero]; } - (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds -{ - return [self cameraThatFitsCoordinateBounds:bounds edgePadding:UIEdgeInsetsZero]; + edgePadding:(UIEdgeInsets)insets { + if (!_mbglMap) { + return self.residualCamera; + } + + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + mbgl::CameraOptions cameraOptions = + self.mbglMap.cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets -{ - if (!_mbglMap) - { - return self.residualCamera; - } +- (MLNMapCamera *)camera:(MLNMapCamera *)camera + fittingCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(UIEdgeInsets)insets { + if (!_mbglMap) { + return self.residualCamera; + } - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); - mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); - return [self cameraForCameraOptions:cameraOptions]; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + + MLNMapCamera *currentCamera = self.camera; + CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; + CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + + mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngBounds( + MLNLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)camera:(MLNMapCamera *)camera fittingCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets -{ - if (!_mbglMap) - { - return self.residualCamera; - } +- (MLNMapCamera *)camera:(MLNMapCamera *)camera + fittingShape:(MLNShape *)shape + edgePadding:(UIEdgeInsets)insets { + if (!_mbglMap) { + return self.residualCamera; + } - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); - MLNMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + MLNMapCamera *currentCamera = self.camera; + CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; + CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; - mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); - return [self cameraForCameraOptions:cameraOptions]; + mbgl::CameraOptions cameraOptions = + self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction, pitch); + + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)camera:(MLNMapCamera *)camera fittingShape:(MLNShape *)shape edgePadding:(UIEdgeInsets)insets { - if (!_mbglMap) - { - return self.residualCamera; - } +- (MLNMapCamera *)cameraThatFitsShape:(MLNShape *)shape + direction:(CLLocationDirection)direction + edgePadding:(UIEdgeInsets)insets { + if (!_mbglMap) { + return self.residualCamera; + } - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); - MLNMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + mbgl::CameraOptions cameraOptions = + self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction); - mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction, pitch); - - return [self cameraForCameraOptions: cameraOptions]; + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)cameraThatFitsShape:(MLNShape *)shape direction:(CLLocationDirection)direction edgePadding:(UIEdgeInsets)insets { - if (!_mbglMap) - { - return self.residualCamera; - } +- (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions { + if (!_mbglMap) { + return self.residualCamera; + } - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInset); + mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions(); + CLLocationCoordinate2D centerCoordinate = MLNLocationCoordinate2DFromLatLng( + cameraOptions.center ? *cameraOptions.center : *mapCamera.center); + double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; + CLLocationDirection direction = + cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction; + CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch; + CLLocationDistance altitude = + MLNAltitudeForZoomLevel(zoomLevel, pitch, centerCoordinate.latitude, self.frame.size); - mbgl::CameraOptions cameraOptions = self.mbglMap.cameraForGeometry([shape geometryObject], padding, direction); + auto camera = [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + altitude:altitude + pitch:pitch + heading:direction]; - return [self cameraForCameraOptions:cameraOptions]; -} + if (cameraOptions.roll) { + camera.roll = cameraOptions.roll.value(); + } -- (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions -{ - if (!_mbglMap) - { - return self.residualCamera; - } - - mbgl::CameraOptions mapCamera = self.mbglMap.getCameraOptions(); - CLLocationCoordinate2D centerCoordinate = MLNLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center); - double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; - CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction; - CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch; - CLLocationDistance altitude = MLNAltitudeForZoomLevel(zoomLevel, pitch, centerCoordinate.latitude, self.frame.size); - - auto camera = [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate altitude:altitude pitch:pitch heading:direction]; - - if (cameraOptions.roll) { - camera.roll = cameraOptions.roll.value(); - } - - return camera; + return camera; } /// Returns a CameraOptions object that specifies parameters for animating to /// the given camera. -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets -{ - mbgl::CameraOptions options; - if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) - { - options.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); - } - options.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - options.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, - camera.centerCoordinate.latitude, - self.frame.size); - if (camera.heading >= 0) - { - options.bearing = camera.heading; - } - if (camera.pitch >= 0) - { - options.pitch = camera.pitch; - } - options.roll = camera.roll; - return options; +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera + edgePadding:(UIEdgeInsets)insets { + mbgl::CameraOptions options; + if (CLLocationCoordinate2DIsValid(camera.centerCoordinate)) { + options.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); + } + options.padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + options.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, + camera.centerCoordinate.latitude, self.frame.size); + if (camera.heading >= 0) { + options.bearing = camera.heading; + } + if (camera.pitch >= 0) { + options.pitch = camera.pitch; + } + options.roll = camera.roll; + return options; } -- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view -{ - return MLNLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]); +- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view { + return MLNLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]); } /// Converts a point in the view’s coordinate system to a geographic coordinate. -- (mbgl::LatLng)convertPoint:(CGPoint)point toLatLngFromView:(nullable UIView *)view -{ - CGPoint convertedPoint = [self convertPoint:point fromView:view]; - return self.mbglMap.latLngForPixel(mbgl::ScreenCoordinate(convertedPoint.x, convertedPoint.y)).wrapped(); +- (mbgl::LatLng)convertPoint:(CGPoint)point toLatLngFromView:(nullable UIView *)view { + CGPoint convertedPoint = [self convertPoint:point fromView:view]; + return self.mbglMap.latLngForPixel(mbgl::ScreenCoordinate(convertedPoint.x, convertedPoint.y)) + .wrapped(); } -- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable UIView *)view -{ - if ( ! CLLocationCoordinate2DIsValid(coordinate)) - { - return CGPointMake(NAN, NAN); - } - return [self convertLatLng:MLNLatLngFromLocationCoordinate2D(coordinate) toPointToView:view]; +- (CGPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate + toPointToView:(nullable UIView *)view { + if (!CLLocationCoordinate2DIsValid(coordinate)) { + return CGPointMake(NAN, NAN); + } + return [self convertLatLng:MLNLatLngFromLocationCoordinate2D(coordinate) toPointToView:view]; } /// Converts a geographic coordinate to a point in the view’s coordinate system. -- (CGPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable UIView *)view -{ - mbgl::ScreenCoordinate pixel = self.mbglMap.pixelForLatLng(latLng); - return [self convertPoint:CGPointMake(pixel.x, pixel.y) toView:view]; +- (CGPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable UIView *)view { + mbgl::ScreenCoordinate pixel = self.mbglMap.pixelForLatLng(latLng); + return [self convertPoint:CGPointMake(pixel.x, pixel.y) toView:view]; } -- (MLNCoordinateBounds)convertRect:(CGRect)rect toCoordinateBoundsFromView:(nullable UIView *)view -{ - return MLNCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]); +- (MLNCoordinateBounds)convertRect:(CGRect)rect toCoordinateBoundsFromView:(nullable UIView *)view { + return MLNCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]); } -- (CGRect)convertCoordinateBounds:(MLNCoordinateBounds)bounds toRectToView:(nullable UIView *)view -{ - return [self convertLatLngBounds:MLNLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view]; +- (CGRect)convertCoordinateBounds:(MLNCoordinateBounds)bounds toRectToView:(nullable UIView *)view { + return [self convertLatLngBounds:MLNLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view]; } /// Converts a geographic bounding box to a rectangle in the view’s coordinate /// system. - (CGRect)convertLatLngBounds:(mbgl::LatLngBounds)bounds toRectToView:(nullable UIView *)view { - auto northwest = bounds.northwest(); - auto northeast = bounds.northeast(); - auto southwest = bounds.southwest(); - auto southeast = bounds.southeast(); + auto northwest = bounds.northwest(); + auto northeast = bounds.northeast(); + auto southwest = bounds.southwest(); + auto southeast = bounds.southeast(); - auto center = [self convertPoint:{ CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds) } toLatLngFromView:view]; + auto center = [self convertPoint:{CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)} + toLatLngFromView:view]; - // Extend bounds to account for the antimeridian - northwest.unwrapForShortestPath(center); - northeast.unwrapForShortestPath(center); - southwest.unwrapForShortestPath(center); - southeast.unwrapForShortestPath(center); + // Extend bounds to account for the antimeridian + northwest.unwrapForShortestPath(center); + northeast.unwrapForShortestPath(center); + southwest.unwrapForShortestPath(center); + southeast.unwrapForShortestPath(center); - auto correctedLatLngBounds = mbgl::LatLngBounds::empty(); - correctedLatLngBounds.extend(northwest); - correctedLatLngBounds.extend(northeast); - correctedLatLngBounds.extend(southwest); - correctedLatLngBounds.extend(southeast); + auto correctedLatLngBounds = mbgl::LatLngBounds::empty(); + correctedLatLngBounds.extend(northwest); + correctedLatLngBounds.extend(northeast); + correctedLatLngBounds.extend(southwest); + correctedLatLngBounds.extend(southeast); - CGRect rect = { [self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], CGSizeZero }; - rect = MLNExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() toPointToView:view]); - return rect; + CGRect rect = {[self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], + CGSizeZero}; + rect = MLNExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() + toPointToView:view]); + return rect; } /// Converts a rectangle in the given view’s coordinate system to a geographic /// bounding box. -- (mbgl::LatLngBounds)convertRect:(CGRect)rect toLatLngBoundsFromView:(nullable UIView *)view -{ - auto bounds = mbgl::LatLngBounds::empty(); - auto topLeft = [self convertPoint:{ CGRectGetMinX(rect), CGRectGetMinY(rect) } toLatLngFromView:view]; - auto topRight = [self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMinY(rect) } toLatLngFromView:view]; - auto bottomRight = [self convertPoint:{ CGRectGetMaxX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view]; - auto bottomLeft = [self convertPoint:{ CGRectGetMinX(rect), CGRectGetMaxY(rect) } toLatLngFromView:view]; +- (mbgl::LatLngBounds)convertRect:(CGRect)rect toLatLngBoundsFromView:(nullable UIView *)view { + auto bounds = mbgl::LatLngBounds::empty(); + auto topLeft = [self convertPoint:{CGRectGetMinX(rect), CGRectGetMinY(rect)} + toLatLngFromView:view]; + auto topRight = [self convertPoint:{CGRectGetMaxX(rect), CGRectGetMinY(rect)} + toLatLngFromView:view]; + auto bottomRight = [self convertPoint:{CGRectGetMaxX(rect), CGRectGetMaxY(rect)} + toLatLngFromView:view]; + auto bottomLeft = [self convertPoint:{CGRectGetMinX(rect), CGRectGetMaxY(rect)} + toLatLngFromView:view]; - // If the bounds straddles the antimeridian, unwrap it so that one side - // extends beyond ±180° longitude. - auto center = [self convertPoint:{ CGRectGetMidX(rect), CGRectGetMidY(rect) } toLatLngFromView:view]; - topLeft.unwrapForShortestPath(center); - topRight.unwrapForShortestPath(center); - bottomRight.unwrapForShortestPath(center); - bottomLeft.unwrapForShortestPath(center); + // If the bounds straddles the antimeridian, unwrap it so that one side + // extends beyond ±180° longitude. + auto center = [self convertPoint:{CGRectGetMidX(rect), CGRectGetMidY(rect)} + toLatLngFromView:view]; + topLeft.unwrapForShortestPath(center); + topRight.unwrapForShortestPath(center); + bottomRight.unwrapForShortestPath(center); + bottomLeft.unwrapForShortestPath(center); - bounds.extend(topLeft); - bounds.extend(topRight); - bounds.extend(bottomRight); - bounds.extend(bottomLeft); + bounds.extend(topLeft); + bounds.extend(topRight); + bounds.extend(bottomRight); + bounds.extend(bottomLeft); - return bounds; + return bounds; +} + +- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude { + return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel); } - (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude -{ - return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel); + zoomLevel:(double)zoomLevel { + return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); } -- (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude zoomLevel:(double)zoomLevel -{ - return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); -} - -- (MLNMapProjection*)mapProjection { - return [[MLNMapProjection alloc] initWithMapView:self]; +- (MLNMapProjection *)mapProjection { + return [[MLNMapProjection alloc] initWithMapView:self]; } // MARK: - Camera Change Reason - -- (void)resetCameraChangeReason -{ - self.cameraChangeReasonBitmask = MLNCameraChangeReasonNone; +- (void)resetCameraChangeReason { + self.cameraChangeReasonBitmask = MLNCameraChangeReasonNone; } // MARK: - Annotations - -- (nullable NSArray> *)annotations -{ - if (_annotationContextsByAnnotationTag.empty()) - { - return nil; - } - - // Map all the annotation tags to the annotations themselves. - std::vector> annotations; - std::transform(_annotationContextsByAnnotationTag.begin(), - _annotationContextsByAnnotationTag.end(), - std::back_inserter(annotations), - ^ id (const std::pair &pair) - { - return pair.second.annotation; - }); - - annotations.erase(std::remove_if(annotations.begin(), annotations.end(), - [](const id annotation) { return annotation == nullptr; }), - annotations.end()); - - return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; -} - -- (nullable NSArray> *)visibleAnnotations -{ - return [self visibleAnnotationsInRect:self.bounds]; -} - -- (nullable NSArray> *)visibleAnnotationsInRect:(CGRect)rect -{ - if (_annotationContextsByAnnotationTag.empty()) - { - return nil; - } - - std::vector annotationTags = [self annotationTagsInRect:rect]; - std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; - - if (shapeAnnotationTags.size()) { - annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); - } - - if (annotationTags.size()) - { - NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; - - for (auto const& annotationTag: annotationTags) - { - if (!_annotationContextsByAnnotationTag.count(annotationTag) || - annotationTag == MLNAnnotationTagNotFound) - { - continue; - } - - MLNAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - MLNAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag); - if (annotationContext.annotation) - { - [annotations addObject:annotationContext.annotation]; - } - } - - return [annotations copy]; - } - +- (nullable NSArray> *)annotations { + if (_annotationContextsByAnnotationTag.empty()) { return nil; + } + + // Map all the annotation tags to the annotations themselves. + std::vector> annotations; + std::transform(_annotationContextsByAnnotationTag.begin(), + _annotationContextsByAnnotationTag.end(), std::back_inserter(annotations), + ^id(const std::pair &pair) { + return pair.second.annotation; + }); + + annotations.erase( + std::remove_if(annotations.begin(), annotations.end(), + [](const id annotation) { return annotation == nullptr; }), + annotations.end()); + + return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; +} + +- (nullable NSArray> *)visibleAnnotations { + return [self visibleAnnotationsInRect:self.bounds]; +} + +- (nullable NSArray> *)visibleAnnotationsInRect:(CGRect)rect { + if (_annotationContextsByAnnotationTag.empty()) { + return nil; + } + + std::vector annotationTags = [self annotationTagsInRect:rect]; + std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; + + if (shapeAnnotationTags.size()) { + annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), + shapeAnnotationTags.end()); + } + + if (annotationTags.size()) { + NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; + + for (auto const &annotationTag : annotationTags) { + if (!_annotationContextsByAnnotationTag.count(annotationTag) || + annotationTag == MLNAnnotationTagNotFound) { + continue; + } + + MLNAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + MLNAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag); + if (annotationContext.annotation) { + [annotations addObject:annotationContext.annotation]; + } + } + + return [annotations copy]; + } + + return nil; } /// Returns the annotation assigned the given tag. Cheap. -- (id )annotationWithTag:(MLNAnnotationTag)tag -{ - if ( ! _annotationContextsByAnnotationTag.count(tag) || - tag == MLNAnnotationTagNotFound) { - return nil; - } +- (id)annotationWithTag:(MLNAnnotationTag)tag { + if (!_annotationContextsByAnnotationTag.count(tag) || tag == MLNAnnotationTagNotFound) { + return nil; + } - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); - return annotationContext.annotation; + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); + return annotationContext.annotation; } /// Returns the annotation tag assigned to the given annotation. -- (MLNAnnotationTag)annotationTagForAnnotation:(id )annotation -{ - if ( ! annotation || annotation == self.userLocation - || _annotationTagsByAnnotation.count(annotation) == 0) - { - return MLNAnnotationTagNotFound; - } +- (MLNAnnotationTag)annotationTagForAnnotation:(id)annotation { + if (!annotation || annotation == self.userLocation || + _annotationTagsByAnnotation.count(annotation) == 0) { + return MLNAnnotationTagNotFound; + } - return _annotationTagsByAnnotation.at(annotation); + return _annotationTagsByAnnotation.at(annotation); } -- (void)addAnnotation:(id )annotation -{ - MLNLogDebug(@"Adding annotation: %@", annotation); - if ( ! annotation) return; +- (void)addAnnotation:(id)annotation { + MLNLogDebug(@"Adding annotation: %@", annotation); + if (!annotation) return; - // The core bulk add API is efficient with respect to indexing and - // screen refreshes, thus we should defer to it even for individual adds. - // - [self addAnnotations:@[ annotation ]]; + // The core bulk add API is efficient with respect to indexing and + // screen refreshes, thus we should defer to it even for individual adds. + // + [self addAnnotations:@[ annotation ]]; } -- (void)addAnnotations:(NSArray> *)annotations -{ - MLNLogDebug(@"Adding: %lu annotations", annotations.count); - if ( ! annotations) return; - [self willChangeValueForKey:@"annotations"]; +- (void)addAnnotations:(NSArray> *)annotations { + MLNLogDebug(@"Adding: %lu annotations", annotations.count); + if (!annotations) return; + [self willChangeValueForKey:@"annotations"]; - NSMutableDictionary *annotationImagesForAnnotation = [NSMutableDictionary dictionary]; - NSMutableDictionary *annotationViewsForAnnotation = [NSMutableDictionary dictionary]; + NSMutableDictionary *annotationImagesForAnnotation = [NSMutableDictionary dictionary]; + NSMutableDictionary *annotationViewsForAnnotation = [NSMutableDictionary dictionary]; - BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; - BOOL delegateImplementsImageForPoint = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; + BOOL delegateImplementsViewForAnnotation = + [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; + BOOL delegateImplementsImageForPoint = + [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; - NSMutableArray *newAnnotationViews = [[NSMutableArray alloc] initWithCapacity:annotations.count]; + NSMutableArray *newAnnotationViews = [[NSMutableArray alloc] initWithCapacity:annotations.count]; - for (id annotation in annotations) - { - MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation"); + for (id annotation in annotations) { + MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], + @"annotation should conform to MLNAnnotation"); - // adding the same annotation object twice is a no-op - if (_annotationTagsByAnnotation.count(annotation) != 0) - { - continue; - } - - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - // The polyline or polygon knows how to style itself (with the map view’s help). - MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; - if (!multiPoint.pointCount) { - continue; - } - - _isChangingAnnotationLayers = YES; - MLNAnnotationTag annotationTag = self.mbglMap.addAnnotation([multiPoint annotationObjectWithDelegate:self]); - MLNAnnotationContext context; - context.annotation = annotation; - _annotationContextsByAnnotationTag[annotationTag] = context; - _annotationTagsByAnnotation[annotation] = annotationTag; - - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; - } - else if ( ! [annotation isKindOfClass:[MLNMultiPolyline class]] - && ![annotation isKindOfClass:[MLNMultiPolygon class]] - && ![annotation isKindOfClass:[MLNShapeCollection class]] - && ![annotation isKindOfClass:[MLNPointCollection class]]) - { - MLNAnnotationView *annotationView; - NSString *symbolName; - NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; - - if (delegateImplementsViewForAnnotation) - { - annotationView = [self annotationViewForAnnotation:annotation]; - if (annotationView) - { - annotationViewsForAnnotation[annotationValue] = annotationView; - annotationView.annotation = annotation; - annotationView.center = MLNPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]); - [newAnnotationViews addObject:annotationView]; - - MLNAnnotationImage *annotationImage = self.invisibleAnnotationImage; - symbolName = annotationImage.styleIconIdentifier; - annotationImagesForAnnotation[annotationValue] = annotationImage; - if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) - { - [self installAnnotationImage:annotationImage]; - } - } - } - - if ( ! annotationView) { - MLNAnnotationImage *annotationImage; - - if (delegateImplementsImageForPoint) - { - annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; - } - if ( ! annotationImage) - { - annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; - } - if ( ! annotationImage) - { - annotationImage = self.defaultAnnotationImage; - } - - symbolName = annotationImage.styleIconIdentifier; - - if ( ! symbolName) - { - symbolName = [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = symbolName; - } - if ( ! self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) - { - [self installAnnotationImage:annotationImage]; - } - - annotationImagesForAnnotation[annotationValue] = annotationImage; - } - - MLNAnnotationTag annotationTag = self.mbglMap.addAnnotation(mbgl::SymbolAnnotation { - MLNPointFromLocationCoordinate2D(annotation.coordinate), - symbolName.UTF8String - }); - - MLNAnnotationContext context; - context.annotation = annotation; - MLNAnnotationImage *annotationImage = annotationImagesForAnnotation[annotationValue]; - context.imageReuseIdentifier = annotationImage.reuseIdentifier; - - if (annotationView) { - context.annotationView = annotationView; - context.viewReuseIdentifier = annotationView.reuseIdentifier; - } - - _annotationTagsByAnnotation[annotation] = annotationTag; - _annotationContextsByAnnotationTag[annotationTag] = context; - - if ([annotation isKindOfClass:[NSObject class]]) { - MLNAssert(![annotation isKindOfClass:[MLNMultiPoint class]], @"Point annotation should not be MLNMultiPoint."); - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; - } - } + // adding the same annotation object twice is a no-op + if (_annotationTagsByAnnotation.count(annotation) != 0) { + continue; } - [self updateAnnotationContainerViewWithAnnotationViews:newAnnotationViews]; + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + // The polyline or polygon knows how to style itself (with the map view’s help). + MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; + if (!multiPoint.pointCount) { + continue; + } - [self didChangeValueForKey:@"annotations"]; - if (_isChangingAnnotationLayers) - { - [self.style willChangeValueForKey:@"layers"]; + _isChangingAnnotationLayers = YES; + MLNAnnotationTag annotationTag = + self.mbglMap.addAnnotation([multiPoint annotationObjectWithDelegate:self]); + MLNAnnotationContext context; + context.annotation = annotation; + _annotationContextsByAnnotationTag[annotationTag] = context; + _annotationTagsByAnnotation[annotation] = annotationTag; + + [(NSObject *)annotation addObserver:self + forKeyPath:@"coordinates" + options:0 + context:(void *)(NSUInteger)annotationTag]; + } else if (![annotation isKindOfClass:[MLNMultiPolyline class]] && + ![annotation isKindOfClass:[MLNMultiPolygon class]] && + ![annotation isKindOfClass:[MLNShapeCollection class]] && + ![annotation isKindOfClass:[MLNPointCollection class]]) { + MLNAnnotationView *annotationView; + NSString *symbolName; + NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; + + if (delegateImplementsViewForAnnotation) { + annotationView = [self annotationViewForAnnotation:annotation]; + if (annotationView) { + annotationViewsForAnnotation[annotationValue] = annotationView; + annotationView.annotation = annotation; + annotationView.center = MLNPointRounded([self convertCoordinate:annotation.coordinate + toPointToView:self]); + [newAnnotationViews addObject:annotationView]; + + MLNAnnotationImage *annotationImage = self.invisibleAnnotationImage; + symbolName = annotationImage.styleIconIdentifier; + annotationImagesForAnnotation[annotationValue] = annotationImage; + if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { + [self installAnnotationImage:annotationImage]; + } + } + } + + if (!annotationView) { + MLNAnnotationImage *annotationImage; + + if (delegateImplementsImageForPoint) { + annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; + } + if (!annotationImage) { + annotationImage = + [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; + } + if (!annotationImage) { + annotationImage = self.defaultAnnotationImage; + } + + symbolName = annotationImage.styleIconIdentifier; + + if (!symbolName) { + symbolName = + [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = symbolName; + } + if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { + [self installAnnotationImage:annotationImage]; + } + + annotationImagesForAnnotation[annotationValue] = annotationImage; + } + + MLNAnnotationTag annotationTag = self.mbglMap.addAnnotation(mbgl::SymbolAnnotation{ + MLNPointFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String}); + + MLNAnnotationContext context; + context.annotation = annotation; + MLNAnnotationImage *annotationImage = annotationImagesForAnnotation[annotationValue]; + context.imageReuseIdentifier = annotationImage.reuseIdentifier; + + if (annotationView) { + context.annotationView = annotationView; + context.viewReuseIdentifier = annotationView.reuseIdentifier; + } + + _annotationTagsByAnnotation[annotation] = annotationTag; + _annotationContextsByAnnotationTag[annotationTag] = context; + + if ([annotation isKindOfClass:[NSObject class]]) { + MLNAssert(![annotation isKindOfClass:[MLNMultiPoint class]], + @"Point annotation should not be MLNMultiPoint."); + [(NSObject *)annotation addObserver:self + forKeyPath:@"coordinate" + options:0 + context:(void *)(NSUInteger)annotationTag]; + } } + } - if ([self.delegate respondsToSelector:@selector(mapView:didAddAnnotationViews:)]) - { - [self.delegate mapView:self didAddAnnotationViews:newAnnotationViews]; - } + [self updateAnnotationContainerViewWithAnnotationViews:newAnnotationViews]; - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + [self didChangeValueForKey:@"annotations"]; + if (_isChangingAnnotationLayers) { + [self.style willChangeValueForKey:@"layers"]; + } + + if ([self.delegate respondsToSelector:@selector(mapView:didAddAnnotationViews:)]) { + [self.delegate mapView:self didAddAnnotationViews:newAnnotationViews]; + } + + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); } -- (void)updateAnnotationContainerViewWithAnnotationViews:(NSArray *)annotationViews -{ - if (annotationViews.count == 0) return; +- (void)updateAnnotationContainerViewWithAnnotationViews: + (NSArray *)annotationViews { + if (annotationViews.count == 0) return; - MLNAnnotationContainerView *newAnnotationContainerView; - if (self.annotationContainerView) - { - // reload any previously added views - newAnnotationContainerView = [MLNAnnotationContainerView annotationContainerViewWithAnnotationContainerView:self.annotationContainerView]; - [self.annotationContainerView removeFromSuperview]; - } - else - { - newAnnotationContainerView = [[MLNAnnotationContainerView alloc] initWithFrame:self.bounds]; - } - newAnnotationContainerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - newAnnotationContainerView.contentMode = UIViewContentModeCenter; - [newAnnotationContainerView addSubviews:annotationViews]; - [_mbglView->getView() insertSubview:newAnnotationContainerView atIndex:0]; - self.annotationContainerView = newAnnotationContainerView; + MLNAnnotationContainerView *newAnnotationContainerView; + if (self.annotationContainerView) { + // reload any previously added views + newAnnotationContainerView = [MLNAnnotationContainerView + annotationContainerViewWithAnnotationContainerView:self.annotationContainerView]; + [self.annotationContainerView removeFromSuperview]; + } else { + newAnnotationContainerView = [[MLNAnnotationContainerView alloc] initWithFrame:self.bounds]; + } + newAnnotationContainerView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + newAnnotationContainerView.contentMode = UIViewContentModeCenter; + [newAnnotationContainerView addSubviews:annotationViews]; + [_mbglView->getView() insertSubview:newAnnotationContainerView atIndex:0]; + self.annotationContainerView = newAnnotationContainerView; - [self updatePresentsWithTransaction]; + [self updatePresentsWithTransaction]; } /// Initialize and return a default annotation image that depicts a round pin /// rising from the center, with a shadow slightly below center. The alignment /// rect therefore excludes the bottom half. -- (MLNAnnotationImage *)defaultAnnotationImage -{ - UIImage *image = [UIImage mgl_resourceImageNamed:MLNDefaultStyleMarkerSymbolName]; - image = [image imageWithAlignmentRectInsets: - UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)]; - MLNAnnotationImage *annotationImage = [MLNAnnotationImage annotationImageWithImage:image - reuseIdentifier:MLNDefaultStyleMarkerSymbolName]; - annotationImage.styleIconIdentifier = [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - return annotationImage; +- (MLNAnnotationImage *)defaultAnnotationImage { + UIImage *image = [UIImage mgl_resourceImageNamed:MLNDefaultStyleMarkerSymbolName]; + image = [image imageWithAlignmentRectInsets:UIEdgeInsetsMake(0, 0, image.size.height / 2, 0)]; + MLNAnnotationImage *annotationImage = + [MLNAnnotationImage annotationImageWithImage:image + reuseIdentifier:MLNDefaultStyleMarkerSymbolName]; + annotationImage.styleIconIdentifier = + [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + return annotationImage; } -- (MLNAnnotationImage *)invisibleAnnotationImage -{ - MLNAnnotationImage *annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MLNInvisibleStyleMarkerSymbolName]; +- (MLNAnnotationImage *)invisibleAnnotationImage { + MLNAnnotationImage *annotationImage = + [self dequeueReusableAnnotationImageWithIdentifier:MLNInvisibleStyleMarkerSymbolName]; - if (!annotationImage) - { - UIGraphicsBeginImageContext(CGSizeMake(1, 1)); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - annotationImage = [MLNAnnotationImage annotationImageWithImage:image - reuseIdentifier:MLNInvisibleStyleMarkerSymbolName]; - annotationImage.styleIconIdentifier = [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - } + if (!annotationImage) { + UIGraphicsBeginImageContext(CGSizeMake(1, 1)); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + annotationImage = + [MLNAnnotationImage annotationImageWithImage:image + reuseIdentifier:MLNInvisibleStyleMarkerSymbolName]; + annotationImage.styleIconIdentifier = + [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + } - return annotationImage; + return annotationImage; } -- (MLNAnnotationView *)annotationViewForAnnotation:(id)annotation -{ - MLNAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation]; +- (MLNAnnotationView *)annotationViewForAnnotation:(id)annotation { + MLNAnnotationView *annotationView = [self.delegate mapView:self viewForAnnotation:annotation]; - if (annotationView) - { - // Make sure that the annotation views are selected/deselected correctly because - // annotations are not dismissed when they move out of the visible bounds - BOOL isViewForSelectedAnnotation = self.selectedAnnotation == annotation; - [annotationView setSelected:isViewForSelectedAnnotation]; + if (annotationView) { + // Make sure that the annotation views are selected/deselected correctly because + // annotations are not dismissed when they move out of the visible bounds + BOOL isViewForSelectedAnnotation = self.selectedAnnotation == annotation; + [annotationView setSelected:isViewForSelectedAnnotation]; - annotationView.annotation = annotation; - annotationView.mapView = self; - CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationView.frame.size }, annotationView.alignmentRectInsets); + annotationView.annotation = annotation; + annotationView.mapView = self; + CGRect bounds = UIEdgeInsetsInsetRect({CGPointZero, annotationView.frame.size}, + annotationView.alignmentRectInsets); - // Take any offset into consideration - CGFloat adjustedAnnotationWidth = CGRectGetWidth(bounds) + fabs(annotationView.centerOffset.dx); - CGFloat adjustedAnnotationHeight = CGRectGetHeight(bounds) + fabs(annotationView.centerOffset.dx); + // Take any offset into consideration + CGFloat adjustedAnnotationWidth = CGRectGetWidth(bounds) + fabs(annotationView.centerOffset.dx); + CGFloat adjustedAnnotationHeight = + CGRectGetHeight(bounds) + fabs(annotationView.centerOffset.dx); - _largestAnnotationViewSize = CGSizeMake(MAX(_largestAnnotationViewSize.width, adjustedAnnotationWidth), - MAX(_largestAnnotationViewSize.height, adjustedAnnotationHeight)); + _largestAnnotationViewSize = + CGSizeMake(MAX(_largestAnnotationViewSize.width, adjustedAnnotationWidth), + MAX(_largestAnnotationViewSize.height, adjustedAnnotationHeight)); - _unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, _largestAnnotationViewSize.width), - MAX(_unionedAnnotationRepresentationSize.height, _largestAnnotationViewSize.height)); - } + _unionedAnnotationRepresentationSize = CGSizeMake( + MAX(_unionedAnnotationRepresentationSize.width, _largestAnnotationViewSize.width), + MAX(_unionedAnnotationRepresentationSize.height, _largestAnnotationViewSize.height)); + } - return annotationView; + return annotationView; } -- (nullable MLNAnnotationView *)viewForAnnotation:(id)annotation -{ - MLNLogDebug(@"Retrieving the annotation view for: %@", annotation); - if (annotation == self.userLocation) - { - return self.userLocationAnnotationView; - } +- (nullable MLNAnnotationView *)viewForAnnotation:(id)annotation { + MLNLogDebug(@"Retrieving the annotation view for: %@", annotation); + if (annotation == self.userLocation) { + return self.userLocationAnnotationView; + } + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + if (annotationTag == MLNAnnotationTagNotFound) { + return nil; + } + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + return annotationContext.annotationView; +} + +- (double)alphaForShapeAnnotation:(MLNShape *)annotation { + if (_delegateHasAlphasForShapeAnnotations) { + return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; + } + return 1.0; +} + +- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation { + UIColor *color = (_delegateHasStrokeColorsForShapeAnnotations + ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] + : self.tintColor); + return color.mgl_color; +} + +- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation { + UIColor *color = (_delegateHasFillColorsForShapeAnnotations + ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] + : self.tintColor); + return color.mgl_color; +} + +- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation { + if (_delegateHasLineWidthsForShapeAnnotations) { + return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MLNPolyline *)annotation]; + } + return 3.0; +} + +- (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage { + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; + annotationImage.delegate = self; + + // add sprite + self.mbglMap.addAnnotationImage( + [annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); + + // Create a slop area with a “radius” equal in size to the annotation + // image’s alignment rect, allowing the eventual tap to be on any point + // within this image. Union this slop area with any existing slop areas. + CGRect bounds = UIEdgeInsetsInsetRect({CGPointZero, annotationImage.image.size}, + annotationImage.image.alignmentRectInsets); + _unionedAnnotationRepresentationSize = + CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, bounds.size.width), + MAX(_unionedAnnotationRepresentationSize.height, bounds.size.height)); +} + +- (void)removeAnnotation:(id)annotation { + MLNLogDebug(@"Removing annotation: %@", annotation); + if (!annotation) return; + + // The core bulk deletion API is efficient with respect to indexing + // and screen refreshes, thus we should defer to it even for + // individual deletes. + // + [self removeAnnotations:@[ annotation ]]; +} + +- (void)removeAnnotations:(NSArray> *)annotations { + MLNLogDebug(@"Removing: %lu annotations", annotations.count); + if (!annotations) return; + + [self willChangeValueForKey:@"annotations"]; + + for (id annotation in annotations) { + MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], + @"annotation should conform to MLNAnnotation"); + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; if (annotationTag == MLNAnnotationTagNotFound) { - return nil; + continue; } + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - return annotationContext.annotationView; -} + MLNAnnotationView *annotationView = annotationContext.annotationView; -- (double)alphaForShapeAnnotation:(MLNShape *)annotation -{ - if (_delegateHasAlphasForShapeAnnotations) - { - return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; - } - return 1.0; -} - -- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation -{ - UIColor *color = (_delegateHasStrokeColorsForShapeAnnotations - ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] - : self.tintColor); - return color.mgl_color; -} - -- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation -{ - UIColor *color = (_delegateHasFillColorsForShapeAnnotations - ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] - : self.tintColor); - return color.mgl_color; -} - -- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation -{ - if (_delegateHasLineWidthsForShapeAnnotations) - { - return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MLNPolyline *)annotation]; - } - return 3.0; -} - -- (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage -{ - NSString *iconIdentifier = annotationImage.styleIconIdentifier; - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - annotationImage.delegate = self; - - // add sprite - self.mbglMap.addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); - - // Create a slop area with a “radius” equal in size to the annotation - // image’s alignment rect, allowing the eventual tap to be on any point - // within this image. Union this slop area with any existing slop areas. - CGRect bounds = UIEdgeInsetsInsetRect({ CGPointZero, annotationImage.image.size }, - annotationImage.image.alignmentRectInsets); - _unionedAnnotationRepresentationSize = CGSizeMake(MAX(_unionedAnnotationRepresentationSize.width, bounds.size.width), - MAX(_unionedAnnotationRepresentationSize.height, bounds.size.height)); -} - -- (void)removeAnnotation:(id )annotation -{ - MLNLogDebug(@"Removing annotation: %@", annotation); - if ( ! annotation) return; - - // The core bulk deletion API is efficient with respect to indexing - // and screen refreshes, thus we should defer to it even for - // individual deletes. - // - [self removeAnnotations:@[ annotation ]]; -} - -- (void)removeAnnotations:(NSArray> *)annotations -{ - MLNLogDebug(@"Removing: %lu annotations", annotations.count); - if ( ! annotations) return; - - [self willChangeValueForKey:@"annotations"]; - - for (id annotation in annotations) - { - MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation"); - - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - if (annotationTag == MLNAnnotationTagNotFound) - { - continue; - } - - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - MLNAnnotationView *annotationView = annotationContext.annotationView; - - if (annotationContext.viewReuseIdentifier) - { - NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier]; - if (![annotationViewReuseQueue containsObject:annotationView]) - { - [annotationViewReuseQueue removeObject:annotationView]; - } - } - - annotationView.annotation = nil; - [annotationView removeFromSuperview]; - [self.annotationContainerView.annotationViews removeObject:annotationView]; - - if (annotationTag == _selectedAnnotationTag) - { - [self deselectAnnotation:annotation animated:NO]; - } - - _annotationContextsByAnnotationTag.erase(annotationTag); - _annotationTagsByAnnotation.erase(annotation); - - if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MLNMultiPoint class]]) - { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag]; - } - else if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; - } - - _isChangingAnnotationLayers = YES; - // If the underlying map is gone, there’s nothing to remove, but still - // continue to unregister KVO and other annotation resources. - if (_mbglMap) - { - self.mbglMap.removeAnnotation(annotationTag); - } + if (annotationContext.viewReuseIdentifier) { + NSMutableArray *annotationViewReuseQueue = + [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier]; + if (![annotationViewReuseQueue containsObject:annotationView]) { + [annotationViewReuseQueue removeObject:annotationView]; + } } - [self updatePresentsWithTransaction]; + annotationView.annotation = nil; + [annotationView removeFromSuperview]; + [self.annotationContainerView.annotationViews removeObject:annotationView]; - [self didChangeValueForKey:@"annotations"]; - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); - if (_isChangingAnnotationLayers) - { - [self.style willChangeValueForKey:@"layers"]; + if (annotationTag == _selectedAnnotationTag) { + [self deselectAnnotation:annotation animated:NO]; } + + _annotationContextsByAnnotationTag.erase(annotationTag); + _annotationTagsByAnnotation.erase(annotation); + + if ([annotation isKindOfClass:[NSObject class]] && + ![annotation isKindOfClass:[MLNMultiPoint class]]) { + [(NSObject *)annotation removeObserver:self + forKeyPath:@"coordinate" + context:(void *)(NSUInteger)annotationTag]; + } else if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + [(NSObject *)annotation removeObserver:self + forKeyPath:@"coordinates" + context:(void *)(NSUInteger)annotationTag]; + } + + _isChangingAnnotationLayers = YES; + // If the underlying map is gone, there’s nothing to remove, but still + // continue to unregister KVO and other annotation resources. + if (_mbglMap) { + self.mbglMap.removeAnnotation(annotationTag); + } + } + + [self updatePresentsWithTransaction]; + + [self didChangeValueForKey:@"annotations"]; + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + if (_isChangingAnnotationLayers) { + [self.style willChangeValueForKey:@"layers"]; + } } -- (nonnull NSArray> *)overlays -{ - if (self.annotations == nil) { return @[]; } +- (nonnull NSArray> *)overlays { + if (self.annotations == nil) { + return @[]; + } - NSMutableArray> *mutableOverlays = [NSMutableArray array]; + NSMutableArray> *mutableOverlays = [NSMutableArray array]; - [self.annotations enumerateObjectsUsingBlock:^(id _Nonnull annotation, NSUInteger idx, BOOL * _Nonnull stop) { - if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) - { - [mutableOverlays addObject:(id)annotation]; - } - }]; + [self.annotations enumerateObjectsUsingBlock:^(id _Nonnull annotation, + NSUInteger idx, BOOL *_Nonnull stop) { + if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) { + [mutableOverlays addObject:(id)annotation]; + } + }]; - return [NSArray arrayWithArray:mutableOverlays]; + return [NSArray arrayWithArray:mutableOverlays]; } -- (void)addOverlay:(id )overlay -{ - MLNLogDebug(@"Adding overlay: %@", overlay); - [self addOverlays:@[ overlay ]]; +- (void)addOverlay:(id)overlay { + MLNLogDebug(@"Adding overlay: %@", overlay); + [self addOverlays:@[ overlay ]]; } -- (void)addOverlays:(NSArray> *)overlays -{ - MLNLogDebug(@"Adding: %lu overlays", overlays.count); +- (void)addOverlays:(NSArray> *)overlays { + MLNLogDebug(@"Adding: %lu overlays", overlays.count); #if DEBUG - for (id overlay in overlays) - { - MLNAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], @"overlay should conform to MLNOverlay"); - } + for (id overlay in overlays) { + MLNAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], + @"overlay should conform to MLNOverlay"); + } #endif - [self addAnnotations:overlays]; + [self addAnnotations:overlays]; } -- (void)removeOverlay:(id )overlay -{ - MLNLogDebug(@"Removing overlay: %@", overlay); - [self removeOverlays:@[ overlay ]]; +- (void)removeOverlay:(id)overlay { + MLNLogDebug(@"Removing overlay: %@", overlay); + [self removeOverlays:@[ overlay ]]; } -- (void)removeOverlays:(NSArray> *)overlays -{ - MLNLogDebug(@"Removing: %lu overlays", overlays.count); +- (void)removeOverlays:(NSArray> *)overlays { + MLNLogDebug(@"Removing: %lu overlays", overlays.count); #if DEBUG - for (id overlay in overlays) - { - MLNAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], @"overlay should conform to MLNOverlay"); - } + for (id overlay in overlays) { + MLNAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], + @"overlay should conform to MLNOverlay"); + } #endif - [self removeAnnotations:overlays]; + [self removeAnnotations:overlays]; } -- (nullable MLNAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier -{ - return self.annotationImagesByIdentifier[identifier]; +- (nullable MLNAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier: + (NSString *)identifier { + return self.annotationImagesByIdentifier[identifier]; } -- (nullable MLNAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier -{ - NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:identifier]; - MLNAnnotationView *reusableView = annotationViewReuseQueue.firstObject; - [reusableView prepareForReuse]; - [annotationViewReuseQueue removeObject:reusableView]; +- (nullable MLNAnnotationView *)dequeueReusableAnnotationViewWithIdentifier:(NSString *)identifier { + NSMutableArray *annotationViewReuseQueue = + [self annotationViewReuseQueueForIdentifier:identifier]; + MLNAnnotationView *reusableView = annotationViewReuseQueue.firstObject; + [reusableView prepareForReuse]; + [annotationViewReuseQueue removeObject:reusableView]; - return reusableView; + return reusableView; } /** @@ -5298,471 +5198,470 @@ static void *windowScreenContext = &windowScreenContext; with the same point. Setting this parameter to false is useful for asking “what if?” */ -- (MLNAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist -{ - // Look for any annotation near the tap. An annotation is “near” if the - // distance between its center and the tap is less than the maximum height - // or width of an installed annotation image or annotation view. - CGRect queryRect = CGRectInset({ point, CGSizeZero }, - -_unionedAnnotationRepresentationSize.width, - -_unionedAnnotationRepresentationSize.height); - queryRect = CGRectInset(queryRect, -MLNAnnotationImagePaddingForHitTest, - -MLNAnnotationImagePaddingForHitTest); - std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; - std::vector nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect]; +- (MLNAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist { + // Look for any annotation near the tap. An annotation is “near” if the + // distance between its center and the tap is less than the maximum height + // or width of an installed annotation image or annotation view. + CGRect queryRect = CGRectInset({point, CGSizeZero}, -_unionedAnnotationRepresentationSize.width, + -_unionedAnnotationRepresentationSize.height); + queryRect = CGRectInset(queryRect, -MLNAnnotationImagePaddingForHitTest, + -MLNAnnotationImagePaddingForHitTest); + std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; + std::vector nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect]; - if (nearbyShapeAnnotations.size()) { - nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), nearbyShapeAnnotations.end()); - } + if (nearbyShapeAnnotations.size()) { + nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), + nearbyShapeAnnotations.end()); + } - if (nearbyAnnotations.size()) - { - // Assume that the user is fat-fingering an annotation. - CGRect hitRect = CGRectInset({ point, CGSizeZero }, - -MLNAnnotationImagePaddingForHitTest, - -MLNAnnotationImagePaddingForHitTest); + if (nearbyAnnotations.size()) { + // Assume that the user is fat-fingering an annotation. + CGRect hitRect = CGRectInset({point, CGSizeZero}, -MLNAnnotationImagePaddingForHitTest, + -MLNAnnotationImagePaddingForHitTest); - // Filter out any annotation whose image or view is unselectable or for which - // hit testing fails. - auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MLNAnnotationTag annotationTag) { - id annotation = [self annotationWithTag:annotationTag]; - MLNAssert(annotation, @"Unknown annotation found nearby tap"); - if ( ! annotation) - { - return true; + // Filter out any annotation whose image or view is unselectable or for which + // hit testing fails. + auto end = std::remove_if( + nearbyAnnotations.begin(), nearbyAnnotations.end(), + [&](const MLNAnnotationTag annotationTag) { + id annotation = [self annotationWithTag:annotationTag]; + MLNAssert(annotation, @"Unknown annotation found nearby tap"); + if (!annotation) { + return true; + } + + MLNAnnotationContext annotationContext = + _annotationContextsByAnnotationTag.at(annotationTag); + CGRect annotationRect; + + MLNAnnotationView *annotationView = annotationContext.annotationView; + + if (annotationView) { + if (!annotationView.enabled) { + return true; } - MLNAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - CGRect annotationRect; + CGPoint calloutAnchorPoint = + MLNPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]); + CGRect frame = CGRectInset({calloutAnchorPoint, CGSizeZero}, + -CGRectGetWidth(annotationView.frame) / 2, + -CGRectGetHeight(annotationView.frame) / 2); - MLNAnnotationView *annotationView = annotationContext.annotationView; + // We need to take any offset into consideration. Note that a large offset will result + // in a large value for `_unionedAnnotationRepresentationSize` (and thus a larger + // feature query rect). Aim to keep the offset as small as possible. + frame = + CGRectOffset(frame, annotationView.centerOffset.dx, annotationView.centerOffset.dy); - if (annotationView) - { - if ( ! annotationView.enabled) - { - return true; - } - - CGPoint calloutAnchorPoint = MLNPointRounded([self convertCoordinate:annotation.coordinate toPointToView:self]); - CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -CGRectGetWidth(annotationView.frame) / 2, -CGRectGetHeight(annotationView.frame) / 2); - - // We need to take any offset into consideration. Note that a large offset will result in a - // large value for `_unionedAnnotationRepresentationSize` (and thus a larger feature query rect). - // Aim to keep the offset as small as possible. - frame = CGRectOffset(frame, annotationView.centerOffset.dx, annotationView.centerOffset.dy); - - annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); - } - else - { - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) { - return !!(![self.delegate mapView:self shapeAnnotationIsEnabled:(MLNMultiPoint *)annotation]); - } else { - return false; - } - } - - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if ( ! annotationImage.enabled) - { - return true; - } - - MLNAnnotationImage *fallbackAnnotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; - UIImage *fallbackImage = fallbackAnnotationImage.image; - - annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage centeredAtCoordinate:annotation.coordinate]; + annotationRect = UIEdgeInsetsInsetRect(frame, annotationView.alignmentRectInsets); + } else { + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) { + return !!(![self.delegate mapView:self + shapeAnnotationIsEnabled:(MLNMultiPoint *)annotation]); + } else { + return false; + } } - // Filter out the annotation if the fattened finger didn’t land - // within the image’s alignment rect. - return !!!CGRectIntersectsRect(annotationRect, hitRect); + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + if (!annotationImage.enabled) { + return true; + } + + MLNAnnotationImage *fallbackAnnotationImage = + [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; + UIImage *fallbackImage = fallbackAnnotationImage.image; + + annotationRect = [self frameOfImage:annotationImage.image ?: fallbackImage + centeredAtCoordinate:annotation.coordinate]; + } + + // Filter out the annotation if the fattened finger didn’t land + // within the image’s alignment rect. + return !!!CGRectIntersectsRect(annotationRect, hitRect); }); - nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + } - } - - MLNAnnotationTag hitAnnotationTag = MLNAnnotationTagNotFound; - if (nearbyAnnotations.size()) - { - // The first selection in the cycle should be the one nearest to the - // tap. Also the annotation tags need to be stable in order to compare them with - // the remembered tags _annotationsNearbyLastTap. - CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self]; - std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { - CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; - CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; - CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude, - coordinateA.longitude - currentCoordinate.longitude); - CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude, - coordinateB.longitude - currentCoordinate.longitude); - return deltaA < deltaB; + MLNAnnotationTag hitAnnotationTag = MLNAnnotationTagNotFound; + if (nearbyAnnotations.size()) { + // The first selection in the cycle should be the one nearest to the + // tap. Also the annotation tags need to be stable in order to compare them with + // the remembered tags _annotationsNearbyLastTap. + CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self]; + std::sort( + nearbyAnnotations.begin(), nearbyAnnotations.end(), + [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { + CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; + CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; + CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude, + coordinateA.longitude - currentCoordinate.longitude); + CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude, + coordinateB.longitude - currentCoordinate.longitude); + return deltaA < deltaB; }); - if (nearbyAnnotations == _annotationsNearbyLastTap) - { - // The last time we persisted a set of annotations, we had the same - // set of annotations as we do now. Cycle through them. - if (_selectedAnnotationTag == MLNAnnotationTagNotFound - || _selectedAnnotationTag == nearbyAnnotations.back()) - { - // Either no annotation is selected or the last annotation in - // the set was selected. Wrap around to the first annotation in - // the set. - hitAnnotationTag = nearbyAnnotations.front(); - } - else - { - auto result = std::find(nearbyAnnotations.begin(), - nearbyAnnotations.end(), - _selectedAnnotationTag); - if (result == nearbyAnnotations.end()) - { - // An annotation from this set hasn’t been selected before. - // Select the first (nearest) one. - hitAnnotationTag = nearbyAnnotations.front(); - } - else - { - // Step to the next annotation in the set. - auto distance = std::distance(nearbyAnnotations.begin(), result); - hitAnnotationTag = nearbyAnnotations[distance + 1]; - } - } + if (nearbyAnnotations == _annotationsNearbyLastTap) { + // The last time we persisted a set of annotations, we had the same + // set of annotations as we do now. Cycle through them. + if (_selectedAnnotationTag == MLNAnnotationTagNotFound || + _selectedAnnotationTag == nearbyAnnotations.back()) { + // Either no annotation is selected or the last annotation in + // the set was selected. Wrap around to the first annotation in + // the set. + hitAnnotationTag = nearbyAnnotations.front(); + } else { + auto result = + std::find(nearbyAnnotations.begin(), nearbyAnnotations.end(), _selectedAnnotationTag); + if (result == nearbyAnnotations.end()) { + // An annotation from this set hasn’t been selected before. + // Select the first (nearest) one. + hitAnnotationTag = nearbyAnnotations.front(); + } else { + // Step to the next annotation in the set. + auto distance = std::distance(nearbyAnnotations.begin(), result); + hitAnnotationTag = nearbyAnnotations[distance + 1]; } - else - { - // Remember the nearby annotations for the next time this method is - // called. - if (persist) - { - _annotationsNearbyLastTap = nearbyAnnotations; - } + } + } else { + // Remember the nearby annotations for the next time this method is + // called. + if (persist) { + _annotationsNearbyLastTap = nearbyAnnotations; + } - // Choose the first nearby annotation. - if (nearbyAnnotations.size()) - { - hitAnnotationTag = nearbyAnnotations.front(); - } - } + // Choose the first nearby annotation. + if (nearbyAnnotations.size()) { + hitAnnotationTag = nearbyAnnotations.front(); + } } + } - return hitAnnotationTag; + return hitAnnotationTag; } /// Returns the tags of the annotations coincident with the given rectangle. -- (std::vector)annotationTagsInRect:(CGRect)rect -{ - return _rendererFrontend->getRenderer()->queryPointAnnotations({ - { CGRectGetMinX(rect), CGRectGetMinY(rect) }, - { CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, - }); +- (std::vector)annotationTagsInRect:(CGRect)rect { + return _rendererFrontend->getRenderer()->queryPointAnnotations({ + {CGRectGetMinX(rect), CGRectGetMinY(rect)}, + {CGRectGetMaxX(rect), CGRectGetMaxY(rect)}, + }); } -- (std::vector)shapeAnnotationTagsInRect:(CGRect)rect -{ - return _rendererFrontend->getRenderer()->queryShapeAnnotations({ - { CGRectGetMinX(rect), CGRectGetMinY(rect) }, - { CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, - }); +- (std::vector)shapeAnnotationTagsInRect:(CGRect)rect { + return _rendererFrontend->getRenderer()->queryShapeAnnotations({ + {CGRectGetMinX(rect), CGRectGetMinY(rect)}, + {CGRectGetMaxX(rect), CGRectGetMaxY(rect)}, + }); } - -- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id)annotation animated:(BOOL)animated { - // Consider delegating - return [annotation isKindOfClass:[MLNPointAnnotation class]]; +- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id)annotation + animated:(BOOL)animated { + // Consider delegating + return [annotation isKindOfClass:[MLNPointAnnotation class]]; } -- (id )selectedAnnotation -{ - if (_userLocationAnnotationIsSelected) - { - return self.userLocation; - } +- (id)selectedAnnotation { + if (_userLocationAnnotationIsSelected) { + return self.userLocation; + } - if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || - _selectedAnnotationTag == MLNAnnotationTagNotFound) { - return nil; - } + if (!_annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || + _selectedAnnotationTag == MLNAnnotationTagNotFound) { + return nil; + } - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag); - return annotationContext.annotation; + MLNAnnotationContext &annotationContext = + _annotationContextsByAnnotationTag.at(_selectedAnnotationTag); + return annotationContext.annotation; } -- (void)setSelectedAnnotation:(id )annotation -{ - MLNLogDebug(@"Selecting annotation: %@", annotation); - [self willChangeValueForKey:@"selectedAnnotations"]; - _selectedAnnotationTag = [self annotationTagForAnnotation:annotation]; - _userLocationAnnotationIsSelected = annotation && annotation == self.userLocation; - [self didChangeValueForKey:@"selectedAnnotations"]; +- (void)setSelectedAnnotation:(id)annotation { + MLNLogDebug(@"Selecting annotation: %@", annotation); + [self willChangeValueForKey:@"selectedAnnotations"]; + _selectedAnnotationTag = [self annotationTagForAnnotation:annotation]; + _userLocationAnnotationIsSelected = annotation && annotation == self.userLocation; + [self didChangeValueForKey:@"selectedAnnotations"]; } -- (NSArray> *)selectedAnnotations -{ - id selectedAnnotation = self.selectedAnnotation; - return (selectedAnnotation ? @[ selectedAnnotation ] : @[]); +- (NSArray> *)selectedAnnotations { + id selectedAnnotation = self.selectedAnnotation; + return (selectedAnnotation ? @[ selectedAnnotation ] : @[]); } -- (void)setSelectedAnnotations:(NSArray> *)selectedAnnotations -{ - if ( ! selectedAnnotations.count) return; +- (void)setSelectedAnnotations:(NSArray> *)selectedAnnotations { + if (!selectedAnnotations.count) return; - id firstAnnotation = selectedAnnotations[0]; + id firstAnnotation = selectedAnnotations[0]; - MLNAssert([firstAnnotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation"); + MLNAssert([firstAnnotation conformsToProtocol:@protocol(MLNAnnotation)], + @"annotation should conform to MLNAnnotation"); - if ([firstAnnotation isKindOfClass:[MLNMultiPoint class]]) return; + if ([firstAnnotation isKindOfClass:[MLNMultiPoint class]]) return; - [self selectAnnotation:firstAnnotation animated:YES completionHandler:nil]; + [self selectAnnotation:firstAnnotation animated:YES completionHandler:nil]; } -- (void)selectAnnotation:(id )annotation animated:(BOOL)animated -{ - [self selectAnnotation:annotation animated:animated completionHandler:nil]; +- (void)selectAnnotation:(id)annotation animated:(BOOL)animated { + [self selectAnnotation:annotation animated:animated completionHandler:nil]; } -- (void)selectAnnotation:(id )annotation animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - [self selectAnnotation:annotation moveIntoView:animated animateSelection:animated completionHandler:completion]; +- (void)selectAnnotation:(id)annotation + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + [self selectAnnotation:annotation + moveIntoView:animated + animateSelection:animated + completionHandler:completion]; } -- (void)selectAnnotation:(id )annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Selecting annotation: %@ moveIntoView: %@ animateSelection: %@", annotation, MLNStringFromBOOL(moveIntoView), MLNStringFromBOOL(animateSelection)); - CGRect positioningRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:CGPointZero]; - [self selectAnnotation:annotation moveIntoView:moveIntoView animateSelection:animateSelection calloutPositioningRect:positioningRect completionHandler:completion]; +- (void)selectAnnotation:(id)annotation + moveIntoView:(BOOL)moveIntoView + animateSelection:(BOOL)animateSelection + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Selecting annotation: %@ moveIntoView: %@ animateSelection: %@", annotation, + MLNStringFromBOOL(moveIntoView), MLNStringFromBOOL(animateSelection)); + CGRect positioningRect = [self positioningRectForAnnotation:annotation + defaultCalloutPoint:CGPointZero]; + [self selectAnnotation:annotation + moveIntoView:moveIntoView + animateSelection:animateSelection + calloutPositioningRect:positioningRect + completionHandler:completion]; } -- (void)selectAnnotation:(id )annotation moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection calloutPositioningRect:(CGRect)calloutPositioningRect completionHandler:(nullable void (^)(void))completion -{ - if ( ! annotation) return; +- (void)selectAnnotation:(id)annotation + moveIntoView:(BOOL)moveIntoView + animateSelection:(BOOL)animateSelection + calloutPositioningRect:(CGRect)calloutPositioningRect + completionHandler:(nullable void (^)(void))completion { + if (!annotation) return; - if (annotation == self.selectedAnnotation) return; + if (annotation == self.selectedAnnotation) return; - [self deselectAnnotation:self.selectedAnnotation animated:NO]; + [self deselectAnnotation:self.selectedAnnotation animated:NO]; - // Add the annotation to the map if it hasn’t been added yet. - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - if (annotationTag == MLNAnnotationTagNotFound && annotation != self.userLocation) - { - [self addAnnotation:annotation]; - annotationTag = [self annotationTagForAnnotation:annotation]; - if (annotationTag == MLNAnnotationTagNotFound) return; + // Add the annotation to the map if it hasn’t been added yet. + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + if (annotationTag == MLNAnnotationTagNotFound && annotation != self.userLocation) { + [self addAnnotation:annotation]; + annotationTag = [self annotationTagForAnnotation:annotation]; + if (annotationTag == MLNAnnotationTagNotFound) return; + } + + MLNAnnotationView *annotationView = nil; + + if (annotation != self.userLocation) + if (annotationTag != MLNAnnotationTagNotFound) { + MLNAnnotationContext &annotationContext = + _annotationContextsByAnnotationTag.at(annotationTag); + annotationView = annotationContext.annotationView; + if (annotationView && annotationView.enabled) { + // Annotations represented by views use the view frame as the positioning rect. + calloutPositioningRect = annotationView.frame; + [annotationView.superview bringSubviewToFront:annotationView]; + + [annotationView setSelected:YES animated:animateSelection]; + } } - MLNAnnotationView *annotationView = nil; + self.selectedAnnotation = annotation; - if (annotation != self.userLocation) - if (annotationTag != MLNAnnotationTagNotFound) { - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - annotationView = annotationContext.annotationView; - if (annotationView && annotationView.enabled) { - // Annotations represented by views use the view frame as the positioning rect. - calloutPositioningRect = annotationView.frame; - [annotationView.superview bringSubviewToFront:annotationView]; + // Determine if we're allowed to move this offscreen annotation on screen, even though we've asked + // it to + if (moveIntoView) { + moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation + animated:animateSelection]; + } - [annotationView setSelected:YES animated:animateSelection]; - } + // If we have an invalid positioning rect, we need to provide a suitable default. + // This (currently) happens if you select an annotation that has NOT yet been + // added. See https://github.com/mapbox/mapbox-gl-native/issues/11476 + if (CGRectIsNull(calloutPositioningRect)) { + CLLocationCoordinate2D origin = annotation.coordinate; + CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; + calloutPositioningRect = {.origin = originPoint, .size = CGSizeZero}; + } + + CGRect expandedPositioningRect = calloutPositioningRect; + + // Used for callout positioning, and moving offscreen annotations onscreen. + CGRect constrainedRect = self.contentFrame; + CGRect bounds = constrainedRect; + + BOOL expandedPositioningRectToMoveCalloutIntoViewWithMargins = NO; + + UIView *calloutView = nil; + + if ([annotation respondsToSelector:@selector(title)] && annotation.title && + [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && + [self.delegate mapView:self annotationCanShowCallout:annotation]) { + // build the callout + if ([self.delegate respondsToSelector:@selector(mapView:calloutViewForAnnotation:)]) { + id providedCalloutView = [self.delegate mapView:self calloutViewForAnnotation:annotation]; + if (providedCalloutView) { + if (![providedCalloutView isKindOfClass:[UIView class]]) { + [NSException raise:NSInvalidArgumentException + format:@"Callout view must be a kind of UIView"]; } + MLNAssert([providedCalloutView conformsToProtocol:@protocol(MLNCalloutView)], + @"callout view must conform to MLNCalloutView"); + calloutView = providedCalloutView; + } + } + if (!calloutView) { + calloutView = [self calloutViewForAnnotation:annotation]; + } + self.calloutViewForSelectedAnnotation = calloutView; - self.selectedAnnotation = annotation; + if (_userLocationAnnotationIsSelected) { + calloutPositioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame]; - // Determine if we're allowed to move this offscreen annotation on screen, even though we've asked it to - if (moveIntoView) { - moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation animated:animateSelection]; + CGRect implicitAnnotationFrame = + [self.userLocationAnnotationView.layer.presentationLayer frame]; + CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame; + _initialImplicitCalloutViewOffset = CGPointMake( + CGRectGetMinX(explicitAnnotationFrame) - CGRectGetMinX(implicitAnnotationFrame), + CGRectGetMinY(explicitAnnotationFrame) - CGRectGetMinY(implicitAnnotationFrame)); } - // If we have an invalid positioning rect, we need to provide a suitable default. - // This (currently) happens if you select an annotation that has NOT yet been - // added. See https://github.com/mapbox/mapbox-gl-native/issues/11476 - if (CGRectIsNull(calloutPositioningRect)) { - CLLocationCoordinate2D origin = annotation.coordinate; - CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; - calloutPositioningRect = { .origin = originPoint, .size = CGSizeZero }; + // consult delegate for left and/or right accessory views + if ([self.delegate respondsToSelector:@selector(mapView: + leftCalloutAccessoryViewForAnnotation:)]) { + calloutView.leftAccessoryView = [self.delegate mapView:self + leftCalloutAccessoryViewForAnnotation:annotation]; + + if ([calloutView.leftAccessoryView isKindOfClass:[UIControl class]]) { + UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleCalloutAccessoryTapGesture:)]; + + [calloutView.leftAccessoryView addGestureRecognizer:calloutAccessoryTap]; + } } - CGRect expandedPositioningRect = calloutPositioningRect; + if ([self.delegate respondsToSelector:@selector(mapView: + rightCalloutAccessoryViewForAnnotation:)]) { + calloutView.rightAccessoryView = [self.delegate mapView:self + rightCalloutAccessoryViewForAnnotation:annotation]; - // Used for callout positioning, and moving offscreen annotations onscreen. - CGRect constrainedRect = self.contentFrame; - CGRect bounds = constrainedRect; + if ([calloutView.rightAccessoryView isKindOfClass:[UIControl class]]) { + UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleCalloutAccessoryTapGesture:)]; - BOOL expandedPositioningRectToMoveCalloutIntoViewWithMargins = NO; - - UIView *calloutView = nil; - - if ([annotation respondsToSelector:@selector(title)] && - annotation.title && - [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && - [self.delegate mapView:self annotationCanShowCallout:annotation]) - { - // build the callout - if ([self.delegate respondsToSelector:@selector(mapView:calloutViewForAnnotation:)]) - { - id providedCalloutView = [self.delegate mapView:self calloutViewForAnnotation:annotation]; - if (providedCalloutView) { - if (![providedCalloutView isKindOfClass:[UIView class]]) { - [NSException raise:NSInvalidArgumentException format:@"Callout view must be a kind of UIView"]; - } - MLNAssert([providedCalloutView conformsToProtocol:@protocol(MLNCalloutView)], @"callout view must conform to MLNCalloutView"); - calloutView = providedCalloutView; - } - } - if (!calloutView) - { - calloutView = [self calloutViewForAnnotation:annotation]; - } - self.calloutViewForSelectedAnnotation = calloutView; - - if (_userLocationAnnotationIsSelected) - { - calloutPositioningRect = [self.userLocationAnnotationView.layer.presentationLayer frame]; - - CGRect implicitAnnotationFrame = [self.userLocationAnnotationView.layer.presentationLayer frame]; - CGRect explicitAnnotationFrame = self.userLocationAnnotationView.frame; - _initialImplicitCalloutViewOffset = CGPointMake(CGRectGetMinX(explicitAnnotationFrame) - CGRectGetMinX(implicitAnnotationFrame), - CGRectGetMinY(explicitAnnotationFrame) - CGRectGetMinY(implicitAnnotationFrame)); - } - - // consult delegate for left and/or right accessory views - if ([self.delegate respondsToSelector:@selector(mapView:leftCalloutAccessoryViewForAnnotation:)]) - { - calloutView.leftAccessoryView = [self.delegate mapView:self leftCalloutAccessoryViewForAnnotation:annotation]; - - if ([calloutView.leftAccessoryView isKindOfClass:[UIControl class]]) - { - UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(handleCalloutAccessoryTapGesture:)]; - - [calloutView.leftAccessoryView addGestureRecognizer:calloutAccessoryTap]; - } - } - - if ([self.delegate respondsToSelector:@selector(mapView:rightCalloutAccessoryViewForAnnotation:)]) - { - calloutView.rightAccessoryView = [self.delegate mapView:self rightCalloutAccessoryViewForAnnotation:annotation]; - - if ([calloutView.rightAccessoryView isKindOfClass:[UIControl class]]) - { - UITapGestureRecognizer *calloutAccessoryTap = [[UITapGestureRecognizer alloc] initWithTarget:self - action:@selector(handleCalloutAccessoryTapGesture:)]; - - [calloutView.rightAccessoryView addGestureRecognizer:calloutAccessoryTap]; - } - } - - // set annotation delegate to handle taps on the callout view - calloutView.delegate = self; - - // If the callout view provides inset (outset) information, we can use it to expand our positioning - // rect, which we then use to help move the annotation on-screen if want need to. - if (moveIntoView && [calloutView respondsToSelector:@selector(marginInsetsHintForPresentationFromRect:)]) { - UIEdgeInsets margins = [calloutView marginInsetsHintForPresentationFromRect:calloutPositioningRect]; - expandedPositioningRect = UIEdgeInsetsInsetRect(expandedPositioningRect, margins); - expandedPositioningRectToMoveCalloutIntoViewWithMargins = YES; - } + [calloutView.rightAccessoryView addGestureRecognizer:calloutAccessoryTap]; + } } - if (!expandedPositioningRectToMoveCalloutIntoViewWithMargins) - { - // We don't have a callout (OR our callout didn't implement - // marginInsetsHintForPresentationFromRect: - in this case we need to - // ensure that partially off-screen annotations are NOT moved into view. - // - // We may want to create (and fallback to) an ``MLNMapViewDelegate`` version - // of the `-[MLNCalloutView marginInsetsHintForPresentationFromRect:] - // protocol method. - bounds = CGRectInset(bounds, -calloutPositioningRect.size.width, -calloutPositioningRect.size.height); + // set annotation delegate to handle taps on the callout view + calloutView.delegate = self; + + // If the callout view provides inset (outset) information, we can use it to expand our + // positioning rect, which we then use to help move the annotation on-screen if want need to. + if (moveIntoView && + [calloutView respondsToSelector:@selector(marginInsetsHintForPresentationFromRect:)]) { + UIEdgeInsets margins = + [calloutView marginInsetsHintForPresentationFromRect:calloutPositioningRect]; + expandedPositioningRect = UIEdgeInsetsInsetRect(expandedPositioningRect, margins); + expandedPositioningRectToMoveCalloutIntoViewWithMargins = YES; + } + } + + if (!expandedPositioningRectToMoveCalloutIntoViewWithMargins) { + // We don't have a callout (OR our callout didn't implement + // marginInsetsHintForPresentationFromRect: - in this case we need to + // ensure that partially off-screen annotations are NOT moved into view. + // + // We may want to create (and fallback to) an ``MLNMapViewDelegate`` version + // of the `-[MLNCalloutView marginInsetsHintForPresentationFromRect:] + // protocol method. + bounds = CGRectInset(bounds, -calloutPositioningRect.size.width, + -calloutPositioningRect.size.height); + } + + if (moveIntoView) { + moveIntoView = NO; + + // Any one of these cases should trigger a move onscreen + CGFloat minX = CGRectGetMinX(expandedPositioningRect); + + if (minX < CGRectGetMinX(bounds)) { + constrainedRect.origin.x = minX; + moveIntoView = YES; + } else { + CGFloat maxX = CGRectGetMaxX(expandedPositioningRect); + + if (maxX > CGRectGetMaxX(bounds)) { + constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect); + moveIntoView = YES; + } } - if (moveIntoView) - { - moveIntoView = NO; + CGFloat minY = CGRectGetMinY(expandedPositioningRect); - // Any one of these cases should trigger a move onscreen - CGFloat minX = CGRectGetMinX(expandedPositioningRect); + if (minY < CGRectGetMinY(bounds)) { + constrainedRect.origin.y = minY; + moveIntoView = YES; + } else { + CGFloat maxY = CGRectGetMaxY(expandedPositioningRect); - if (minX < CGRectGetMinX(bounds)) { - constrainedRect.origin.x = minX; - moveIntoView = YES; - } - else { - CGFloat maxX = CGRectGetMaxX(expandedPositioningRect); - - if (maxX > CGRectGetMaxX(bounds)) { - constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect); - moveIntoView = YES; - } - } - - CGFloat minY = CGRectGetMinY(expandedPositioningRect); - - if (minY < CGRectGetMinY(bounds)) { - constrainedRect.origin.y = minY; - moveIntoView = YES; - } - else { - CGFloat maxY = CGRectGetMaxY(expandedPositioningRect); - - if (maxY > CGRectGetMaxY(bounds)) { - constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect); - moveIntoView = YES; - } - } + if (maxY > CGRectGetMaxY(bounds)) { + constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect); + moveIntoView = YES; + } } + } - // Remember, calloutView can be nil here. - [calloutView presentCalloutFromRect:calloutPositioningRect - inView:_mbglView->getView() - constrainedToRect:constrainedRect - animated:animateSelection]; + // Remember, calloutView can be nil here. + [calloutView presentCalloutFromRect:calloutPositioningRect + inView:_mbglView->getView() + constrainedToRect:constrainedRect + animated:animateSelection]; - // Save the anchor coordinate - if ([annotation isKindOfClass:[MLNPointAnnotation class]]) { - self.anchorCoordinateForSelectedAnnotation = annotation.coordinate; - } - else { - // This is used for features like polygons, so that if the map is dragged - // the callout doesn't ping to its coordinate. - CGPoint anchorPoint = CGPointMake(CGRectGetMidX(calloutPositioningRect), CGRectGetMidY(calloutPositioningRect)); - self.anchorCoordinateForSelectedAnnotation = [self convertPoint:anchorPoint toCoordinateFromView:self]; - } + // Save the anchor coordinate + if ([annotation isKindOfClass:[MLNPointAnnotation class]]) { + self.anchorCoordinateForSelectedAnnotation = annotation.coordinate; + } else { + // This is used for features like polygons, so that if the map is dragged + // the callout doesn't ping to its coordinate. + CGPoint anchorPoint = + CGPointMake(CGRectGetMidX(calloutPositioningRect), CGRectGetMidY(calloutPositioningRect)); + self.anchorCoordinateForSelectedAnnotation = [self convertPoint:anchorPoint + toCoordinateFromView:self]; + } - // notify delegate - if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) - { - [self.delegate mapView:self didSelectAnnotation:annotation]; - } + // notify delegate + if ([self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) { + [self.delegate mapView:self didSelectAnnotation:annotation]; + } - if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotationView:)]) - { - [self.delegate mapView:self didSelectAnnotationView:annotationView]; - } + if (annotationView && [self.delegate respondsToSelector:@selector(mapView: + didSelectAnnotationView:)]) { + [self.delegate mapView:self didSelectAnnotationView:annotationView]; + } - if (moveIntoView) - { - CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); - CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; - [self setCenterCoordinate:centerCoord zoomLevel:self.zoomLevel direction:self.direction animated:animateSelection completionHandler:completion]; - } - else if (completion) - { - completion(); - } + if (moveIntoView) { + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord + zoomLevel:self.zoomLevel + direction:self.direction + animated:animateSelection + completionHandler:completion]; + } else if (completion) { + completion(); + } } -- (MLNCompactCalloutView *)calloutViewForAnnotation:(id )annotation -{ - MLNCompactCalloutView *calloutView = [MLNCompactCalloutView platformCalloutView]; - calloutView.representedObject = annotation; - calloutView.tintColor = self.tintColor; +- (MLNCompactCalloutView *)calloutViewForAnnotation:(id)annotation { + MLNCompactCalloutView *calloutView = [MLNCompactCalloutView platformCalloutView]; + calloutView.representedObject = annotation; + calloutView.tintColor = self.tintColor; - return calloutView; + return calloutView; } /// Returns the rectangle that represents the annotation image of the annotation @@ -5770,1462 +5669,1382 @@ static void *windowScreenContext = &windowScreenContext; /// and is appropriate for positioning a popover. /// If a shape annotation is visible but its centroid is not, and a default point is specified, /// the callout view is anchored to the default callout point. -- (CGRect)positioningRectForAnnotation:(id )annotation defaultCalloutPoint:(CGPoint)calloutPoint -{ - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - - if (CGRectIsNull(positioningRect)) { - return positioningRect; - } - - // For annotations which `coordinate` falls offscreen it will use the current tap point as anchor instead. - if ( ! CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation) - { - if (!CGPointEqualToPoint(calloutPoint, CGPointZero)) { - positioningRect = CGRectMake(calloutPoint.x, calloutPoint.y, positioningRect.size.width, positioningRect.size.height); - } - } +- (CGRect)positioningRectForAnnotation:(id)annotation + defaultCalloutPoint:(CGPoint)calloutPoint { + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + CGRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; + if (CGRectIsNull(positioningRect)) { return positioningRect; + } + + // For annotations which `coordinate` falls offscreen it will use the current tap point as anchor + // instead. + if (!CGRectIntersectsRect(positioningRect, self.bounds) && annotation != self.userLocation) { + if (!CGPointEqualToPoint(calloutPoint, CGPointZero)) { + positioningRect = CGRectMake(calloutPoint.x, calloutPoint.y, positioningRect.size.width, + positioningRect.size.height); + } + } + + return positioningRect; } /// Returns the rectangle that represents the annotation image of the annotation /// with the given tag. This rectangle is fitted to the image’s alignment rect /// and is appropriate for positioning a popover. -- (CGRect)positioningRectForCalloutForAnnotationWithTag:(MLNAnnotationTag)annotationTag -{ - id annotation = [self annotationWithTag:annotationTag]; - if ( ! annotation) - { - return CGRectNull; - } +- (CGRect)positioningRectForCalloutForAnnotationWithTag:(MLNAnnotationTag)annotationTag { + id annotation = [self annotationWithTag:annotationTag]; + if (!annotation) { + return CGRectNull; + } - CLLocationCoordinate2D coordinate; + CLLocationCoordinate2D coordinate; - if ((annotation == self.selectedAnnotation) && - CLLocationCoordinate2DIsValid(self.anchorCoordinateForSelectedAnnotation)) { - coordinate = self.anchorCoordinateForSelectedAnnotation; - } - else { - coordinate = annotation.coordinate; - } + if ((annotation == self.selectedAnnotation) && + CLLocationCoordinate2DIsValid(self.anchorCoordinateForSelectedAnnotation)) { + coordinate = self.anchorCoordinateForSelectedAnnotation; + } else { + coordinate = annotation.coordinate; + } - if ([annotation isKindOfClass:[MLNMultiPoint class]]) { - CLLocationCoordinate2D origin = coordinate; - CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; - return CGRectMake(originPoint.x, originPoint.y, MLNAnnotationImagePaddingForHitTest, MLNAnnotationImagePaddingForHitTest); - } + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + CLLocationCoordinate2D origin = coordinate; + CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; + return CGRectMake(originPoint.x, originPoint.y, MLNAnnotationImagePaddingForHitTest, + MLNAnnotationImagePaddingForHitTest); + } - UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image; - if ( ! image) - { - image = [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName].image; - } - if ( ! image) - { - return CGRectZero; - } + UIImage *image = [self imageOfAnnotationWithTag:annotationTag].image; + if (!image) { + image = + [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName].image; + } + if (!image) { + return CGRectZero; + } - CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:coordinate]; - positioningRect.origin.x -= 0.5; + CGRect positioningRect = [self frameOfImage:image centeredAtCoordinate:coordinate]; + positioningRect.origin.x -= 0.5; - return CGRectInset(positioningRect, -MLNAnnotationImagePaddingForCallout, - -MLNAnnotationImagePaddingForCallout); + return CGRectInset(positioningRect, -MLNAnnotationImagePaddingForCallout, + -MLNAnnotationImagePaddingForCallout); } /// Returns the rectangle relative to the viewport that represents the given /// image centered at the given coordinate. -- (CGRect)frameOfImage:(UIImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate -{ - CGPoint calloutAnchorPoint = MLNPointRounded([self convertCoordinate:coordinate toPointToView:self]); - CGRect frame = CGRectInset({ calloutAnchorPoint, CGSizeZero }, -image.size.width / 2, -image.size.height / 2); - return UIEdgeInsetsInsetRect(frame, image.alignmentRectInsets); +- (CGRect)frameOfImage:(UIImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate { + CGPoint calloutAnchorPoint = MLNPointRounded([self convertCoordinate:coordinate + toPointToView:self]); + CGRect frame = + CGRectInset({calloutAnchorPoint, CGSizeZero}, -image.size.width / 2, -image.size.height / 2); + return UIEdgeInsetsInsetRect(frame, image.alignmentRectInsets); } /// Returns the annotation image assigned to the annotation with the given tag. -- (MLNAnnotationImage *)imageOfAnnotationWithTag:(MLNAnnotationTag)annotationTag -{ - if (annotationTag == MLNAnnotationTagNotFound - || _annotationContextsByAnnotationTag.count(annotationTag) == 0) - { - return nil; +- (MLNAnnotationImage *)imageOfAnnotationWithTag:(MLNAnnotationTag)annotationTag { + if (annotationTag == MLNAnnotationTagNotFound || + _annotationContextsByAnnotationTag.count(annotationTag) == 0) { + return nil; + } + + NSString *customSymbol = + _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; + NSString *symbolName = customSymbol.length ? customSymbol : MLNDefaultStyleMarkerSymbolName; + + return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; +} + +- (void)deselectAnnotation:(id)annotation animated:(BOOL)animated { + if (!annotation) return; + + if (self.selectedAnnotation == annotation) { + MLNLogDebug(@"Deselecting annotation: %@ animated: %@", annotation, + MLNStringFromBOOL(animated)); + // dismiss popup + [self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; + + // deselect annotation view + MLNAnnotationView *annotationView = nil; + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + + if (annotationTag != MLNAnnotationTagNotFound) { + MLNAnnotationContext &annotationContext = + _annotationContextsByAnnotationTag.at(annotationTag); + annotationView = annotationContext.annotationView; + [annotationView setSelected:NO animated:animated]; } - NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; - NSString *symbolName = customSymbol.length ? customSymbol : MLNDefaultStyleMarkerSymbolName; + // clean up + self.calloutViewForSelectedAnnotation = nil; + self.selectedAnnotation = nil; + self.anchorCoordinateForSelectedAnnotation = kCLLocationCoordinate2DInvalid; - return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; -} - -- (void)deselectAnnotation:(id )annotation animated:(BOOL)animated -{ - if ( ! annotation) return; - - if (self.selectedAnnotation == annotation) - { - MLNLogDebug(@"Deselecting annotation: %@ animated: %@", annotation, MLNStringFromBOOL(animated)); - // dismiss popup - [self.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; - - // deselect annotation view - MLNAnnotationView *annotationView = nil; - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - - if (annotationTag != MLNAnnotationTagNotFound) - { - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - annotationView = annotationContext.annotationView; - [annotationView setSelected:NO animated:animated]; - } - - // clean up - self.calloutViewForSelectedAnnotation = nil; - self.selectedAnnotation = nil; - self.anchorCoordinateForSelectedAnnotation = kCLLocationCoordinate2DInvalid; - - // notify delegate - if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) - { - [self.delegate mapView:self didDeselectAnnotation:annotation]; - } - - if (annotationView && [self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotationView:)]) - { - [self.delegate mapView:self didDeselectAnnotationView:annotationView]; - } - - [self updatePresentsWithTransaction]; - } -} - -- (void)calloutViewWillAppear:(UIView *)calloutView -{ - if (_userLocationAnnotationIsSelected || - CGPointEqualToPoint(_initialImplicitCalloutViewOffset, CGPointZero)) - { - return; + // notify delegate + if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) { + [self.delegate mapView:self didDeselectAnnotation:annotation]; } - __weak __typeof__(self) weakSelf = self; - - // The user location callout view initially points to the user location - // annotation’s implicit (visual) frame, which is offset from the - // annotation’s explicit frame. Now the callout view needs to rendezvous - // with the explicit frame. Then, - // -updateUserLocationAnnotationViewAnimatedWithDuration: will take over the - // next time an updated location arrives. - [UIView animateWithDuration:_userLocationAnimationCompletionDate.timeIntervalSinceNow - delay:0 - options:(UIViewAnimationOptionCurveLinear | - UIViewAnimationOptionAllowUserInteraction | - UIViewAnimationOptionBeginFromCurrentState) - animations:^ - { - __typeof__(self) strongSelf = weakSelf; - if ( ! strongSelf) - { - return; - } - - calloutView.frame = CGRectOffset(calloutView.frame, - strongSelf->_initialImplicitCalloutViewOffset.x, - strongSelf->_initialImplicitCalloutViewOffset.y); - strongSelf->_initialImplicitCalloutViewOffset = CGPointZero; - } - completion:NULL]; -} - -- (void)showAnnotations:(NSArray> *)annotations animated:(BOOL)animated -{ - CGFloat maximumPadding = 100; - CGFloat yPadding = (self.frame.size.height / 5 <= maximumPadding) ? (self.frame.size.height / 5) : maximumPadding; - CGFloat xPadding = (self.frame.size.width / 5 <= maximumPadding) ? (self.frame.size.width / 5) : maximumPadding; - - UIEdgeInsets edgeInsets = UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding); - - [self showAnnotations:annotations edgePadding:edgeInsets animated:animated completionHandler:nil]; -} - -- (void)showAnnotations:(NSArray> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated -{ - [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil]; -} - -- (void)showAnnotations:(NSArray> *)annotations edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Showing: %lu annotations edgePadding: %@ animated: %@", annotations.count, NSStringFromUIEdgeInsets(insets), MLNStringFromBOOL(animated)); - if ( ! annotations.count) - { - if (completion) { - completion(); - } - return; + if (annotationView && [self.delegate respondsToSelector:@selector(mapView: + didDeselectAnnotationView:)]) { + [self.delegate mapView:self didDeselectAnnotationView:annotationView]; } - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - - for (id annotation in annotations) - { - if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) - { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(((id )annotation).overlayBounds)); - } - else - { - bounds.extend(MLNLatLngFromLocationCoordinate2D(annotation.coordinate)); - } - } - - [self setVisibleCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(bounds) - edgePadding:insets - animated:animated - completionHandler:completion]; + [self updatePresentsWithTransaction]; + } } +- (void)calloutViewWillAppear:(UIView *)calloutView { + if (_userLocationAnnotationIsSelected || + CGPointEqualToPoint(_initialImplicitCalloutViewOffset, CGPointZero)) { + return; + } + + __weak __typeof__(self) weakSelf = self; + + // The user location callout view initially points to the user location + // annotation’s implicit (visual) frame, which is offset from the + // annotation’s explicit frame. Now the callout view needs to rendezvous + // with the explicit frame. Then, + // -updateUserLocationAnnotationViewAnimatedWithDuration: will take over the + // next time an updated location arrives. + [UIView animateWithDuration:_userLocationAnimationCompletionDate.timeIntervalSinceNow + delay:0 + options:(UIViewAnimationOptionCurveLinear | + UIViewAnimationOptionAllowUserInteraction | + UIViewAnimationOptionBeginFromCurrentState) + animations:^{ + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + calloutView.frame = CGRectOffset( + calloutView.frame, strongSelf->_initialImplicitCalloutViewOffset.x, + strongSelf->_initialImplicitCalloutViewOffset.y); + strongSelf->_initialImplicitCalloutViewOffset = CGPointZero; + } + completion:NULL]; +} + +- (void)showAnnotations:(NSArray> *)annotations animated:(BOOL)animated { + CGFloat maximumPadding = 100; + CGFloat yPadding = (self.frame.size.height / 5 <= maximumPadding) ? (self.frame.size.height / 5) + : maximumPadding; + CGFloat xPadding = + (self.frame.size.width / 5 <= maximumPadding) ? (self.frame.size.width / 5) : maximumPadding; + + UIEdgeInsets edgeInsets = UIEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding); + + [self showAnnotations:annotations edgePadding:edgeInsets animated:animated completionHandler:nil]; +} + +- (void)showAnnotations:(NSArray> *)annotations + edgePadding:(UIEdgeInsets)insets + animated:(BOOL)animated { + [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil]; +} + +- (void)showAnnotations:(NSArray> *)annotations + edgePadding:(UIEdgeInsets)insets + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Showing: %lu annotations edgePadding: %@ animated: %@", annotations.count, + NSStringFromUIEdgeInsets(insets), MLNStringFromBOOL(animated)); + if (!annotations.count) { + if (completion) { + completion(); + } + return; + } + + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + + for (id annotation in annotations) { + if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) { + bounds.extend( + MLNLatLngBoundsFromCoordinateBounds(((id)annotation).overlayBounds)); + } else { + bounds.extend(MLNLatLngFromLocationCoordinate2D(annotation.coordinate)); + } + } + + [self setVisibleCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(bounds) + edgePadding:insets + animated:animated + completionHandler:completion]; +} // MARK: Annotation Image Delegate -- (void)annotationImageNeedsRedisplay:(MLNAnnotationImage *)annotationImage -{ - NSString *reuseIdentifier = annotationImage.reuseIdentifier; - NSString *iconIdentifier = annotationImage.styleIconIdentifier; - NSString *fallbackReuseIdentifier = MLNDefaultStyleMarkerSymbolName; - NSString *fallbackIconIdentifier = [MLNAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier]; +- (void)annotationImageNeedsRedisplay:(MLNAnnotationImage *)annotationImage { + NSString *reuseIdentifier = annotationImage.reuseIdentifier; + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + NSString *fallbackReuseIdentifier = MLNDefaultStyleMarkerSymbolName; + NSString *fallbackIconIdentifier = + [MLNAnnotationSpritePrefix stringByAppendingString:fallbackReuseIdentifier]; - if (annotationImage.image) - { - // Add the new icon to the style. - NSString *updatedIconIdentifier = [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = updatedIconIdentifier; - [self installAnnotationImage:annotationImage]; + if (annotationImage.image) { + // Add the new icon to the style. + NSString *updatedIconIdentifier = + [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = updatedIconIdentifier; + [self installAnnotationImage:annotationImage]; - if ([iconIdentifier isEqualToString:fallbackIconIdentifier]) - { - // Update any annotations associated with the annotation image. - [self applyIconIdentifier:updatedIconIdentifier toAnnotationsWithImageReuseIdentifier:reuseIdentifier]; - } + if ([iconIdentifier isEqualToString:fallbackIconIdentifier]) { + // Update any annotations associated with the annotation image. + [self applyIconIdentifier:updatedIconIdentifier + toAnnotationsWithImageReuseIdentifier:reuseIdentifier]; } - else - { - // Add the default icon to the style if necessary. - annotationImage.styleIconIdentifier = fallbackIconIdentifier; - if ( ! [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]) - { - [self installAnnotationImage:self.defaultAnnotationImage]; - } - - // Update any annotations associated with the annotation image. - [self applyIconIdentifier:fallbackIconIdentifier toAnnotationsWithImageReuseIdentifier:reuseIdentifier]; + } else { + // Add the default icon to the style if necessary. + annotationImage.styleIconIdentifier = fallbackIconIdentifier; + if (![self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]) { + [self installAnnotationImage:self.defaultAnnotationImage]; } + + // Update any annotations associated with the annotation image. + [self applyIconIdentifier:fallbackIconIdentifier + toAnnotationsWithImageReuseIdentifier:reuseIdentifier]; + } } -- (void)applyIconIdentifier:(NSString *)iconIdentifier toAnnotationsWithImageReuseIdentifier:(NSString *)reuseIdentifier -{ - for (auto &pair : _annotationContextsByAnnotationTag) - { - if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier]) - { - const mbgl::Point point = MLNPointFromLocationCoordinate2D(pair.second.annotation.coordinate); - self.mbglMap.updateAnnotation(pair.first, mbgl::SymbolAnnotation { point, iconIdentifier.UTF8String ?: "" }); - } +- (void)applyIconIdentifier:(NSString *)iconIdentifier + toAnnotationsWithImageReuseIdentifier:(NSString *)reuseIdentifier { + for (auto &pair : _annotationContextsByAnnotationTag) { + if ([pair.second.imageReuseIdentifier isEqualToString:reuseIdentifier]) { + const mbgl::Point point = + MLNPointFromLocationCoordinate2D(pair.second.annotation.coordinate); + self.mbglMap.updateAnnotation(pair.first, + mbgl::SymbolAnnotation{point, iconIdentifier.UTF8String ?: ""}); } + } } // MARK: - User Location - - -- (void)disableLocationManager -{ - [_locationManager stopUpdatingLocation]; - [_locationManager stopUpdatingHeading]; - _locationManager = nil; +- (void)disableLocationManager { + [_locationManager stopUpdatingLocation]; + [_locationManager stopUpdatingHeading]; + _locationManager = nil; } -- (void)setLocationManager:(nullable id)locationManager -{ - MLNLogDebug(@"Setting locationManager: %@", locationManager); +- (void)setLocationManager:(nullable id)locationManager { + MLNLogDebug(@"Setting locationManager: %@", locationManager); - if (!locationManager) { - locationManager = [[MLNCLLocationManager alloc] init]; - } - [_locationManager stopUpdatingLocation]; - [_locationManager stopUpdatingHeading]; - _locationManager.delegate = nil; + if (!locationManager) { + locationManager = [[MLNCLLocationManager alloc] init]; + } + [_locationManager stopUpdatingLocation]; + [_locationManager stopUpdatingHeading]; + _locationManager.delegate = nil; - _locationManager = locationManager; - _locationManager.delegate = self; + _locationManager = locationManager; + _locationManager.delegate = self; } -- (void)validateLocationServices -{ - BOOL shouldEnableLocationServices = self.showsUserLocation && !self.dormant; +- (void)validateLocationServices { + BOOL shouldEnableLocationServices = self.showsUserLocation && !self.dormant; - if (shouldEnableLocationServices) - { - if (self.shouldRequestAuthorizationToUseLocationServices && self.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { - BOOL hasWhenInUseUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]; + if (shouldEnableLocationServices) { + if (self.shouldRequestAuthorizationToUseLocationServices && + self.locationManager.authorizationStatus == kCLAuthorizationStatusNotDetermined) { + BOOL hasWhenInUseUsageDescription = !![[NSBundle mainBundle] + objectForInfoDictionaryKey:@"NSLocationWhenInUseUsageDescription"]; - if (@available(iOS 11.0, *)) { - // A WhenInUse string is required in iOS 11+ and the map never has any need for Always, so it's enough to just ask for WhenInUse. - if (hasWhenInUseUsageDescription) { - [self.locationManager requestWhenInUseAuthorization]; - } else { - [NSException raise:MLNMissingLocationServicesUsageDescriptionException - format:@"To use location services this app must have a NSLocationWhenInUseUsageDescription string in its Info.plist."]; - } - } else { - // We might have to ask for Always if the app does not provide a WhenInUse string. - BOOL hasAlwaysUsageDescription = !![[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]; - - if (hasWhenInUseUsageDescription) { - [self.locationManager requestWhenInUseAuthorization]; - } else if (hasAlwaysUsageDescription) { - [self.locationManager requestAlwaysAuthorization]; - } else { - [NSException raise:MLNMissingLocationServicesUsageDescriptionException - format:@"To use location services this app must have a NSLocationWhenInUseUsageDescription and/or NSLocationAlwaysUsageDescription string in its Info.plist."]; - } - } + if (@available(iOS 11.0, *)) { + // A WhenInUse string is required in iOS 11+ and the map never has any need for Always, so + // it's enough to just ask for WhenInUse. + if (hasWhenInUseUsageDescription) { + [self.locationManager requestWhenInUseAuthorization]; + } else { + [NSException raise:MLNMissingLocationServicesUsageDescriptionException + format:@"To use location services this app must have a " + @"NSLocationWhenInUseUsageDescription string in its Info.plist."]; } + } else { + // We might have to ask for Always if the app does not provide a WhenInUse string. + BOOL hasAlwaysUsageDescription = !![[NSBundle mainBundle] + objectForInfoDictionaryKey:@"NSLocationAlwaysUsageDescription"]; - [self.locationManager startUpdatingLocation]; + if (hasWhenInUseUsageDescription) { + [self.locationManager requestWhenInUseAuthorization]; + } else if (hasAlwaysUsageDescription) { + [self.locationManager requestAlwaysAuthorization]; + } else { + [NSException raise:MLNMissingLocationServicesUsageDescriptionException + format:@"To use location services this app must have a " + @"NSLocationWhenInUseUsageDescription and/or " + @"NSLocationAlwaysUsageDescription string in its Info.plist."]; + } + } + } - [self validateUserHeadingUpdating]; - } - else if ( ! shouldEnableLocationServices && self.locationManager) - { - [self.locationManager stopUpdatingLocation]; - [self.locationManager stopUpdatingHeading]; - } + [self.locationManager startUpdatingLocation]; + + [self validateUserHeadingUpdating]; + } else if (!shouldEnableLocationServices && self.locationManager) { + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + } } - (NSString *)accuracyDescriptionString { - NSDictionary *dictionary = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"]; - return dictionary[@"MLNAccuracyAuthorizationDescription"]; + NSDictionary *dictionary = [[NSBundle mainBundle] + objectForInfoDictionaryKey:@"NSLocationTemporaryUsageDescriptionDictionary"]; + return dictionary[@"MLNAccuracyAuthorizationDescription"]; } -- (void)setShowsUserLocation:(BOOL)showsUserLocation -{ - MLNLogDebug(@"Setting showsUserLocation: %@", MLNStringFromBOOL(showsUserLocation)); - if (showsUserLocation == _showsUserLocation) return; +- (void)setShowsUserLocation:(BOOL)showsUserLocation { + MLNLogDebug(@"Setting showsUserLocation: %@", MLNStringFromBOOL(showsUserLocation)); + if (showsUserLocation == _showsUserLocation) return; - _showsUserLocation = showsUserLocation; + _showsUserLocation = showsUserLocation; - if (showsUserLocation) - { - if ([self.delegate respondsToSelector:@selector(mapViewWillStartLocatingUser:)]) - { - [self.delegate mapViewWillStartLocatingUser:self]; - } - - self.userLocation = [[MLNUserLocation alloc] initWithMapView:self]; - - MLNUserLocationAnnotationView *userLocationAnnotationView; - - if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) - { - userLocationAnnotationView = (MLNUserLocationAnnotationView *)[self.delegate mapView:self viewForAnnotation:self.userLocation]; - if (userLocationAnnotationView && ! [userLocationAnnotationView isKindOfClass:MLNUserLocationAnnotationView.class]) - { - [NSException raise:MLNUserLocationAnnotationTypeException - format:@"User location annotation view must be a kind of MLNUserLocationAnnotationView. %@", userLocationAnnotationView.debugDescription]; - } - } - - self.userLocationAnnotationView = userLocationAnnotationView ?: [[MLNFaux3DUserLocationAnnotationView alloc] init]; - self.userLocationAnnotationView.mapView = self; - self.userLocationAnnotationView.userLocation = self.userLocation; - - self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); - - [self validateLocationServices]; + if (showsUserLocation) { + if ([self.delegate respondsToSelector:@selector(mapViewWillStartLocatingUser:)]) { + [self.delegate mapViewWillStartLocatingUser:self]; } - else - { - [self validateLocationServices]; - if ([self.delegate respondsToSelector:@selector(mapViewDidStopLocatingUser:)]) - { - [self.delegate mapViewDidStopLocatingUser:self]; - } + self.userLocation = [[MLNUserLocation alloc] initWithMapView:self]; - [self setUserTrackingMode:MLNUserTrackingModeNone animated:YES completionHandler:nil]; + MLNUserLocationAnnotationView *userLocationAnnotationView; - [self.userLocationAnnotationView removeFromSuperview]; - self.userLocationAnnotationView = nil; + if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) { + userLocationAnnotationView = + (MLNUserLocationAnnotationView *)[self.delegate mapView:self + viewForAnnotation:self.userLocation]; + if (userLocationAnnotationView && + ![userLocationAnnotationView isKindOfClass:MLNUserLocationAnnotationView.class]) { + [NSException raise:MLNUserLocationAnnotationTypeException + format:@"User location annotation view must be a kind of " + @"MLNUserLocationAnnotationView. %@", + userLocationAnnotationView.debugDescription]; + } } + + self.userLocationAnnotationView = + userLocationAnnotationView ?: [[MLNFaux3DUserLocationAnnotationView alloc] init]; + self.userLocationAnnotationView.mapView = self; + self.userLocationAnnotationView.userLocation = self.userLocation; + + self.userLocationAnnotationView.autoresizingMask = + (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + + [self validateLocationServices]; + } else { + [self validateLocationServices]; + + if ([self.delegate respondsToSelector:@selector(mapViewDidStopLocatingUser:)]) { + [self.delegate mapViewDidStopLocatingUser:self]; + } + + [self setUserTrackingMode:MLNUserTrackingModeNone animated:YES completionHandler:nil]; + + [self.userLocationAnnotationView removeFromSuperview]; + self.userLocationAnnotationView = nil; + } } -- (void)setUserLocationAnnotationView:(MLNUserLocationAnnotationView *)newAnnotationView -{ - if ( ! [newAnnotationView isEqual:_userLocationAnnotationView]) - { - _userLocationAnnotationView = newAnnotationView; - [self updateUserLocationAnnotationView]; - } +- (void)setUserLocationAnnotationView:(MLNUserLocationAnnotationView *)newAnnotationView { + if (![newAnnotationView isEqual:_userLocationAnnotationView]) { + _userLocationAnnotationView = newAnnotationView; + [self updateUserLocationAnnotationView]; + } } -+ (NSSet *)keyPathsForValuesAffectingUserLocation -{ - return [NSSet setWithObject:@"userLocationAnnotationView"]; ++ (NSSet *)keyPathsForValuesAffectingUserLocation { + return [NSSet setWithObject:@"userLocationAnnotationView"]; } -- (BOOL)isUserLocationVisible -{ - if (self.userLocationAnnotationView) - { - CGPoint locationPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self]; +- (BOOL)isUserLocationVisible { + if (self.userLocationAnnotationView) { + CGPoint locationPoint = [self convertCoordinate:self.userLocation.coordinate + toPointToView:self]; - CGRect locationRect = CGRectMake(locationPoint.x - self.userLocation.location.horizontalAccuracy, - locationPoint.y - self.userLocation.location.horizontalAccuracy, - self.userLocation.location.horizontalAccuracy * 2, - self.userLocation.location.horizontalAccuracy * 2); + CGRect locationRect = + CGRectMake(locationPoint.x - self.userLocation.location.horizontalAccuracy, + locationPoint.y - self.userLocation.location.horizontalAccuracy, + self.userLocation.location.horizontalAccuracy * 2, + self.userLocation.location.horizontalAccuracy * 2); - return CGRectIntersectsRect([self bounds], locationRect); - } + return CGRectIntersectsRect([self bounds], locationRect); + } - return NO; + return NO; +} + +- (void)setUserTrackingMode:(MLNUserTrackingMode)mode { + [self setUserTrackingMode:mode animated:YES completionHandler:nil]; +} + +- (void)setUserTrackingMode:(MLNUserTrackingMode)mode animated:(BOOL)animated { + [self setUserTrackingMode:mode animated:animated completionHandler:nil]; } - (void)setUserTrackingMode:(MLNUserTrackingMode)mode -{ - [self setUserTrackingMode:mode animated:YES completionHandler:nil]; + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting userTrackingMode: %lu animated: %@", mode, MLNStringFromBOOL(animated)); + if (mode == _userTrackingMode) { + if (completion) { + completion(); + } + return; + } + + MLNUserTrackingMode oldMode = _userTrackingMode; + [self willChangeValueForKey:@"userTrackingMode"]; + _userTrackingMode = mode; + [self didChangeValueForKey:@"userTrackingMode"]; + + switch (_userTrackingMode) { + case MLNUserTrackingModeNone: { + self.userTrackingState = MLNUserTrackingStatePossible; + + // Immediately update the annotation view; other cases update inside + // the locationManager:didUpdateLocations: method. + [self updateUserLocationAnnotationView]; + + break; + } + case MLNUserTrackingModeFollow: + case MLNUserTrackingModeFollowWithCourse: { + self.userTrackingState = + animated ? MLNUserTrackingStatePossible : MLNUserTrackingStateChanged; + self.showsUserLocation = YES; + + break; + } + case MLNUserTrackingModeFollowWithHeading: { + if (oldMode == MLNUserTrackingModeNone) { + self.userTrackingState = + animated ? MLNUserTrackingStatePossible : MLNUserTrackingStateChanged; + } + + self.showsUserLocation = YES; + + if (self.zoomLevel < self.currentMinimumZoom) { + [self setZoomLevel:self.currentMinimumZoom animated:YES]; + } + + break; + } + } + + CLLocation *location; + if (_userTrackingMode != MLNUserTrackingModeNone && (location = self.userLocation.location) && + self.userLocationAnnotationView) { + [self locationManager:self.locationManager + didUpdateLocations:@[ location ] + animated:animated + completionHandler:completion]; + } else if (completion) { + completion(); + } + + [self validateUserHeadingUpdating]; + + if ([self.delegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)]) { + [self.delegate mapView:self didChangeUserTrackingMode:_userTrackingMode animated:animated]; + } } -- (void)setUserTrackingMode:(MLNUserTrackingMode)mode animated:(BOOL)animated -{ - [self setUserTrackingMode:mode animated:animated completionHandler:nil]; -} - -- (void)setUserTrackingMode:(MLNUserTrackingMode)mode animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting userTrackingMode: %lu animated: %@", mode, MLNStringFromBOOL(animated)); - if (mode == _userTrackingMode) - { - if (completion) - { - completion(); - } - return; - } - - MLNUserTrackingMode oldMode = _userTrackingMode; - [self willChangeValueForKey:@"userTrackingMode"]; - _userTrackingMode = mode; - [self didChangeValueForKey:@"userTrackingMode"]; - - switch (_userTrackingMode) - { - case MLNUserTrackingModeNone: - { - self.userTrackingState = MLNUserTrackingStatePossible; - - // Immediately update the annotation view; other cases update inside - // the locationManager:didUpdateLocations: method. - [self updateUserLocationAnnotationView]; - - break; - } - case MLNUserTrackingModeFollow: - case MLNUserTrackingModeFollowWithCourse: - { - self.userTrackingState = animated ? MLNUserTrackingStatePossible : MLNUserTrackingStateChanged; - self.showsUserLocation = YES; - - break; - } - case MLNUserTrackingModeFollowWithHeading: - { - if (oldMode == MLNUserTrackingModeNone) - { - self.userTrackingState = animated ? MLNUserTrackingStatePossible : MLNUserTrackingStateChanged; - } - - self.showsUserLocation = YES; - - if (self.zoomLevel < self.currentMinimumZoom) - { - [self setZoomLevel:self.currentMinimumZoom animated:YES]; - } - - break; - } - } - - CLLocation *location; - if (_userTrackingMode != MLNUserTrackingModeNone && (location = self.userLocation.location) && self.userLocationAnnotationView) - { - [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion]; - } - else if (completion) - { - completion(); - } - - [self validateUserHeadingUpdating]; - - if ([self.delegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)]) - { - [self.delegate mapView:self didChangeUserTrackingMode:_userTrackingMode animated:animated]; - } +- (void)setUserLocationVerticalAlignment:(MLNAnnotationVerticalAlignment)alignment { + [self setUserLocationVerticalAlignment:alignment animated:YES]; } - (void)setUserLocationVerticalAlignment:(MLNAnnotationVerticalAlignment)alignment -{ - [self setUserLocationVerticalAlignment:alignment animated:YES]; + animated:(BOOL)animated { + _userLocationVerticalAlignment = alignment; + if (self.userTrackingMode != MLNUserTrackingModeNone) { + CLLocation *location = self.userLocation.location; + if (location) { + [self locationManager:self.locationManager + didUpdateLocations:@[ location ] + animated:animated + completionHandler:nil]; + } + } } -- (void)setUserLocationVerticalAlignment:(MLNAnnotationVerticalAlignment)alignment animated:(BOOL)animated -{ - _userLocationVerticalAlignment = alignment; - if (self.userTrackingMode != MLNUserTrackingModeNone) - { - CLLocation *location = self.userLocation.location; - if (location) - { - [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:nil]; - } - } +- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate { + [self setTargetCoordinate:targetCoordinate animated:YES completionHandler:nil]; +} + +- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated { + [self setTargetCoordinate:targetCoordinate animated:animated completionHandler:nil]; } - (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate -{ - [self setTargetCoordinate:targetCoordinate animated:YES completionHandler:nil]; + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting targetCoordinate: %@ animated: %@", + MLNStringFromCLLocationCoordinate2D(targetCoordinate), MLNStringFromBOOL(animated)); + BOOL isSynchronous = YES; + if (targetCoordinate.latitude != self.targetCoordinate.latitude || + targetCoordinate.longitude != self.targetCoordinate.longitude) { + _targetCoordinate = targetCoordinate; + if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) { + self.userTrackingState = MLNUserTrackingStatePossible; + + if (CLLocation *location = self.userLocation.location) { + isSynchronous = NO; + [self locationManager:self.locationManager + didUpdateLocations:@[ location ] + animated:animated + completionHandler:completion]; + } + } + } + if (isSynchronous && completion) { + completion(); + } } -- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated -{ - [self setTargetCoordinate:targetCoordinate animated:animated completionHandler:nil]; +- (void)setShowsUserHeadingIndicator:(BOOL)showsUserHeadingIndicator { + MLNLogDebug(@"Setting showsUserHeadingIndicator: %@", + MLNStringFromBOOL(showsUserHeadingIndicator)); + _showsUserHeadingIndicator = showsUserHeadingIndicator; + + if (_showsUserHeadingIndicator) { + self.showsUserLocation = YES; + } + [self validateUserHeadingUpdating]; } -- (void)setTargetCoordinate:(CLLocationCoordinate2D)targetCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - MLNLogDebug(@"Setting targetCoordinate: %@ animated: %@", MLNStringFromCLLocationCoordinate2D(targetCoordinate), MLNStringFromBOOL(animated)); - BOOL isSynchronous = YES; - if (targetCoordinate.latitude != self.targetCoordinate.latitude - || targetCoordinate.longitude != self.targetCoordinate.longitude) - { - _targetCoordinate = targetCoordinate; - if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) - { - self.userTrackingState = MLNUserTrackingStatePossible; +- (void)validateUserHeadingUpdating { + BOOL canShowPermanentHeadingIndicator = + self.showsUserHeadingIndicator && + self.userTrackingMode != MLNUserTrackingModeFollowWithCourse; - if (CLLocation *location = self.userLocation.location) - { - isSynchronous = NO; - [self locationManager:self.locationManager didUpdateLocations:@[location] animated:animated completionHandler:completion]; - } - } - } - if (isSynchronous && completion) - { - completion(); - } + if (canShowPermanentHeadingIndicator || + self.userTrackingMode == MLNUserTrackingModeFollowWithHeading) { + [self updateHeadingForDeviceOrientation]; + [self.locationManager startUpdatingHeading]; + } else { + [self.locationManager stopUpdatingHeading]; + } } -- (void)setShowsUserHeadingIndicator:(BOOL)showsUserHeadingIndicator -{ - MLNLogDebug(@"Setting showsUserHeadingIndicator: %@", MLNStringFromBOOL(showsUserHeadingIndicator)); - _showsUserHeadingIndicator = showsUserHeadingIndicator; - - if (_showsUserHeadingIndicator) - { - self.showsUserLocation = YES; - } - [self validateUserHeadingUpdating]; +- (void)locationManager:(id)manager didUpdateLocations:(NSArray *)locations { + [self locationManager:manager didUpdateLocations:locations animated:YES completionHandler:nil]; } -- (void)validateUserHeadingUpdating -{ - BOOL canShowPermanentHeadingIndicator = self.showsUserHeadingIndicator && self.userTrackingMode != MLNUserTrackingModeFollowWithCourse; +- (void)locationManager:(__unused id)manager + didUpdateLocations:(NSArray *)locations + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + CLLocation *oldLocation = self.userLocation.location; + CLLocation *newLocation = locations.lastObject; + _distanceFromOldUserLocation = [newLocation distanceFromLocation:oldLocation]; - if (canShowPermanentHeadingIndicator || self.userTrackingMode == MLNUserTrackingModeFollowWithHeading) - { - [self updateHeadingForDeviceOrientation]; - [self.locationManager startUpdatingHeading]; + if (!_showsUserLocation || !newLocation || !CLLocationCoordinate2DIsValid(newLocation.coordinate)) + return; + + if (!oldLocation || !CLLocationCoordinate2DIsValid(oldLocation.coordinate) || + [newLocation distanceFromLocation:oldLocation] || oldLocation.course != newLocation.course) { + if (!oldLocation || !CLLocationCoordinate2DIsValid(oldLocation.coordinate) || + self.userTrackingState != MLNUserTrackingStateBegan) { + self.userLocation.location = newLocation; } - else - { - [self.locationManager stopUpdatingHeading]; + + if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]) { + [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; } + } + + NSTimeInterval userLocationDuration = MLNAnimationDuration; + if (oldLocation && !CGPointEqualToPoint(self.userLocationAnnotationView.center, CGPointZero)) { + userLocationDuration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], + MLNUserLocationAnimationDuration); + } + [self updateUserLocationAnnotationViewAnimatedWithDuration:userLocationDuration]; + + NSTimeInterval cameraDuration = MLNUserLocationAnimationDuration; + if (self.dynamicNavigationCameraAnimationDuration && oldLocation) { + cameraDuration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], + MLNUserLocationAnimationDuration); + } + [self didUpdateLocationWithUserTrackingDuration:cameraDuration completionHandler:completion]; + + if (self.userTrackingMode == MLNUserTrackingModeNone && + self.userLocationAnnotationView.accessibilityElementIsFocused && + [UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, + self.userLocationAnnotationView); + } } -- (void)locationManager:(id)manager didUpdateLocations:(NSArray *)locations -{ - [self locationManager:manager didUpdateLocations:locations animated:YES completionHandler:nil]; +- (void)didUpdateLocationWithUserTrackingAnimated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + [self didUpdateLocationWithUserTrackingDuration:animated ? MLNUserLocationAnimationDuration : 0 + completionHandler:completion]; } -- (void)locationManager:(__unused id)manager didUpdateLocations:(NSArray *)locations animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - CLLocation *oldLocation = self.userLocation.location; - CLLocation *newLocation = locations.lastObject; - _distanceFromOldUserLocation = [newLocation distanceFromLocation:oldLocation]; - - if ( ! _showsUserLocation || ! newLocation || ! CLLocationCoordinate2DIsValid(newLocation.coordinate)) return; - - if (! oldLocation || ! CLLocationCoordinate2DIsValid(oldLocation.coordinate) || [newLocation distanceFromLocation:oldLocation] - || oldLocation.course != newLocation.course) - { - if ( ! oldLocation || ! CLLocationCoordinate2DIsValid(oldLocation.coordinate) || self.userTrackingState != MLNUserTrackingStateBegan) - { - self.userLocation.location = newLocation; - } - - if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]) - { - [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; - } +- (void)didUpdateLocationWithUserTrackingDuration:(NSTimeInterval)duration + completionHandler:(nullable void (^)(void))completion { + CLLocation *location = self.userLocation.location; + if (!_showsUserLocation || !location || !CLLocationCoordinate2DIsValid(location.coordinate) || + self.userTrackingMode == MLNUserTrackingModeNone || + self.userTrackingState == MLNUserTrackingStateBegan) { + if (completion) { + completion(); } + return; + } - NSTimeInterval userLocationDuration = MLNAnimationDuration; - if (oldLocation && ! CGPointEqualToPoint(self.userLocationAnnotationView.center, CGPointZero)) - { - userLocationDuration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], MLNUserLocationAnimationDuration); + // If the user location annotation is already where it’s supposed to be, + // don’t change the viewport. + CGPoint correctPoint = self.userLocationAnnotationViewCenter; + CGPoint currentPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self]; + if (std::abs(currentPoint.x - correctPoint.x) <= 1.0 && + std::abs(currentPoint.y - correctPoint.y) <= 1.0 && + self.userTrackingMode != MLNUserTrackingModeFollowWithCourse) { + if (completion) { + completion(); } - [self updateUserLocationAnnotationViewAnimatedWithDuration:userLocationDuration]; + return; + } - NSTimeInterval cameraDuration = MLNUserLocationAnimationDuration; - if (self.dynamicNavigationCameraAnimationDuration && oldLocation) { - cameraDuration = MIN([newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp], MLNUserLocationAnimationDuration); + if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse && + CLLocationCoordinate2DIsValid(self.targetCoordinate)) { + if (self.userTrackingState != MLNUserTrackingStateBegan) { + // Keep both the user and the destination in view. + [self didUpdateLocationWithTargetAnimated:duration != 0 completionHandler:completion]; } - [self didUpdateLocationWithUserTrackingDuration:cameraDuration completionHandler:completion]; - - if (self.userTrackingMode == MLNUserTrackingModeNone && - self.userLocationAnnotationView.accessibilityElementIsFocused && - [UIApplication sharedApplication].applicationState == UIApplicationStateActive) - { - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, self.userLocationAnnotationView); - } -} - -- (void)didUpdateLocationWithUserTrackingAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - [self didUpdateLocationWithUserTrackingDuration:animated ? MLNUserLocationAnimationDuration : 0 completionHandler:completion]; -} - -- (void)didUpdateLocationWithUserTrackingDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion -{ - CLLocation *location = self.userLocation.location; - if ( ! _showsUserLocation || ! location - || ! CLLocationCoordinate2DIsValid(location.coordinate) - || self.userTrackingMode == MLNUserTrackingModeNone - || self.userTrackingState == MLNUserTrackingStateBegan) - { - if (completion) - { - completion(); - } - return; - } - - // If the user location annotation is already where it’s supposed to be, - // don’t change the viewport. - CGPoint correctPoint = self.userLocationAnnotationViewCenter; - CGPoint currentPoint = [self convertCoordinate:self.userLocation.coordinate toPointToView:self]; - if (std::abs(currentPoint.x - correctPoint.x) <= 1.0 && std::abs(currentPoint.y - correctPoint.y) <= 1.0 - && self.userTrackingMode != MLNUserTrackingModeFollowWithCourse) - { - if (completion) - { - completion(); - } - return; - } - - if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse - && CLLocationCoordinate2DIsValid(self.targetCoordinate)) - { - if (self.userTrackingState != MLNUserTrackingStateBegan) - { - // Keep both the user and the destination in view. - [self didUpdateLocationWithTargetAnimated:duration != 0 completionHandler:completion]; - } - } - else if (self.userTrackingState == MLNUserTrackingStatePossible) - { - // The first location update is often a great distance away from the - // current viewport, so fly there to provide additional context. - [self didUpdateLocationSignificantlyAnimated:duration != 0 completionHandler:completion]; - } - else if (self.userTrackingState == MLNUserTrackingStateChanged) - { - // Subsequent updates get a more subtle animation. - [self didUpdateLocationIncrementallyDuration:duration completionHandler:completion]; - } - [self unrotateIfNeededAnimated:YES]; + } else if (self.userTrackingState == MLNUserTrackingStatePossible) { + // The first location update is often a great distance away from the + // current viewport, so fly there to provide additional context. + [self didUpdateLocationSignificantlyAnimated:duration != 0 completionHandler:completion]; + } else if (self.userTrackingState == MLNUserTrackingStateChanged) { + // Subsequent updates get a more subtle animation. + [self didUpdateLocationIncrementallyDuration:duration completionHandler:completion]; + } + [self unrotateIfNeededAnimated:YES]; } /// Changes the viewport based on an incremental location update. -- (void)didUpdateLocationIncrementallyDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion -{ - [self _setCenterCoordinate:self.userLocation.location.coordinate - edgePadding:self.edgePaddingForFollowing - zoomLevel:self.zoomLevel - direction:self.directionByFollowingWithCourse - duration:duration - animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear] - completionHandler:completion]; +- (void)didUpdateLocationIncrementallyDuration:(NSTimeInterval)duration + completionHandler:(nullable void (^)(void))completion { + [self _setCenterCoordinate:self.userLocation.location.coordinate + edgePadding:self.edgePaddingForFollowing + zoomLevel:self.zoomLevel + direction:self.directionByFollowingWithCourse + duration:duration + animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear] + completionHandler:completion]; } /// Changes the viewport based on a significant location update, such as the /// first location update. -- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ +- (void)didUpdateLocationSignificantlyAnimated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + if (_distanceFromOldUserLocation >= MLNDistanceThresholdForCameraPause) { + self.userTrackingState = MLNUserTrackingStateBeginSignificantTransition; + } else { + self.userTrackingState = MLNUserTrackingStateBegan; + } - if (_distanceFromOldUserLocation >= MLNDistanceThresholdForCameraPause) { - self.userTrackingState = MLNUserTrackingStateBeginSignificantTransition; - } else { - self.userTrackingState = MLNUserTrackingStateBegan; - } + MLNMapCamera *camera = self.camera; + camera.centerCoordinate = self.userLocation.location.coordinate; + camera.heading = self.directionByFollowingWithCourse; + if (self.zoomLevel < MLNMinimumZoomLevelForUserTracking) { + camera.altitude = MLNAltitudeForZoomLevel(MLNDefaultZoomLevelForUserTracking, camera.pitch, + camera.centerCoordinate.latitude, self.frame.size); + } - MLNMapCamera *camera = self.camera; - camera.centerCoordinate = self.userLocation.location.coordinate; - camera.heading = self.directionByFollowingWithCourse; - if (self.zoomLevel < MLNMinimumZoomLevelForUserTracking) - { - camera.altitude = MLNAltitudeForZoomLevel(MLNDefaultZoomLevelForUserTracking, - camera.pitch, - camera.centerCoordinate.latitude, - self.frame.size); - } - - __weak MLNMapView *weakSelf = self; - [self _flyToCamera:camera - edgePadding:self.edgePaddingForFollowing - withDuration:animated ? -1 : 0 - peakAltitude:-1 - completionHandler:^{ + __weak MLNMapView *weakSelf = self; + [self _flyToCamera:camera + edgePadding:self.edgePaddingForFollowing + withDuration:animated ? -1 : 0 + peakAltitude:-1 + completionHandler:^{ MLNMapView *strongSelf = weakSelf; if (strongSelf.userTrackingState == MLNUserTrackingStateBegan || - strongSelf.userTrackingState == MLNDistanceThresholdForCameraPause) - { - strongSelf.userTrackingState = MLNUserTrackingStateChanged; + strongSelf.userTrackingState == MLNDistanceThresholdForCameraPause) { + strongSelf.userTrackingState = MLNUserTrackingStateChanged; } - if (completion) - { - completion(); + if (completion) { + completion(); } - }]; + }]; } /// Changes the viewport based on a location update in the presence of a target /// coordinate that must also be displayed on the map concurrently. -- (void)didUpdateLocationWithTargetAnimated:(BOOL)animated completionHandler:(nullable void (^)(void))completion -{ - BOOL firstUpdate = self.userTrackingState == MLNUserTrackingStatePossible; - void (^animationCompletion)(void); - if (animated && firstUpdate) - { - self.userTrackingState = MLNUserTrackingStateBegan; - __weak MLNMapView *weakSelf = self; - animationCompletion = ^{ - MLNMapView *strongSelf = weakSelf; - if (strongSelf.userTrackingState == MLNUserTrackingStateBegan) - { - strongSelf.userTrackingState = MLNUserTrackingStateChanged; - } - if (completion) - { - completion(); - } - }; - } - - CLLocationCoordinate2D foci[] = { - self.userLocation.location.coordinate, - self.targetCoordinate, +- (void)didUpdateLocationWithTargetAnimated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + BOOL firstUpdate = self.userTrackingState == MLNUserTrackingStatePossible; + void (^animationCompletion)(void); + if (animated && firstUpdate) { + self.userTrackingState = MLNUserTrackingStateBegan; + __weak MLNMapView *weakSelf = self; + animationCompletion = ^{ + MLNMapView *strongSelf = weakSelf; + if (strongSelf.userTrackingState == MLNUserTrackingStateBegan) { + strongSelf.userTrackingState = MLNUserTrackingStateChanged; + } + if (completion) { + completion(); + } }; - UIEdgeInsets inset = self.edgePaddingForFollowingWithCourse; + } + + CLLocationCoordinate2D foci[] = { + self.userLocation.location.coordinate, + self.targetCoordinate, + }; + UIEdgeInsets inset = self.edgePaddingForFollowingWithCourse; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (self.userLocationVerticalAlignment == MLNAnnotationVerticalAlignmentCenter) - { - inset.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMidY(self.contentFrame); - } + if (self.userLocationVerticalAlignment == MLNAnnotationVerticalAlignmentCenter) { + inset.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMidY(self.contentFrame); + } #pragma clang diagnostic pop - [self _setVisibleCoordinates:foci - count:sizeof(foci) / sizeof(foci[0]) - edgePadding:inset - direction:self.directionByFollowingWithCourse - duration:animated ? MLNUserLocationAnimationDuration : 0 - animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear] - completionHandler:animationCompletion]; + [self _setVisibleCoordinates:foci + count:sizeof(foci) / sizeof(foci[0]) + edgePadding:inset + direction:self.directionByFollowingWithCourse + duration:animated ? MLNUserLocationAnimationDuration : 0 + animationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear] + completionHandler:animationCompletion]; } /// Returns the edge padding to apply when moving the map to a tracked location. -- (UIEdgeInsets)edgePaddingForFollowing -{ - // Center on user location unless we're already centered there (or very close). - CGPoint correctPoint = self.userLocationAnnotationViewCenter; +- (UIEdgeInsets)edgePaddingForFollowing { + // Center on user location unless we're already centered there (or very close). + CGPoint correctPoint = self.userLocationAnnotationViewCenter; - // Shift the entire frame upward or downward to accommodate a shifted user - // location annotation view. - CGRect bounds = self.bounds; - CGRect boundsAroundCorrectPoint = CGRectOffset(bounds, - correctPoint.x - CGRectGetMidX(bounds), - correctPoint.y - CGRectGetMidY(bounds)); - return UIEdgeInsetsMake(CGRectGetMinY(boundsAroundCorrectPoint) - CGRectGetMinY(bounds), - CGRectGetMaxX(boundsAroundCorrectPoint) - CGRectGetMaxX(bounds), - CGRectGetMaxY(bounds) - CGRectGetMaxY(boundsAroundCorrectPoint), - CGRectGetMaxX(bounds) - CGRectGetMaxX(boundsAroundCorrectPoint)); + // Shift the entire frame upward or downward to accommodate a shifted user + // location annotation view. + CGRect bounds = self.bounds; + CGRect boundsAroundCorrectPoint = CGRectOffset(bounds, correctPoint.x - CGRectGetMidX(bounds), + correctPoint.y - CGRectGetMidY(bounds)); + return UIEdgeInsetsMake(CGRectGetMinY(boundsAroundCorrectPoint) - CGRectGetMinY(bounds), + CGRectGetMaxX(boundsAroundCorrectPoint) - CGRectGetMaxX(bounds), + CGRectGetMaxY(bounds) - CGRectGetMaxY(boundsAroundCorrectPoint), + CGRectGetMaxX(bounds) - CGRectGetMaxX(boundsAroundCorrectPoint)); } /// Returns the edge padding to apply during bifocal course tracking. -- (UIEdgeInsets)edgePaddingForFollowingWithCourse -{ - UIEdgeInsets inset = MLNUserLocationAnnotationViewInset; - inset.top += CGRectGetHeight(self.userLocationAnnotationView.frame); - inset.bottom += CGRectGetHeight(self.userLocationAnnotationView.frame); - return inset; +- (UIEdgeInsets)edgePaddingForFollowingWithCourse { + UIEdgeInsets inset = MLNUserLocationAnnotationViewInset; + inset.top += CGRectGetHeight(self.userLocationAnnotationView.frame); + inset.bottom += CGRectGetHeight(self.userLocationAnnotationView.frame); + return inset; } /// Returns the direction the map should be turned to due to course tracking. -- (CLLocationDirection)directionByFollowingWithCourse -{ - CLLocationDirection direction = -1; - if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) - { - if (CLLocationCoordinate2DIsValid(self.targetCoordinate)) - { - mbgl::LatLng userLatLng = MLNLatLngFromLocationCoordinate2D(self.userLocation.coordinate); - mbgl::LatLng targetLatLng = MLNLatLngFromLocationCoordinate2D(self.targetCoordinate); - mbgl::ProjectedMeters userMeters = mbgl::Projection::projectedMetersForLatLng(userLatLng); - mbgl::ProjectedMeters targetMeters = mbgl::Projection::projectedMetersForLatLng(targetLatLng); - double angle = atan2(targetMeters.easting() - userMeters.easting(), - targetMeters.northing() - userMeters.northing()); - direction = mbgl::util::wrap(MLNDegreesFromRadians(angle), 0., 360.); - } - else - { - direction = self.userLocation.location.course; - } +- (CLLocationDirection)directionByFollowingWithCourse { + CLLocationDirection direction = -1; + if (self.userTrackingMode == MLNUserTrackingModeFollowWithCourse) { + if (CLLocationCoordinate2DIsValid(self.targetCoordinate)) { + mbgl::LatLng userLatLng = MLNLatLngFromLocationCoordinate2D(self.userLocation.coordinate); + mbgl::LatLng targetLatLng = MLNLatLngFromLocationCoordinate2D(self.targetCoordinate); + mbgl::ProjectedMeters userMeters = mbgl::Projection::projectedMetersForLatLng(userLatLng); + mbgl::ProjectedMeters targetMeters = mbgl::Projection::projectedMetersForLatLng(targetLatLng); + double angle = atan2(targetMeters.easting() - userMeters.easting(), + targetMeters.northing() - userMeters.northing()); + direction = mbgl::util::wrap(MLNDegreesFromRadians(angle), 0., 360.); + } else { + direction = self.userLocation.location.course; + } - if (direction >= 0) - { + if (direction >= 0) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (self.userLocationVerticalAlignment == MLNAnnotationVerticalAlignmentTop) - { - direction += 180; - } + if (self.userLocationVerticalAlignment == MLNAnnotationVerticalAlignmentTop) { + direction += 180; + } #pragma clang diagnostic pop - } } - return direction; + } + return direction; } -- (BOOL)locationManagerShouldDisplayHeadingCalibration:(id)manager -{ - if (self.displayHeadingCalibration) [self performSelector:@selector(dismissHeadingCalibrationDisplay:) - withObject:manager - afterDelay:10.0]; +- (BOOL)locationManagerShouldDisplayHeadingCalibration:(id)manager { + if (self.displayHeadingCalibration) + [self performSelector:@selector(dismissHeadingCalibrationDisplay:) + withObject:manager + afterDelay:10.0]; - return self.displayHeadingCalibration; + return self.displayHeadingCalibration; } -- (void)dismissHeadingCalibrationDisplay:(id)manager -{ - [manager dismissHeadingCalibrationDisplay]; +- (void)dismissHeadingCalibrationDisplay:(id)manager { + [manager dismissHeadingCalibrationDisplay]; } -- (void)locationManager:(__unused id)manager didUpdateHeading:(CLHeading *)newHeading -{ - dispatch_async(dispatch_get_main_queue(), ^(void) { - if ( ! self->_showsUserLocation || self.pan.state == UIGestureRecognizerStateBegan || newHeading.headingAccuracy < 0) return; +- (void)locationManager:(__unused id)manager + didUpdateHeading:(CLHeading *)newHeading { + dispatch_async(dispatch_get_main_queue(), ^(void) { + if (!self->_showsUserLocation || self.pan.state == UIGestureRecognizerStateBegan || + newHeading.headingAccuracy < 0) + return; - self.userLocation.heading = newHeading; + self.userLocation.heading = newHeading; - if (self.showsUserHeadingIndicator || self.userTrackingMode == MLNUserTrackingModeFollowWithHeading) - { - [self updateUserLocationAnnotationView]; - } - - if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]) - { - [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; - - if (!self->_showsUserLocation) return; - } - - CLLocationDirection headingDirection = (newHeading.trueHeading >= 0 ? newHeading.trueHeading : newHeading.magneticHeading); - - if (headingDirection >= 0 && self.userTrackingMode == MLNUserTrackingModeFollowWithHeading - && self.userTrackingState != MLNUserTrackingStateBegan) - { - [self _setDirection:headingDirection center:self.userLocation.coordinate animated:YES]; - [self updateUserLocationAnnotationView]; - } - }); -} - -- (void)locationManager:(__unused id)manager didFailWithError:(NSError *)error -{ - if ([error code] == kCLErrorDenied) - { - self.userTrackingMode = MLNUserTrackingModeNone; - self.showsUserLocation = NO; - - if ([self.delegate respondsToSelector:@selector(mapView:didFailToLocateUserWithError:)]) - { - [self.delegate mapView:self didFailToLocateUserWithError:error]; - } - } -} - -- (void)locationManagerDidChangeAuthorization:(id)manager -{ - if (![self shouldShowLocationDotBasedOnCurrentLocationPermissions]) - { - [self.userLocationAnnotationView removeFromSuperview]; - [self.locationManager stopUpdatingLocation]; - [self.locationManager stopUpdatingHeading]; - } else { - if (@available(iOS 14, *)) { -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 - if (self.userTrackingMode != MLNUserTrackingModeNone && - [manager respondsToSelector:@selector(authorizationStatus)] && - (manager.authorizationStatus != kCLAuthorizationStatusRestricted || - manager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways || - manager.authorizationStatus != kCLAuthorizationStatusAuthorizedWhenInUse) && - [manager respondsToSelector:@selector(accuracyAuthorization)] && - manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy && - [self accuracyDescriptionString] != nil ) { - [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"MLNAccuracyAuthorizationDescription"]; - } else { - [self validateLocationServices]; - } -#endif - } else { - [self validateLocationServices]; - } + if (self.showsUserHeadingIndicator || + self.userTrackingMode == MLNUserTrackingModeFollowWithHeading) { + [self updateUserLocationAnnotationView]; } + if ([self.delegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]) { + [self.delegate mapView:self didUpdateUserLocation:self.userLocation]; + + if (!self->_showsUserLocation) return; + } + + CLLocationDirection headingDirection = + (newHeading.trueHeading >= 0 ? newHeading.trueHeading : newHeading.magneticHeading); + + if (headingDirection >= 0 && self.userTrackingMode == MLNUserTrackingModeFollowWithHeading && + self.userTrackingState != MLNUserTrackingStateBegan) { + [self _setDirection:headingDirection center:self.userLocation.coordinate animated:YES]; + [self updateUserLocationAnnotationView]; + } + }); +} + +- (void)locationManager:(__unused id)manager didFailWithError:(NSError *)error { + if ([error code] == kCLErrorDenied) { + self.userTrackingMode = MLNUserTrackingModeNone; + self.showsUserLocation = NO; + + if ([self.delegate respondsToSelector:@selector(mapView:didFailToLocateUserWithError:)]) { + [self.delegate mapView:self didFailToLocateUserWithError:error]; + } + } +} + +- (void)locationManagerDidChangeAuthorization:(id)manager { + if (![self shouldShowLocationDotBasedOnCurrentLocationPermissions]) { + [self.userLocationAnnotationView removeFromSuperview]; + [self.locationManager stopUpdatingLocation]; + [self.locationManager stopUpdatingHeading]; + } else { if (@available(iOS 14, *)) { - if ([self.delegate respondsToSelector:@selector(mapView:didChangeLocationManagerAuthorization:)]) { - [self.delegate mapView:self didChangeLocationManagerAuthorization:manager]; - } +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 140000 + if (self.userTrackingMode != MLNUserTrackingModeNone && + [manager respondsToSelector:@selector(authorizationStatus)] && + (manager.authorizationStatus != kCLAuthorizationStatusRestricted || + manager.authorizationStatus != kCLAuthorizationStatusAuthorizedAlways || + manager.authorizationStatus != kCLAuthorizationStatusAuthorizedWhenInUse) && + [manager respondsToSelector:@selector(accuracyAuthorization)] && + manager.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy && + [self accuracyDescriptionString] != nil) { + [self.locationManager requestTemporaryFullAccuracyAuthorizationWithPurposeKey: + @"MLNAccuracyAuthorizationDescription"]; + } else { + [self validateLocationServices]; + } +#endif + } else { + [self validateLocationServices]; } + } + + if (@available(iOS 14, *)) { + if ([self.delegate respondsToSelector:@selector(mapView: + didChangeLocationManagerAuthorization:)]) { + [self.delegate mapView:self didChangeLocationManagerAuthorization:manager]; + } + } } -- (BOOL)shouldShowLocationDotBasedOnCurrentLocationPermissions -{ - return self.locationManager && (self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways - || self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse); +- (BOOL)shouldShowLocationDotBasedOnCurrentLocationPermissions { + return self.locationManager && + (self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedAlways || + self.locationManager.authorizationStatus == kCLAuthorizationStatusAuthorizedWhenInUse); } -- (void)updateHeadingForDeviceOrientation -{ - if (self.locationManager) - { - // note that right/left device and interface orientations are opposites (see UIApplication.h) - // - CLDeviceOrientation orientation; - switch ([[UIApplication sharedApplication] statusBarOrientation]) - { - case (UIInterfaceOrientationLandscapeLeft): - { - orientation = CLDeviceOrientationLandscapeRight; - break; - } - case (UIInterfaceOrientationLandscapeRight): - { - orientation = CLDeviceOrientationLandscapeLeft; - break; - } - case (UIInterfaceOrientationPortraitUpsideDown): - { - orientation = CLDeviceOrientationPortraitUpsideDown; - break; - } - case (UIInterfaceOrientationPortrait): - default: - { - orientation = CLDeviceOrientationPortrait; - break; - } - } - - // Setting the location manager's heading orientation causes it to send - // a heading event, which in turn makes us redraw, which kicks off a - // loop... so don't do that. rdar://34059173 - if (self.locationManager.headingOrientation != orientation) - { - self.locationManager.headingOrientation = orientation; - } +- (void)updateHeadingForDeviceOrientation { + if (self.locationManager) { + // note that right/left device and interface orientations are opposites (see UIApplication.h) + // + CLDeviceOrientation orientation; + switch ([[UIApplication sharedApplication] statusBarOrientation]) { + case (UIInterfaceOrientationLandscapeLeft): { + orientation = CLDeviceOrientationLandscapeRight; + break; + } + case (UIInterfaceOrientationLandscapeRight): { + orientation = CLDeviceOrientationLandscapeLeft; + break; + } + case (UIInterfaceOrientationPortraitUpsideDown): { + orientation = CLDeviceOrientationPortraitUpsideDown; + break; + } + case (UIInterfaceOrientationPortrait): + default: { + orientation = CLDeviceOrientationPortrait; + break; + } } + + // Setting the location manager's heading orientation causes it to send + // a heading event, which in turn makes us redraw, which kicks off a + // loop... so don't do that. rdar://34059173 + if (self.locationManager.headingOrientation != orientation) { + self.locationManager.headingOrientation = orientation; + } + } } // MARK: Data -- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point -{ - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromCGPoint(point)); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil]; +- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromCGPoint(point)); + return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil]; } -- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", NSStringFromCGPoint(point), styleLayerIdentifiers); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; +- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", + NSStringFromCGPoint(point), styleLayerIdentifiers); + return [self visibleFeaturesAtPoint:point + inStyleLayersWithIdentifiers:styleLayerIdentifiers + predicate:nil]; } -- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers predicate:(NSPredicate *)predicate -{ - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromCGPoint(point), styleLayerIdentifiers, predicate); - mbgl::ScreenCoordinate screenCoordinate = { point.x, point.y }; +- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers + predicate:(NSPredicate *)predicate { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", + NSStringFromCGPoint(point), styleLayerIdentifiers, predicate); + mbgl::ScreenCoordinate screenCoordinate = {point.x, point.y}; - std::optional> optionalLayerIDs; - if (styleLayerIdentifiers) - { - __block std::vector layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) - { - layerIDs.push_back(identifier.UTF8String); + std::optional> optionalLayerIDs; + if (styleLayerIdentifiers) { + __block std::vector layerIDs; + layerIDs.reserve(styleLayerIdentifiers.count); + [styleLayerIdentifiers + enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) { + layerIDs.push_back(identifier.UTF8String); }]; - optionalLayerIDs = layerIDs; - } + optionalLayerIDs = layerIDs; + } - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenCoordinate, { optionalLayerIDs, optionalFilter }); - return MLNFeaturesFromMBGLFeatures(features); + std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures( + screenCoordinate, {optionalLayerIDs, optionalFilter}); + return MLNFeaturesFromMBGLFeatures(features); } -- (NSArray> *)visibleFeaturesInRect:(CGRect)rect { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromCGRect(rect)); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil]; +- (NSArray> *)visibleFeaturesInRect:(CGRect)rect { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromCGRect(rect)); + return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil]; } -- (NSArray> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", NSStringFromCGRect(rect), styleLayerIdentifiers); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; +- (NSArray> *)visibleFeaturesInRect:(CGRect)rect + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", + NSStringFromCGRect(rect), styleLayerIdentifiers); + return [self visibleFeaturesInRect:rect + inStyleLayersWithIdentifiers:styleLayerIdentifiers + predicate:nil]; } -- (NSArray> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers predicate:(NSPredicate *)predicate { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromCGRect(rect), styleLayerIdentifiers, predicate); - mbgl::ScreenBox screenBox = { - { CGRectGetMinX(rect), CGRectGetMinY(rect) }, - { CGRectGetMaxX(rect), CGRectGetMaxY(rect) }, - }; +- (NSArray> *)visibleFeaturesInRect:(CGRect)rect + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers + predicate:(NSPredicate *)predicate { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", + NSStringFromCGRect(rect), styleLayerIdentifiers, predicate); + mbgl::ScreenBox screenBox = { + {CGRectGetMinX(rect), CGRectGetMinY(rect)}, + {CGRectGetMaxX(rect), CGRectGetMaxY(rect)}, + }; - std::optional> optionalLayerIDs; - if (styleLayerIdentifiers) { - __block std::vector layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); + std::optional> optionalLayerIDs; + if (styleLayerIdentifiers) { + __block std::vector layerIDs; + layerIDs.reserve(styleLayerIdentifiers.count); + [styleLayerIdentifiers + enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) { + layerIDs.push_back(identifier.UTF8String); }]; - optionalLayerIDs = layerIDs; - } + optionalLayerIDs = layerIDs; + } - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenBox, { optionalLayerIDs, optionalFilter }); - return MLNFeaturesFromMBGLFeatures(features); + std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures( + screenBox, {optionalLayerIDs, optionalFilter}); + return MLNFeaturesFromMBGLFeatures(features); } // MARK: - Utility - -- (void)animateWithDelay:(NSTimeInterval)delay animations:(void (^)(void))animations -{ - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), animations); +- (void)animateWithDelay:(NSTimeInterval)delay animations:(void (^)(void))animations { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), + dispatch_get_main_queue(), animations); } -- (CGFloat)currentMinimumZoom -{ - return fmaxf(*self.mbglMap.getBounds().minZoom, MLNMinimumZoom); +- (CGFloat)currentMinimumZoom { + return fmaxf(*self.mbglMap.getBounds().minZoom, MLNMinimumZoom); } -- (BOOL)isRotationAllowed -{ - return (self.zoomLevel >= self.currentMinimumZoom); +- (BOOL)isRotationAllowed { + return (self.zoomLevel >= self.currentMinimumZoom); } -- (void)unrotateIfNeededForGesture -{ - // Avoid contention with in-progress gestures. - UIGestureRecognizerState state = self.pinch.state; - if (self.direction != 0 - && state != UIGestureRecognizerStateBegan - && state != UIGestureRecognizerStateChanged) - { - [self unrotateIfNeededAnimated:YES]; +- (void)unrotateIfNeededForGesture { + // Avoid contention with in-progress gestures. + UIGestureRecognizerState state = self.pinch.state; + if (self.direction != 0 && state != UIGestureRecognizerStateBegan && + state != UIGestureRecognizerStateChanged) { + [self unrotateIfNeededAnimated:YES]; - // Snap to north. - if ((self.direction < self.toleranceForSnappingToNorth - || self.direction > 360 - self.toleranceForSnappingToNorth) - && self.userTrackingMode != MLNUserTrackingModeFollowWithHeading - && self.userTrackingMode != MLNUserTrackingModeFollowWithCourse) - { - [self resetNorthAnimated:YES]; - } + // Snap to north. + if ((self.direction < self.toleranceForSnappingToNorth || + self.direction > 360 - self.toleranceForSnappingToNorth) && + self.userTrackingMode != MLNUserTrackingModeFollowWithHeading && + self.userTrackingMode != MLNUserTrackingModeFollowWithCourse) { + [self resetNorthAnimated:YES]; } + } } /// Rotate back to true north if the map view is zoomed too far out. -- (void)unrotateIfNeededAnimated:(BOOL)animated -{ - if (self.direction != 0 && ! self.isRotationAllowed - && self.userTrackingState != MLNUserTrackingStateBegan) - { - if (animated) - { - self.userInteractionEnabled = NO; +- (void)unrotateIfNeededAnimated:(BOOL)animated { + if (self.direction != 0 && !self.isRotationAllowed && + self.userTrackingState != MLNUserTrackingStateBegan) { + if (animated) { + self.userInteractionEnabled = NO; - __weak MLNMapView *weakSelf = self; + __weak MLNMapView *weakSelf = self; - [self animateWithDelay:0.1 animations:^ - { - [weakSelf resetNorthAnimated:YES]; + [self animateWithDelay:0.1 + animations:^{ + [weakSelf resetNorthAnimated:YES]; - [self animateWithDelay:MLNAnimationDuration animations:^ - { - weakSelf.userInteractionEnabled = YES; - }]; - - }]; - } - else - { - [self resetNorthAnimated:NO]; - } + [self animateWithDelay:MLNAnimationDuration + animations:^{ + weakSelf.userInteractionEnabled = YES; + }]; + }]; + } else { + [self resetNorthAnimated:NO]; } + } } - (void)cameraWillChangeAnimated:(BOOL)animated { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - if ( ! _userLocationAnnotationIsSelected - || self.userTrackingMode == MLNUserTrackingModeNone - || self.userTrackingState != MLNUserTrackingStateChanged) - { - UIView *calloutView = self.calloutViewForSelectedAnnotation; - BOOL dismissesAutomatically = (calloutView - && [calloutView respondsToSelector:@selector(dismissesAutomatically)] - && calloutView.dismissesAutomatically); - // dismissesAutomatically is an optional property and we want to dismiss - // the callout view if it's unimplemented. - if (dismissesAutomatically || (calloutView && ![calloutView respondsToSelector:@selector(dismissesAutomatically)])) - { - [self deselectAnnotation:self.selectedAnnotation animated:NO]; - } + if (!_userLocationAnnotationIsSelected || self.userTrackingMode == MLNUserTrackingModeNone || + self.userTrackingState != MLNUserTrackingStateChanged) { + UIView *calloutView = self.calloutViewForSelectedAnnotation; + BOOL dismissesAutomatically = + (calloutView && [calloutView respondsToSelector:@selector(dismissesAutomatically)] && + calloutView.dismissesAutomatically); + // dismissesAutomatically is an optional property and we want to dismiss + // the callout view if it's unimplemented. + if (dismissesAutomatically || + (calloutView && ![calloutView respondsToSelector:@selector(dismissesAutomatically)])) { + [self deselectAnnotation:self.selectedAnnotation animated:NO]; } + } - if ( ! [self isSuppressingChangeDelimiters] ) - { - if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)]) - { - [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; - } - else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) - { - [self.delegate mapView:self regionWillChangeAnimated:animated]; - } + if (![self isSuppressingChangeDelimiters]) { + if ([self.delegate respondsToSelector:@selector(mapView: + regionWillChangeWithReason:animated:)]) { + [self.delegate mapView:self + regionWillChangeWithReason:self.cameraChangeReasonBitmask + animated:animated]; + } else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) { + [self.delegate mapView:self regionWillChangeAnimated:animated]; } + } } - (void)cameraIsChanging { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - [self updateCompass]; - [self updateScaleBar]; + [self updateCompass]; + [self updateScaleBar]; - if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)]) - { - [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask]; - } - else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) - { - [self.delegate mapViewRegionIsChanging:self]; - } + if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)]) { + [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask]; + } else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { + [self.delegate mapViewRegionIsChanging:self]; + } } - (void)cameraDidChangeAnimated:(BOOL)animated { - if (!_mbglMap) - { - return; + if (!_mbglMap) { + return; + } + + [self updateCompass]; + [self updateScaleBar]; + + if (![self isSuppressingChangeDelimiters]) { + BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView: + regionDidChangeAnimated:)]; + BOOL respondsToSelectorWithReason = + [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)]; + + if ((respondsToSelector || respondsToSelectorWithReason) && + ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)) { + _featureAccessibilityElements = nil; + _visiblePlaceFeatures = nil; + _visibleRoadFeatures = nil; + if (_accessibilityValueAnnouncementIsPending) { + _accessibilityValueAnnouncementIsPending = NO; + [self performSelector:@selector(announceAccessibilityValue) withObject:nil afterDelay:0.1]; + } else { + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + } } - [self updateCompass]; - [self updateScaleBar]; - - if ( ! [self isSuppressingChangeDelimiters]) - { - BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]; - BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)]; - - if ((respondsToSelector || respondsToSelectorWithReason) && - ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)) - { - _featureAccessibilityElements = nil; - _visiblePlaceFeatures = nil; - _visibleRoadFeatures = nil; - if (_accessibilityValueAnnouncementIsPending) { - _accessibilityValueAnnouncementIsPending = NO; - [self performSelector:@selector(announceAccessibilityValue) withObject:nil afterDelay:0.1]; - } else { - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); - } - } - - if (respondsToSelectorWithReason) - { - [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; - } - else if (respondsToSelector) - { - [self.delegate mapView:self regionDidChangeAnimated:animated]; - } - - [self resetCameraChangeReason]; + if (respondsToSelectorWithReason) { + [self.delegate mapView:self + regionDidChangeWithReason:self.cameraChangeReasonBitmask + animated:animated]; + } else if (respondsToSelector) { + [self.delegate mapView:self regionDidChangeAnimated:animated]; } + + [self resetCameraChangeReason]; + } } -- (void)announceAccessibilityValue -{ - UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue); - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); +- (void)announceAccessibilityValue { + UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, self.accessibilityValue); + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); } - (void)mapViewWillStartLoadingMap { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) - { - [self.delegate mapViewWillStartLoadingMap:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) { + [self.delegate mapViewWillStartLoadingMap:self]; + } } - (void)mapViewDidFinishLoadingMap { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - [self.style willChangeValueForKey:@"sources"]; - [self.style didChangeValueForKey:@"sources"]; - [self.style willChangeValueForKey:@"layers"]; - [self.style didChangeValueForKey:@"layers"]; - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) - { - [self.delegate mapViewDidFinishLoadingMap:self]; - } + [self.style willChangeValueForKey:@"sources"]; + [self.style didChangeValueForKey:@"sources"]; + [self.style willChangeValueForKey:@"layers"]; + [self.style didChangeValueForKey:@"layers"]; + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) { + [self.delegate mapViewDidFinishLoadingMap:self]; + } } - (void)mapViewDidFailLoadingMapWithError:(NSError *)error { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) - { - [self.delegate mapViewDidFailLoadingMap:self withError:error]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) { + [self.delegate mapViewDidFailLoadingMap:self withError:error]; + } } - (void)mapViewWillStartRenderingFrame { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) - { - [self.delegate mapViewWillStartRenderingFrame:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) { + [self.delegate mapViewWillStartRenderingFrame:self]; + } } - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered renderingStats:(const mbgl::gfx::RenderingStats &)stats { - if (!_mbglMap) - { - return; + if (!_mbglMap) { + return; + } + + if (_isChangingAnnotationLayers) { + _isChangingAnnotationLayers = NO; + [self.style didChangeValueForKey:@"layers"]; + } + + if ([self.delegate respondsToSelector:@selector + (mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)]) { + if (!_renderingStats) { + _renderingStats = [[MLNRenderingStats alloc] init]; } - if (_isChangingAnnotationLayers) - { - _isChangingAnnotationLayers = NO; - [self.style didChangeValueForKey:@"layers"]; - } - - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)]) - { - if (!_renderingStats) { - _renderingStats = [[MLNRenderingStats alloc] init]; - } - - [_renderingStats setCoreData:stats]; - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered renderingStats:_renderingStats]; - } - else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)]) - { - [self.delegate mapViewDidFinishRenderingFrame:self - fullyRendered:fullyRendered - frameEncodingTime:stats.encodingTime - frameRenderingTime:stats.renderingTime]; - } - else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) - { - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; - } + [_renderingStats setCoreData:stats]; + [self.delegate mapViewDidFinishRenderingFrame:self + fullyRendered:fullyRendered + renderingStats:_renderingStats]; + } else if ([self.delegate + respondsToSelector:@selector + (mapViewDidFinishRenderingFrame: + fullyRendered:frameEncodingTime:frameRenderingTime:)]) { + [self.delegate mapViewDidFinishRenderingFrame:self + fullyRendered:fullyRendered + frameEncodingTime:stats.encodingTime + frameRenderingTime:stats.renderingTime]; + } else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame: + fullyRendered:)]) { + [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; + } } - (void)mapViewWillStartRenderingMap { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) - { - [self.delegate mapViewWillStartRenderingMap:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) { + [self.delegate mapViewWillStartRenderingMap:self]; + } } - (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) - { - [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) { + [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered]; + } } - (void)mapViewDidBecomeIdle { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) { - [self.delegate mapViewDidBecomeIdle:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) { + [self.delegate mapViewDidBecomeIdle:self]; + } } - (void)mapViewDidFinishLoadingStyle { - if (!_mbglMap) - { - return; - } + if (!_mbglMap) { + return; + } - self.style = [[MLNStyle alloc] initWithRawStyle:&self.mbglMap.getStyle() stylable:self]; - if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) - { - [self.delegate mapView:self didFinishLoadingStyle:self.style]; - } + self.style = [[MLNStyle alloc] initWithRawStyle:&self.mbglMap.getStyle() stylable:self]; + if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) { + [self.delegate mapView:self didFinishLoadingStyle:self.style]; + } } - (void)sourceDidChange:(MLNSource *)source { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:sourceDidChange:)]) { - [self.delegate mapView:self sourceDidChange:source]; - } + if ([self.delegate respondsToSelector:@selector(mapView:sourceDidChange:)]) { + [self.delegate mapView:self sourceDidChange:source]; + } } - (void)didFailToLoadImage:(NSString *)imageName { - - if ([self.delegate respondsToSelector:@selector(mapView:didFailToLoadImage:)]) { - MLNImage *imageToLoad = [self.delegate mapView:self didFailToLoadImage:imageName]; - if (imageToLoad) { - auto image = [imageToLoad mgl_styleImageWithIdentifier:imageName]; - self.mbglMap.getStyle().addImage(std::move(image)); - } + if ([self.delegate respondsToSelector:@selector(mapView:didFailToLoadImage:)]) { + MLNImage *imageToLoad = [self.delegate mapView:self didFailToLoadImage:imageName]; + if (imageToLoad) { + auto image = [imageToLoad mgl_styleImageWithIdentifier:imageName]; + self.mbglMap.getStyle().addImage(std::move(image)); } + } } - (BOOL)shouldRemoveStyleImage:(NSString *)imageName { - if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) { - return [self.delegate mapView:self shouldRemoveStyleImage:imageName]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) { + return [self.delegate mapView:self shouldRemoveStyleImage:imageName]; + } - return YES; + return YES; } -- (void)shaderWillCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderWillCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderWillCompile:backend:defines:)]) { - [self.delegate mapView:self shaderWillCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderWillCompile:backend:defines:)]) { + [self.delegate mapView:self shaderWillCompile:id backend:backend defines:defines]; + } } -- (void)shaderDidCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderDidCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderDidCompile:backend:defines:)]) { - [self.delegate mapView:self shaderDidCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderDidCompile:backend:defines:)]) { + [self.delegate mapView:self shaderDidCompile:id backend:backend defines:defines]; + } } -- (void)shaderDidFailCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderDidFailCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderDidFailCompile:backend:defines:)]) { - [self.delegate mapView:self shaderDidFailCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderDidFailCompile:backend:defines:)]) { + [self.delegate mapView:self shaderDidFailCompile:id backend:backend defines:defines]; + } } -- (void)glyphsWillLoad:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsWillLoad:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsWillLoad:range:)]) { - [self.delegate mapView:self glyphsWillLoad:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsWillLoad:range:)]) { + [self.delegate mapView:self glyphsWillLoad:fontStack range:range]; + } } -- (void)glyphsDidLoad:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsDidLoad:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidLoad:range:)]) { - [self.delegate mapView:self glyphsDidLoad:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidLoad:range:)]) { + [self.delegate mapView:self glyphsDidLoad:fontStack range:range]; + } } -- (void)glyphsDidError:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsDidError:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidError:range:)]) { - [self.delegate mapView:self glyphsDidError:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidError:range:)]) { + [self.delegate mapView:self glyphsDidError:fontStack range:range]; + } } - (void)tileDidTriggerAction:(MLNTileOperation)operation @@ -7235,678 +7054,656 @@ static void *windowScreenContext = &windowScreenContext; wrap:(NSInteger)wrap overscaledZ:(NSInteger)overscaledZ sourceID:(nonnull NSString *)sourceID { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:tileDidTriggerAction:x:y:z:wrap:overscaledZ:sourceID:)]) { - [self.delegate mapView:self tileDidTriggerAction:operation x:x y:y z:z wrap:wrap overscaledZ:overscaledZ sourceID:sourceID]; - } + if ([self.delegate respondsToSelector:@selector + (mapView:tileDidTriggerAction:x:y:z:wrap:overscaledZ:sourceID:)]) { + [self.delegate mapView:self + tileDidTriggerAction:operation + x:x + y:y + z:z + wrap:wrap + overscaledZ:overscaledZ + sourceID:sourceID]; + } } - (void)spriteWillLoad:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteWillLoad:url:)]) { - [self.delegate mapView:self spriteWillLoad:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteWillLoad:url:)]) { + [self.delegate mapView:self spriteWillLoad:id url:url]; + } } - (void)spriteDidLoad:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteDidLoad:url:)]) { - [self.delegate mapView:self spriteDidLoad:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteDidLoad:url:)]) { + [self.delegate mapView:self spriteDidLoad:id url:url]; + } } - (void)spriteDidError:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteDidError:url:)]) { - [self.delegate mapView:self spriteDidError:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteDidError:url:)]) { + [self.delegate mapView:self spriteDidError:id url:url]; + } } -- (void)updateUserLocationAnnotationView -{ - [self updateUserLocationAnnotationViewAnimatedWithDuration:0]; +- (void)updateUserLocationAnnotationView { + [self updateUserLocationAnnotationViewAnimatedWithDuration:0]; } -- (void)updateAnnotationViews -{ - BOOL delegateImplementsViewForAnnotation = [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; +- (void)updateAnnotationViews { + BOOL delegateImplementsViewForAnnotation = + [self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]; - if (!delegateImplementsViewForAnnotation) - { - return; + if (!delegateImplementsViewForAnnotation) { + return; + } + + // If the map is pitched consider the viewport to be exactly the same as the bounds. + // Otherwise, add a small buffer. + CGFloat largestWidth = MAX(_largestAnnotationViewSize.width, CGRectGetWidth(self.frame)); + CGFloat largestHeight = MAX(_largestAnnotationViewSize.height, CGRectGetHeight(self.frame)); + CGFloat widthAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestWidth * 2.0; + CGFloat heightAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestHeight * 2.0; + CGRect viewPort = CGRectInset(self.bounds, widthAdjustment, heightAdjustment); + + NSArray *visibleAnnotations = [self visibleAnnotationsInRect:viewPort]; + NSMutableArray *offscreenAnnotations = [self.annotations mutableCopy]; + [offscreenAnnotations removeObjectsInArray:visibleAnnotations]; + + // Update the center of visible annotation views + for (id annotation in visibleAnnotations) { + // Defer to the shape/polygon styling delegate methods + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + continue; } - // If the map is pitched consider the viewport to be exactly the same as the bounds. - // Otherwise, add a small buffer. - CGFloat largestWidth = MAX(_largestAnnotationViewSize.width, CGRectGetWidth(self.frame)); - CGFloat largestHeight = MAX(_largestAnnotationViewSize.height, CGRectGetHeight(self.frame)); - CGFloat widthAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestWidth * 2.0; - CGFloat heightAdjustment = self.camera.pitch > 0.0 ? 0.0 : -largestHeight * 2.0; - CGRect viewPort = CGRectInset(self.bounds, widthAdjustment, heightAdjustment); + // Get the annotation tag then use it to get the context. + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + MLNAssert(annotationTag != MLNAnnotationTagNotFound, + @"-visibleAnnotationsInRect: returned unrecognized annotation"); + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - NSArray *visibleAnnotations = [self visibleAnnotationsInRect:viewPort]; - NSMutableArray *offscreenAnnotations = [self.annotations mutableCopy]; - [offscreenAnnotations removeObjectsInArray:visibleAnnotations]; + MLNAnnotationView *annotationView = annotationContext.annotationView; + if (!annotationView) { + // This will dequeue views if the delegate implements the dequeue call + annotationView = [self annotationViewForAnnotation:annotationContext.annotation]; - // Update the center of visible annotation views - for (id annotation in visibleAnnotations) - { - // Defer to the shape/polygon styling delegate methods - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - continue; - } - - // Get the annotation tag then use it to get the context. - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - MLNAssert(annotationTag != MLNAnnotationTagNotFound, @"-visibleAnnotationsInRect: returned unrecognized annotation"); - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - - MLNAnnotationView *annotationView = annotationContext.annotationView; - if (!annotationView) - { - // This will dequeue views if the delegate implements the dequeue call - annotationView = [self annotationViewForAnnotation:annotationContext.annotation]; - - if (annotationView) - { - annotationView.mapView = self; - annotationContext.annotationView = annotationView; - - // New annotation (created because there is nothing to dequeue) may not have been added to the - // container view yet. Add them here. - if (!annotationView.superview) { - [self.annotationContainerView insertSubview:annotationView atIndex:0]; - } - } - } - - if (annotationView) - { - annotationView.center = MLNPointRounded([self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]); + if (annotationView) { + annotationView.mapView = self; + annotationContext.annotationView = annotationView; + + // New annotation (created because there is nothing to dequeue) may not have been added to + // the container view yet. Add them here. + if (!annotationView.superview) { + [self.annotationContainerView insertSubview:annotationView atIndex:0]; } + } } - MLNCoordinateBounds coordinateBounds = [self convertRect:viewPort toCoordinateBoundsFromView:self]; - - // Enqueue (and move if required) offscreen annotation views - for (id annotation in offscreenAnnotations) - { - // Defer to the shape/polygon styling delegate methods - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - continue; - } - - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - MLNAssert(annotationTag != MLNAnnotationTagNotFound, @"-visibleAnnotationsInRect: returned unrecognized annotation"); - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - UIView *annotationView = annotationContext.annotationView; - - if (annotationView) - { - CLLocationCoordinate2D coordinate = annotation.coordinate; - - // Every so often (1 out of 1000 frames?) the mbgl query mechanism fails. This logic spot checks the - // offscreenAnnotations values -- if they are actually still on screen then the view center is - // moved and the enqueue operation is avoided. This allows us to keep the performance benefit of - // using the mbgl query result. It also forces views that have just gone offscreen to be cleared - // fully from view. - if (MLNCoordinateInCoordinateBounds(coordinate, coordinateBounds)) - { - annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate toPointToView:self]; - } - else - { - if (annotationView.layer.animationKeys.count > 0) { - continue; - } - - // Move the annotation view far out of view to the left - CGPoint adjustedCenter = annotationView.center; - adjustedCenter.x = -CGRectGetWidth(self.frame) * 10.0; - annotationView.center = adjustedCenter; - - [self enqueueAnnotationViewForAnnotationContext:annotationContext]; - } - } + if (annotationView) { + annotationView.center = + MLNPointRounded([self convertCoordinate:annotationContext.annotation.coordinate + toPointToView:self]); } + } + + MLNCoordinateBounds coordinateBounds = [self convertRect:viewPort + toCoordinateBoundsFromView:self]; + + // Enqueue (and move if required) offscreen annotation views + for (id annotation in offscreenAnnotations) { + // Defer to the shape/polygon styling delegate methods + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + continue; + } + + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + MLNAssert(annotationTag != MLNAnnotationTagNotFound, + @"-visibleAnnotationsInRect: returned unrecognized annotation"); + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + UIView *annotationView = annotationContext.annotationView; + + if (annotationView) { + CLLocationCoordinate2D coordinate = annotation.coordinate; + + // Every so often (1 out of 1000 frames?) the mbgl query mechanism fails. This logic spot + // checks the offscreenAnnotations values -- if they are actually still on screen then the + // view center is moved and the enqueue operation is avoided. This allows us to keep the + // performance benefit of using the mbgl query result. It also forces views that have just + // gone offscreen to be cleared fully from view. + if (MLNCoordinateInCoordinateBounds(coordinate, coordinateBounds)) { + annotationView.center = [self convertCoordinate:annotationContext.annotation.coordinate + toPointToView:self]; + } else { + if (annotationView.layer.animationKeys.count > 0) { + continue; + } + + // Move the annotation view far out of view to the left + CGPoint adjustedCenter = annotationView.center; + adjustedCenter.x = -CGRectGetWidth(self.frame) * 10.0; + annotationView.center = adjustedCenter; + + [self enqueueAnnotationViewForAnnotationContext:annotationContext]; + } + } + } } -- (BOOL)hasAnAnchoredAnnotationCalloutView -{ - // TODO: Remove duplicate code. - UIView *calloutView = self.calloutViewForSelectedAnnotation; - id annotation = calloutView.representedObject; +- (BOOL)hasAnAnchoredAnnotationCalloutView { + // TODO: Remove duplicate code. + UIView *calloutView = self.calloutViewForSelectedAnnotation; + id annotation = calloutView.representedObject; - BOOL isAnchoredToAnnotation = (calloutView - && annotation - && [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)] - && calloutView.isAnchoredToAnnotation); - return isAnchoredToAnnotation; + BOOL isAnchoredToAnnotation = + (calloutView && annotation && + [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)] && + calloutView.isAnchoredToAnnotation); + return isAnchoredToAnnotation; } -- (void)updateCalloutView -{ - UIView *calloutView = self.calloutViewForSelectedAnnotation; - id annotation = calloutView.representedObject; +- (void)updateCalloutView { + UIView *calloutView = self.calloutViewForSelectedAnnotation; + id annotation = calloutView.representedObject; - BOOL isAnchoredToAnnotation = (calloutView - && annotation - && [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)] - && calloutView.isAnchoredToAnnotation); + BOOL isAnchoredToAnnotation = + (calloutView && annotation && + [calloutView respondsToSelector:@selector(isAnchoredToAnnotation)] && + calloutView.isAnchoredToAnnotation); - if (isAnchoredToAnnotation) - { - MLNAnnotationTag tag = [self annotationTagForAnnotation:annotation]; - MLNAnnotationView *annotationView = nil; + if (isAnchoredToAnnotation) { + MLNAnnotationTag tag = [self annotationTagForAnnotation:annotation]; + MLNAnnotationView *annotationView = nil; - if (tag != MLNAnnotationTagNotFound) { - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); - annotationView = annotationContext.annotationView; - } else if (annotation == self.userLocation) { - annotationView = self.userLocationAnnotationView; - } - - CGRect positioningRect = annotationView ? - annotationView.frame : - [self positioningRectForCalloutForAnnotationWithTag:tag]; - - MLNAssert( ! CGRectIsNull(positioningRect), @"Positioning rect should not be CGRectNull by this point"); - - CGPoint centerPoint = CGPointMake(CGRectGetMidX(positioningRect), CGRectGetMinY(positioningRect)); - - if ( ! CGPointEqualToPoint(calloutView.center, centerPoint)) { - calloutView.center = centerPoint; - } + if (tag != MLNAnnotationTagNotFound) { + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); + annotationView = annotationContext.annotationView; + } else if (annotation == self.userLocation) { + annotationView = self.userLocationAnnotationView; } + + CGRect positioningRect = annotationView + ? annotationView.frame + : [self positioningRectForCalloutForAnnotationWithTag:tag]; + + MLNAssert(!CGRectIsNull(positioningRect), + @"Positioning rect should not be CGRectNull by this point"); + + CGPoint centerPoint = + CGPointMake(CGRectGetMidX(positioningRect), CGRectGetMinY(positioningRect)); + + if (!CGPointEqualToPoint(calloutView.center, centerPoint)) { + calloutView.center = centerPoint; + } + } } - (void)updateAttributionAlertView { - if (self.attributionController.presentingViewController) { - self.attributionController.popoverPresentationController.sourceRect = self.attributionButton.frame; - switch (self.attributionButtonPosition) { - case MLNOrnamentPositionTopLeft: - case MLNOrnamentPositionTopRight: - [self.attributionController.popoverPresentationController setPermittedArrowDirections:UIMenuControllerArrowUp]; - break; - case MLNOrnamentPositionBottomLeft: - case MLNOrnamentPositionBottomRight: - [self.attributionController.popoverPresentationController setPermittedArrowDirections:UIMenuControllerArrowDown]; - break; - } - [self.attributionController.popoverPresentationController.containerView setNeedsLayout]; + if (self.attributionController.presentingViewController) { + self.attributionController.popoverPresentationController.sourceRect = + self.attributionButton.frame; + switch (self.attributionButtonPosition) { + case MLNOrnamentPositionTopLeft: + case MLNOrnamentPositionTopRight: + [self.attributionController.popoverPresentationController + setPermittedArrowDirections:UIMenuControllerArrowUp]; + break; + case MLNOrnamentPositionBottomLeft: + case MLNOrnamentPositionBottomRight: + [self.attributionController.popoverPresentationController + setPermittedArrowDirections:UIMenuControllerArrowDown]; + break; } + [self.attributionController.popoverPresentationController.containerView setNeedsLayout]; + } } -- (void)enqueueAnnotationViewForAnnotationContext:(MLNAnnotationContext &)annotationContext -{ - MLNAnnotationView *annotationView = annotationContext.annotationView; +- (void)enqueueAnnotationViewForAnnotationContext:(MLNAnnotationContext &)annotationContext { + MLNAnnotationView *annotationView = annotationContext.annotationView; - if (!annotationView) return; + if (!annotationView) return; - if (annotationContext.viewReuseIdentifier) - { - annotationView.annotation = nil; - NSMutableArray *annotationViewReuseQueue = [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier]; - if (![annotationViewReuseQueue containsObject:annotationView]) - { - [annotationViewReuseQueue addObject:annotationView]; - annotationContext.annotationView = nil; - } + if (annotationContext.viewReuseIdentifier) { + annotationView.annotation = nil; + NSMutableArray *annotationViewReuseQueue = + [self annotationViewReuseQueueForIdentifier:annotationContext.viewReuseIdentifier]; + if (![annotationViewReuseQueue containsObject:annotationView]) { + [annotationViewReuseQueue addObject:annotationView]; + annotationContext.annotationView = nil; } + } } -- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration -{ - MLNUserLocationAnnotationView *annotationView = self.userLocationAnnotationView; - if ( ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { - annotationView.hidden = YES; - return; +- (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration { + MLNUserLocationAnnotationView *annotationView = self.userLocationAnnotationView; + if (!CLLocationCoordinate2DIsValid(self.userLocation.coordinate)) { + annotationView.hidden = YES; + return; + } + + CGPoint userPoint; + if (self.userTrackingMode != MLNUserTrackingModeNone && + self.userTrackingState == MLNUserTrackingStateChanged) { + userPoint = self.userLocationAnnotationViewCenter; + } else { + userPoint = MLNPointRounded([self convertCoordinate:self.userLocation.coordinate + toPointToView:self]); + } + + if (!annotationView.superview) { + [_mbglView->getView() addSubview:annotationView]; + // Prevents the view from sliding in from the origin. + annotationView.center = userPoint; + } + + if (CGRectContainsPoint(CGRectInset(self.bounds, -MLNAnnotationUpdateViewportOutset.width, + -MLNAnnotationUpdateViewportOutset.height), + userPoint)) { + // Smoothly move the user location annotation view and callout view to + // the new location. + + dispatch_block_t animation = ^{ + if (self.selectedAnnotation == self.userLocation) { + UIView *calloutView = self.calloutViewForSelectedAnnotation; + calloutView.frame = CGRectOffset(calloutView.frame, userPoint.x - annotationView.center.x, + userPoint.y - annotationView.center.y); + } + annotationView.center = userPoint; + }; + + if (duration > 0) { + [UIView animateWithDuration:duration + delay:0 + options:(UIViewAnimationOptionCurveLinear | + UIViewAnimationOptionAllowUserInteraction | + UIViewAnimationOptionBeginFromCurrentState) + animations:animation + completion:NULL]; + } else { + animation(); } + _userLocationAnimationCompletionDate = [NSDate dateWithTimeIntervalSinceNow:duration]; - CGPoint userPoint; - if (self.userTrackingMode != MLNUserTrackingModeNone - && self.userTrackingState == MLNUserTrackingStateChanged) - { - userPoint = self.userLocationAnnotationViewCenter; - } - else - { - userPoint = MLNPointRounded([self convertCoordinate:self.userLocation.coordinate toPointToView:self]); - } - - if ( ! annotationView.superview) - { - [_mbglView->getView() addSubview:annotationView]; - // Prevents the view from sliding in from the origin. - annotationView.center = userPoint; - } - - if (CGRectContainsPoint(CGRectInset(self.bounds, -MLNAnnotationUpdateViewportOutset.width, - -MLNAnnotationUpdateViewportOutset.height), userPoint)) - { - // Smoothly move the user location annotation view and callout view to - // the new location. - - dispatch_block_t animation = ^{ - if (self.selectedAnnotation == self.userLocation) - { - UIView *calloutView = self.calloutViewForSelectedAnnotation; - calloutView.frame = CGRectOffset(calloutView.frame, - userPoint.x - annotationView.center.x, - userPoint.y - annotationView.center.y); - } - annotationView.center = userPoint; - }; - - if (duration > 0) { - [UIView animateWithDuration:duration - delay:0 - options:(UIViewAnimationOptionCurveLinear | - UIViewAnimationOptionAllowUserInteraction | - UIViewAnimationOptionBeginFromCurrentState) - animations:animation - completion:NULL]; - } - else { - animation(); - } - _userLocationAnimationCompletionDate = [NSDate dateWithTimeIntervalSinceNow:duration]; - - annotationView.hidden = NO; - [annotationView update]; - } - else - { - // User has moved far enough outside of the viewport that showing it or - // its callout would be useless. - annotationView.hidden = YES; - - if (_userLocationAnnotationIsSelected) - { - [self deselectAnnotation:self.selectedAnnotation animated:YES]; - } + annotationView.hidden = NO; + [annotationView update]; + } else { + // User has moved far enough outside of the viewport that showing it or + // its callout would be useless. + annotationView.hidden = YES; + + if (_userLocationAnnotationIsSelected) { + [self deselectAnnotation:self.selectedAnnotation animated:YES]; } + } } /// Intended center point of the user location annotation view with respect to /// the overall map view (but respecting the content inset). -- (CGPoint)userLocationAnnotationViewCenter -{ - if ([self.delegate respondsToSelector:@selector(mapViewUserLocationAnchorPoint:)]) - { - CGPoint anchorPoint = [self.delegate mapViewUserLocationAnchorPoint:self]; - return CGPointMake(anchorPoint.x + self.contentInset.left, anchorPoint.y + self.contentInset.top); - } +- (CGPoint)userLocationAnnotationViewCenter { + if ([self.delegate respondsToSelector:@selector(mapViewUserLocationAnchorPoint:)]) { + CGPoint anchorPoint = [self.delegate mapViewUserLocationAnchorPoint:self]; + return CGPointMake(anchorPoint.x + self.contentInset.left, + anchorPoint.y + self.contentInset.top); + } - CGRect contentFrame = UIEdgeInsetsInsetRect(self.contentFrame, self.edgePaddingForFollowingWithCourse); + CGRect contentFrame = + UIEdgeInsetsInsetRect(self.contentFrame, self.edgePaddingForFollowingWithCourse); - if (CGRectIsEmpty(contentFrame)) - { - contentFrame = self.contentFrame; - } + if (CGRectIsEmpty(contentFrame)) { + contentFrame = self.contentFrame; + } - CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame)); + CGPoint center = CGPointMake(CGRectGetMidX(contentFrame), CGRectGetMidY(contentFrame)); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - switch (self.userLocationVerticalAlignment) { - case MLNAnnotationVerticalAlignmentCenter: - break; - case MLNAnnotationVerticalAlignmentTop: - center.y = CGRectGetMinY(contentFrame); - break; - case MLNAnnotationVerticalAlignmentBottom: - center.y = CGRectGetMaxY(contentFrame); - break; - } + switch (self.userLocationVerticalAlignment) { + case MLNAnnotationVerticalAlignmentCenter: + break; + case MLNAnnotationVerticalAlignmentTop: + center.y = CGRectGetMinY(contentFrame); + break; + case MLNAnnotationVerticalAlignmentBottom: + center.y = CGRectGetMaxY(contentFrame); + break; + } #pragma clang diagnostic pop - return center; + return center; } -- (void)updateCompass -{ - [self.compassView updateCompass]; +- (void)updateCompass { + [self.compassView updateCompass]; } -- (void)updateScaleBar -{ - // Use the `hidden` property (instead of `self.showsScale`) so that we don't - // break developers who still rely on the <4.0.0 approach of directly - // setting this property. - if ( ! self.scaleBar.hidden) - { - [(MLNScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; - } +- (void)updateScaleBar { + // Use the `hidden` property (instead of `self.showsScale`) so that we don't + // break developers who still rely on the <4.0.0 approach of directly + // setting this property. + if (!self.scaleBar.hidden) { + [(MLNScaleBar *)self.scaleBar + setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; + } } -- (BOOL)isFullyLoaded -{ - return self.mbglMap.isFullyLoaded(); +- (BOOL)isFullyLoaded { + return self.mbglMap.isFullyLoaded(); } -- (void)prepareForInterfaceBuilder -{ - [super prepareForInterfaceBuilder]; +- (void)prepareForInterfaceBuilder { + [super prepareForInterfaceBuilder]; - self.layer.borderColor = [UIColor colorWithRed:59/255. - green:178/255. - blue:208/255. - alpha:0.8].CGColor; - self.layer.borderWidth = 4; - self.layer.backgroundColor = [UIColor whiteColor].CGColor; + self.layer.borderColor = + [UIColor colorWithRed:59 / 255. green:178 / 255. blue:208 / 255. alpha:0.8].CGColor; + self.layer.borderWidth = 4; + self.layer.backgroundColor = [UIColor whiteColor].CGColor; - UIView *diagnosticView = [[UIView alloc] init]; - diagnosticView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:diagnosticView]; + UIView *diagnosticView = [[UIView alloc] init]; + diagnosticView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:diagnosticView]; - // Headline - UILabel *headlineLabel = [[UILabel alloc] init]; - headlineLabel.text = NSStringFromClass([self class]); - headlineLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; - headlineLabel.textAlignment = NSTextAlignmentCenter; - headlineLabel.numberOfLines = 1; - headlineLabel.translatesAutoresizingMaskIntoConstraints = NO; - [headlineLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow - forAxis:UILayoutConstraintAxisHorizontal]; - [diagnosticView addSubview:headlineLabel]; + // Headline + UILabel *headlineLabel = [[UILabel alloc] init]; + headlineLabel.text = NSStringFromClass([self class]); + headlineLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; + headlineLabel.textAlignment = NSTextAlignmentCenter; + headlineLabel.numberOfLines = 1; + headlineLabel.translatesAutoresizingMaskIntoConstraints = NO; + [headlineLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:headlineLabel]; - // Explanation - UILabel *explanationLabel = [[UILabel alloc] init]; - explanationLabel.text = [NSString stringWithFormat:NSLocalizedStringWithDefaultValue(@"DESIGNABLE", nil, nil, @"To display a map which requires authoriaztion here, set %@ to your API key in %@", @"Instructions in Interface Builder designable; {key}, {plist file name}"), @"MLNApiKey", @"Info.plist"]; - explanationLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; - explanationLabel.numberOfLines = 0; - explanationLabel.translatesAutoresizingMaskIntoConstraints = NO; - [explanationLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow - forAxis:UILayoutConstraintAxisHorizontal]; - [diagnosticView addSubview:explanationLabel]; + // Explanation + UILabel *explanationLabel = [[UILabel alloc] init]; + explanationLabel.text = [NSString + stringWithFormat: + NSLocalizedStringWithDefaultValue( + @"DESIGNABLE", nil, nil, + @"To display a map which requires authoriaztion here, set %@ to your API key in %@", + @"Instructions in Interface Builder designable; {key}, {plist file name}"), + @"MLNApiKey", @"Info.plist"]; + explanationLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody]; + explanationLabel.numberOfLines = 0; + explanationLabel.translatesAutoresizingMaskIntoConstraints = NO; + [explanationLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:explanationLabel]; - // Link - UIButton *linkButton = [UIButton buttonWithType:UIButtonTypeSystem]; - [linkButton setTitle:NSLocalizedStringWithDefaultValue(@"FIRST_STEPS_URL", nil, nil, @"docs.mapbox.com/help/tutorials/first-steps-ios-sdk", @"Setup documentation URL display string; keep as short as possible") forState:UIControlStateNormal]; - linkButton.translatesAutoresizingMaskIntoConstraints = NO; - linkButton.titleLabel.numberOfLines = 0; - [linkButton setContentCompressionResistancePriority:UILayoutPriorityDefaultLow - forAxis:UILayoutConstraintAxisHorizontal]; - [diagnosticView addSubview:linkButton]; + // Link + UIButton *linkButton = [UIButton buttonWithType:UIButtonTypeSystem]; + [linkButton setTitle:NSLocalizedStringWithDefaultValue( + @"FIRST_STEPS_URL", nil, nil, + @"docs.mapbox.com/help/tutorials/first-steps-ios-sdk", + @"Setup documentation URL display string; keep as short as possible") + forState:UIControlStateNormal]; + linkButton.translatesAutoresizingMaskIntoConstraints = NO; + linkButton.titleLabel.numberOfLines = 0; + [linkButton setContentCompressionResistancePriority:UILayoutPriorityDefaultLow + forAxis:UILayoutConstraintAxisHorizontal]; + [diagnosticView addSubview:linkButton]; - // Constraints - NSDictionary *views = @{ - @"container": diagnosticView, - @"headline": headlineLabel, - @"explanation": explanationLabel, - @"link": linkButton, - }; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:diagnosticView - attribute:NSLayoutAttributeCenterYWithinMargins - relatedBy:NSLayoutRelationEqual - toItem:self - attribute:NSLayoutAttributeCenterYWithinMargins - multiplier:1 - constant:0]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:diagnosticView - attribute:NSLayoutAttributeTopMargin - relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:self - attribute:NSLayoutAttributeTopMargin - multiplier:1 - constant:8]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottomMargin - relatedBy:NSLayoutRelationGreaterThanOrEqual - toItem:diagnosticView - attribute:NSLayoutAttributeBottomMargin - multiplier:1 - constant:8]]; - [self addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[container(20@20)]-|" - options:NSLayoutFormatAlignAllCenterY - metrics:nil - views:views]]; - [self addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[headline]-[explanation]-[link]|" - options:0 - metrics:nil - views:views]]; - [self addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[headline]|" - options:0 - metrics:nil - views:views]]; - [self addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[explanation]|" - options:0 - metrics:nil - views:views]]; - [self addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[link]|" - options:0 - metrics:nil - views:views]]; + // Constraints + NSDictionary *views = @{ + @"container" : diagnosticView, + @"headline" : headlineLabel, + @"explanation" : explanationLabel, + @"link" : linkButton, + }; + [self addConstraint:[NSLayoutConstraint constraintWithItem:diagnosticView + attribute:NSLayoutAttributeCenterYWithinMargins + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeCenterYWithinMargins + multiplier:1 + constant:0]]; + [self addConstraint:[NSLayoutConstraint constraintWithItem:diagnosticView + attribute:NSLayoutAttributeTopMargin + relatedBy:NSLayoutRelationGreaterThanOrEqual + toItem:self + attribute:NSLayoutAttributeTopMargin + multiplier:1 + constant:8]]; + [self addConstraint:[NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeBottomMargin + relatedBy:NSLayoutRelationGreaterThanOrEqual + toItem:diagnosticView + attribute:NSLayoutAttributeBottomMargin + multiplier:1 + constant:8]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[container(20@20)]-|" + options:NSLayoutFormatAlignAllCenterY + metrics:nil + views:views]]; + [self addConstraints:[NSLayoutConstraint + constraintsWithVisualFormat:@"V:|[headline]-[explanation]-[link]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[headline]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[explanation]|" + options:0 + metrics:nil + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[link]|" + options:0 + metrics:nil + views:views]]; } -- (NSMutableArray *)annotationViewReuseQueueForIdentifier:(NSString *)identifier { - if (!_annotationViewReuseQueueByIdentifier[identifier]) - { - _annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array]; - } +- (NSMutableArray *)annotationViewReuseQueueForIdentifier: + (NSString *)identifier { + if (!_annotationViewReuseQueueByIdentifier[identifier]) { + _annotationViewReuseQueueByIdentifier[identifier] = [NSMutableArray array]; + } - return _annotationViewReuseQueueByIdentifier[identifier]; + return _annotationViewReuseQueueByIdentifier[identifier]; } - (BOOL)isRenderingStatsViewEnabled { - return _mbglMap->isRenderingStatsViewEnabled(); + return _mbglMap->isRenderingStatsViewEnabled(); } - (void)enableRenderingStatsView:(BOOL)value { - _mbglMap->enableRenderingStatsView(value); + _mbglMap->enableRenderingStatsView(value); } -- (void)triggerRepaint -{ - _mbglMap->triggerRepaint(); +- (void)triggerRepaint { + _mbglMap->triggerRepaint(); } /** Adds a plug-in layer that is external to this library */ --(void)addPluginLayerType:(Class)pluginLayerClass { +- (void)addPluginLayerType:(Class)pluginLayerClass { + auto layerManager = mbgl::LayerManager::get(); + auto darwinLayerManager = (mbgl::LayerManagerDarwin *)layerManager; - auto layerManager = mbgl::LayerManager::get(); - auto darwinLayerManager = (mbgl::LayerManagerDarwin *)layerManager; + MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities]; + + std::string layerType = [capabilities.layerID UTF8String]; + + // Default values + mbgl::style::LayerTypeInfo::Source source = mbgl::style::LayerTypeInfo::Source::NotRequired; + mbgl::style::LayerTypeInfo::TileKind tileKind = mbgl::style::LayerTypeInfo::TileKind::NotRequired; + mbgl::style::LayerTypeInfo::FadingTiles fadingTiles = + mbgl::style::LayerTypeInfo::FadingTiles::NotRequired; + mbgl::style::LayerTypeInfo::Layout layout = mbgl::style::LayerTypeInfo::Layout::NotRequired; + mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex = + mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired; + + mbgl::style::LayerTypeInfo::Pass3D pass3D = mbgl::style::LayerTypeInfo::Pass3D::NotRequired; + if (capabilities.requiresPass3D) { + pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required; + } + + auto factory = std::make_unique( + layerType, source, pass3D, layout, fadingTiles, crossTileIndex, tileKind); + + __weak MLNMapView *weakMapView = self; + + Class layerClass = pluginLayerClass; + factory->setOnLayerCreatedEvent([layerClass, weakMapView, + pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) { + // NSLog(@"Creating Plugin Layer: %@", layerClass); + MLNPluginLayer *layer = [[layerClass alloc] init]; + if (!weakMapView.pluginLayers) { + weakMapView.pluginLayers = [NSMutableArray array]; + } + [weakMapView.pluginLayers addObject:layer]; + + // Use weak here so there isn't a retain cycle + MLNPluginLayer *weakPlugInLayer = layer; + + pluginLayer->_platformReference = (__bridge void *)layer; MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities]; - - std::string layerType = [capabilities.layerID UTF8String]; - - // Default values - mbgl::style::LayerTypeInfo::Source source = mbgl::style::LayerTypeInfo::Source::NotRequired; - mbgl::style::LayerTypeInfo::TileKind tileKind = mbgl::style::LayerTypeInfo::TileKind::NotRequired; - mbgl::style::LayerTypeInfo::FadingTiles fadingTiles = mbgl::style::LayerTypeInfo::FadingTiles::NotRequired; - mbgl::style::LayerTypeInfo::Layout layout = mbgl::style::LayerTypeInfo::Layout::NotRequired; - mbgl::style::LayerTypeInfo::CrossTileIndex crossTileIndex = mbgl::style::LayerTypeInfo::CrossTileIndex::NotRequired; - - mbgl::style::LayerTypeInfo::Pass3D pass3D = mbgl::style::LayerTypeInfo::Pass3D::NotRequired; - if (capabilities.requiresPass3D) { - pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required; + auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); + auto &pm = pluginLayerImpl->_propertyManager; + for (MLNPluginLayerProperty *property in capabilities.layerProperties) { + mbgl::style::PluginLayerProperty *p = new mbgl::style::PluginLayerProperty(); + switch (property.propertyType) { + case MLNPluginLayerPropertyTypeSingleFloat: + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::SingleFloat; + p->_defaultSingleFloatValue = property.singleFloatDefaultValue; + break; + case MLNPluginLayerPropertyTypeColor: { + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Color; + if (property.colorDefaultValue) { + CGFloat r, g, b, a; + [property.colorDefaultValue getRed:&r green:&g blue:&b alpha:&a]; + p->_defaultColorValue = mbgl::Color(r, g, b, a); + } + } break; + default: + p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Unknown; + break; + } + p->_propertyName = [property.propertyName UTF8String]; + pm.addProperty(p); } - auto factory = std::make_unique(layerType, - source, - pass3D, - layout, - fadingTiles, - crossTileIndex, - tileKind); + // Set the render function + auto renderFunction = [weakPlugInLayer, weakMapView](mbgl::PaintParameters &paintParameters) { + const mbgl::mtl::RenderPass &renderPass = + static_cast(*paintParameters.renderPass); + id encoder = + (__bridge id)renderPass.getMetalEncoder().get(); - __weak MLNMapView *weakMapView = self; + MLNMapView *strongMapView = weakMapView; - Class layerClass = pluginLayerClass; - factory->setOnLayerCreatedEvent([layerClass, weakMapView, pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) { + const mbgl::TransformState &state = paintParameters.state; - //NSLog(@"Creating Plugin Layer: %@", layerClass); - MLNPluginLayer *layer = [[layerClass alloc] init]; - if (!weakMapView.pluginLayers) { - weakMapView.pluginLayers = [NSMutableArray array]; - } - [weakMapView.pluginLayers addObject:layer]; + MLNPluginLayerDrawingContext drawingContext; + drawingContext.size = CGSizeMake(state.getSize().width, state.getSize().height); + drawingContext.centerCoordinate = + CLLocationCoordinate2DMake(state.getLatLng().latitude(), state.getLatLng().longitude()); + drawingContext.zoomLevel = state.getZoom(); + drawingContext.direction = mbgl::util::rad2deg(-state.getBearing()); + drawingContext.pitch = state.getPitch(); + drawingContext.fieldOfView = state.getFieldOfView(); + drawingContext.projectionMatrix = MLNMatrix4Make(paintParameters.transformParams.projMatrix); + drawingContext.nearClippedProjMatrix = + MLNMatrix4Make(paintParameters.transformParams.nearClippedProjMatrix); - // Use weak here so there isn't a retain cycle - MLNPluginLayer *weakPlugInLayer = layer; + // Call update with the scene state variables + [weakPlugInLayer onUpdateLayer:drawingContext]; - pluginLayer->_platformReference = (__bridge void *)layer; + // Call render + [weakPlugInLayer onRenderLayer:strongMapView renderEncoder:encoder]; + }; - MLNPluginLayerCapabilities *capabilities = [pluginLayerClass layerCapabilities]; - auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); - auto & pm = pluginLayerImpl->_propertyManager; - for (MLNPluginLayerProperty *property in capabilities.layerProperties) { - mbgl::style::PluginLayerProperty *p = new mbgl::style::PluginLayerProperty(); - switch (property.propertyType) { - case MLNPluginLayerPropertyTypeSingleFloat: - p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::SingleFloat; - p->_defaultSingleFloatValue = property.singleFloatDefaultValue; - break; - case MLNPluginLayerPropertyTypeColor: - { - p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Color; - if (property.colorDefaultValue) { - CGFloat r, g, b, a; - [property.colorDefaultValue getRed:&r green:&g blue:&b alpha:&a]; - p->_defaultColorValue = mbgl::Color(r, g, b, a); - } - } - break; - default: - p->_propertyType = mbgl::style::PluginLayerProperty::PropertyType::Unknown; - break; - } - p->_propertyName = [property.propertyName UTF8String]; - pm.addProperty(p); - } - - // Set the render function - auto renderFunction = [weakPlugInLayer, weakMapView](mbgl::PaintParameters& paintParameters){ - - const mbgl::mtl::RenderPass& renderPass = static_cast(*paintParameters.renderPass); - id encoder = (__bridge id)renderPass.getMetalEncoder().get(); - - MLNMapView *strongMapView = weakMapView; - - const mbgl::TransformState& state = paintParameters.state; - - MLNPluginLayerDrawingContext drawingContext; - drawingContext.size = CGSizeMake(state.getSize().width, - state.getSize().height); - drawingContext.centerCoordinate = CLLocationCoordinate2DMake(state.getLatLng().latitude(), - state.getLatLng().longitude()); - drawingContext.zoomLevel = state.getZoom(); - drawingContext.direction = mbgl::util::rad2deg(-state.getBearing()); - drawingContext.pitch = state.getPitch(); - drawingContext.fieldOfView = state.getFieldOfView(); - drawingContext.projectionMatrix = MLNMatrix4Make(paintParameters.transformParams.projMatrix); - drawingContext.nearClippedProjMatrix = MLNMatrix4Make(paintParameters.transformParams.nearClippedProjMatrix); - - // Call update with the scene state variables - [weakPlugInLayer onUpdateLayer:drawingContext]; - - // Call render - [weakPlugInLayer onRenderLayer:strongMapView - renderEncoder:encoder]; - }; - - // Set the lambdas - //auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); - pluginLayerImpl->setRenderFunction(renderFunction); - - // Set the update properties function - pluginLayerImpl->setUpdatePropertiesFunction([weakPlugInLayer](const std::string & jsonProperties) { - // Use autorelease pools in lambdas - @autoreleasepool { - // Just wrap the string with NSData so it can be run through properties - NSData *d = [NSData dataWithBytesNoCopy:(void *)jsonProperties.data() length:jsonProperties.length() freeWhenDone:NO]; - NSError *error = nil; - NSDictionary *properties = [NSJSONSerialization JSONObjectWithData:d - options:0 - error:&error]; - if (error) { - // TODO: What should we do here? - } - [weakPlugInLayer onUpdateLayerProperties:properties]; + // Set the lambdas + // auto pluginLayerImpl = (mbgl::style::PluginLayer::Impl *)pluginLayer->baseImpl.get(); + pluginLayerImpl->setRenderFunction(renderFunction); + + // Set the update properties function + pluginLayerImpl->setUpdatePropertiesFunction( + [weakPlugInLayer](const std::string &jsonProperties) { + // Use autorelease pools in lambdas + @autoreleasepool { + // Just wrap the string with NSData so it can be run through properties + NSData *d = [NSData dataWithBytesNoCopy:(void *)jsonProperties.data() + length:jsonProperties.length() + freeWhenDone:NO]; + NSError *error = nil; + NSDictionary *properties = [NSJSONSerialization JSONObjectWithData:d + options:0 + error:&error]; + if (error) { + // TODO: What should we do here? } + [weakPlugInLayer onUpdateLayerProperties:properties]; + } }); + }); - }); - - // TODO: Same question as above. Do we ever want to have a core only layer type? - // This could actually be something that we could set in the layer capabilities class - darwinLayerManager->addLayerType(std::move(factory)); - //darwinLayerManager->addLayerTypeCoreOnly(std::move(factory)); - + // TODO: Same question as above. Do we ever want to have a core only layer type? + // This could actually be something that we could set in the layer capabilities class + darwinLayerManager->addLayerType(std::move(factory)); + // darwinLayerManager->addLayerTypeCoreOnly(std::move(factory)); } -- (NSArray*)getActionJournalLogFiles -{ - const auto& actionJournal = _mbglMap->getActionJournal(); - if (!actionJournal) { - return nil; - } +- (NSArray *)getActionJournalLogFiles { + const auto &actionJournal = _mbglMap->getActionJournal(); + if (!actionJournal) { + return nil; + } - const auto& files = actionJournal->getLogFiles(); - NSMutableArray* objcFiles = [NSMutableArray new]; + const auto &files = actionJournal->getLogFiles(); + NSMutableArray *objcFiles = [NSMutableArray new]; - for (const auto& file : files) { - [objcFiles addObject:[NSString stringWithUTF8String:file.c_str()]]; - } + for (const auto &file : files) { + [objcFiles addObject:[NSString stringWithUTF8String:file.c_str()]]; + } - return objcFiles; + return objcFiles; } -- (NSArray*)getActionJournalLog -{ - const auto& actionJournal = _mbglMap->getActionJournal(); - if (!actionJournal) { - return nil; - } +- (NSArray *)getActionJournalLog { + const auto &actionJournal = _mbglMap->getActionJournal(); + if (!actionJournal) { + return nil; + } - const auto& log = actionJournal->getLog(); - NSMutableArray* objcLog = [NSMutableArray new]; + const auto &log = actionJournal->getLog(); + NSMutableArray *objcLog = [NSMutableArray new]; - for (const auto& event : log) { - [objcLog addObject:[NSString stringWithUTF8String:event.c_str()]]; - } + for (const auto &event : log) { + [objcLog addObject:[NSString stringWithUTF8String:event.c_str()]]; + } - return objcLog; + return objcLog; } -- (void)clearActionJournalLog -{ - const auto& actionJournal = _mbglMap->getActionJournal(); - if (!actionJournal) { - return; - } +- (void)clearActionJournalLog { + const auto &actionJournal = _mbglMap->getActionJournal(); + if (!actionJournal) { + return; + } - actionJournal->clearLog(); + actionJournal->clearLog(); } - (MLNBackendResource *)backendResource { - return _mbglView->getObject(); + return _mbglView->getObject(); } @end @@ -7915,161 +7712,128 @@ static void *windowScreenContext = &windowScreenContext; @implementation MLNMapView (IBAdditions) -+ (NSSet *)keyPathsForValuesAffectingStyleURL__ -{ - return [NSSet setWithObject:@"styleURL"]; ++ (NSSet *)keyPathsForValuesAffectingStyleURL__ { + return [NSSet setWithObject:@"styleURL"]; } -- (nullable NSString *)styleURL__ -{ - return self.styleURL.absoluteString; +- (nullable NSString *)styleURL__ { + return self.styleURL.absoluteString; } -- (void)setStyleURL__:(nullable NSString *)URLString -{ - URLString = [URLString stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil; - if (URLString.length && !url) - { - [NSException raise:MLNInvalidStyleURLException - format:@"“%@” is not a valid style URL.", URLString]; - } - self.styleURL = url; +- (void)setStyleURL__:(nullable NSString *)URLString { + URLString = + [URLString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil; + if (URLString.length && !url) { + [NSException raise:MLNInvalidStyleURLException + format:@"“%@” is not a valid style URL.", URLString]; + } + self.styleURL = url; } -+ (NSSet *)keyPathsForValuesAffectingLatitude -{ - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; ++ (NSSet *)keyPathsForValuesAffectingLatitude { + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } -- (double)latitude -{ - return self.centerCoordinate.latitude; +- (double)latitude { + return self.centerCoordinate.latitude; } -- (void)setLatitude:(double)latitude -{ - if ( ! isnan(_pendingLongitude)) - { - self.centerCoordinate = CLLocationCoordinate2DMake(latitude, _pendingLongitude); - _pendingLatitude = NAN; - _pendingLongitude = NAN; - } - else - { - // Not enough info to make a valid center coordinate yet. Stash this - // latitude away until the longitude is set too. - _pendingLatitude = latitude; - } +- (void)setLatitude:(double)latitude { + if (!isnan(_pendingLongitude)) { + self.centerCoordinate = CLLocationCoordinate2DMake(latitude, _pendingLongitude); + _pendingLatitude = NAN; + _pendingLongitude = NAN; + } else { + // Not enough info to make a valid center coordinate yet. Stash this + // latitude away until the longitude is set too. + _pendingLatitude = latitude; + } } -+ (NSSet *)keyPathsForValuesAffectingLongitude -{ - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; ++ (NSSet *)keyPathsForValuesAffectingLongitude { + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } -- (double)longitude -{ - return self.centerCoordinate.longitude; +- (double)longitude { + return self.centerCoordinate.longitude; } -- (void)setLongitude:(double)longitude -{ - if ( ! isnan(_pendingLatitude)) - { - self.centerCoordinate = CLLocationCoordinate2DMake(_pendingLatitude, longitude); - _pendingLatitude = NAN; - _pendingLongitude = NAN; - } - else - { - // Not enough info to make a valid center coordinate yet. Stash this - // longitude away until the latitude is set too. - _pendingLongitude = longitude; - } +- (void)setLongitude:(double)longitude { + if (!isnan(_pendingLatitude)) { + self.centerCoordinate = CLLocationCoordinate2DMake(_pendingLatitude, longitude); + _pendingLatitude = NAN; + _pendingLongitude = NAN; + } else { + // Not enough info to make a valid center coordinate yet. Stash this + // longitude away until the latitude is set too. + _pendingLongitude = longitude; + } } -+ (NSSet *)keyPathsForValuesAffectingAllowsZooming -{ - return [NSSet setWithObject:@"zoomEnabled"]; ++ (NSSet *)keyPathsForValuesAffectingAllowsZooming { + return [NSSet setWithObject:@"zoomEnabled"]; } -- (BOOL)allowsZooming -{ - return self.zoomEnabled; +- (BOOL)allowsZooming { + return self.zoomEnabled; } -- (void)setAllowsZooming:(BOOL)allowsZooming -{ - MLNLogDebug(@"Setting allowsZooming: %@", MLNStringFromBOOL(allowsZooming)); - self.zoomEnabled = allowsZooming; +- (void)setAllowsZooming:(BOOL)allowsZooming { + MLNLogDebug(@"Setting allowsZooming: %@", MLNStringFromBOOL(allowsZooming)); + self.zoomEnabled = allowsZooming; } -+ (NSSet *)keyPathsForValuesAffectingAllowsScrolling -{ - return [NSSet setWithObject:@"scrollEnabled"]; ++ (NSSet *)keyPathsForValuesAffectingAllowsScrolling { + return [NSSet setWithObject:@"scrollEnabled"]; } -- (BOOL)allowsScrolling -{ - return self.scrollEnabled; +- (BOOL)allowsScrolling { + return self.scrollEnabled; } -- (void)setAllowsScrolling:(BOOL)allowsScrolling -{ - MLNLogDebug(@"Setting allowsScrolling: %@", MLNStringFromBOOL(allowsScrolling)); - self.scrollEnabled = allowsScrolling; +- (void)setAllowsScrolling:(BOOL)allowsScrolling { + MLNLogDebug(@"Setting allowsScrolling: %@", MLNStringFromBOOL(allowsScrolling)); + self.scrollEnabled = allowsScrolling; } -+ (NSSet *)keyPathsForValuesAffectingAllowsRotating -{ - return [NSSet setWithObject:@"rotateEnabled"]; ++ (NSSet *)keyPathsForValuesAffectingAllowsRotating { + return [NSSet setWithObject:@"rotateEnabled"]; } -- (BOOL)allowsRotating -{ - return self.rotateEnabled; +- (BOOL)allowsRotating { + return self.rotateEnabled; } -- (void)setAllowsRotating:(BOOL)allowsRotating -{ - MLNLogDebug(@"Setting allowsRotating: %@", MLNStringFromBOOL(allowsRotating)); - self.rotateEnabled = allowsRotating; +- (void)setAllowsRotating:(BOOL)allowsRotating { + MLNLogDebug(@"Setting allowsRotating: %@", MLNStringFromBOOL(allowsRotating)); + self.rotateEnabled = allowsRotating; } -+ (NSSet *)keyPathsForValuesAffectingAllowsTilting -{ - return [NSSet setWithObject:@"pitchEnabled"]; ++ (NSSet *)keyPathsForValuesAffectingAllowsTilting { + return [NSSet setWithObject:@"pitchEnabled"]; } -- (BOOL)allowsTilting -{ - return self.pitchEnabled; +- (BOOL)allowsTilting { + return self.pitchEnabled; } -- (void)setAllowsTilting:(BOOL)allowsTilting -{ - MLNLogDebug(@"Setting allowsTilting: %@", MLNStringFromBOOL(allowsTilting)); - self.pitchEnabled = allowsTilting; +- (void)setAllowsTilting:(BOOL)allowsTilting { + MLNLogDebug(@"Setting allowsTilting: %@", MLNStringFromBOOL(allowsTilting)); + self.pitchEnabled = allowsTilting; } -+ (NSSet *)keyPathsForValuesAffectingShowsHeading -{ - return [NSSet setWithObject:@"showsUserHeadingIndicator"]; ++ (NSSet *)keyPathsForValuesAffectingShowsHeading { + return [NSSet setWithObject:@"showsUserHeadingIndicator"]; } -- (BOOL)showsHeading -{ - return self.showsUserHeadingIndicator; +- (BOOL)showsHeading { + return self.showsUserHeadingIndicator; } -- (void)setShowsHeading:(BOOL)showsHeading -{ - MLNLogDebug(@"Setting showsHeading: %@", MLNStringFromBOOL(showsHeading)); - self.showsUserHeadingIndicator = showsHeading; +- (void)setShowsHeading:(BOOL)showsHeading { + MLNLogDebug(@"Setting showsHeading: %@", MLNStringFromBOOL(showsHeading)); + self.showsUserHeadingIndicator = showsHeading; } - - @end diff --git a/platform/ios/src/MLNScaleBar.mm b/platform/ios/src/MLNScaleBar.mm index 1ce40c50c..27e210d01 100644 --- a/platform/ios/src/MLNScaleBar.mm +++ b/platform/ios/src/MLNScaleBar.mm @@ -1,44 +1,31 @@ -#import "Mapbox.h" #import "MLNScaleBar.h" +#import "Mapbox.h" static const CGFloat MLNFeetPerMile = 5280; struct MLNRow { - CLLocationDistance distance; - NSUInteger numberOfBars; + CLLocationDistance distance; + NSUInteger numberOfBars; }; static const MLNRow MLNMetricTable[] = { - {.distance = 1, .numberOfBars = 2}, - {.distance = 2, .numberOfBars = 2}, - {.distance = 4, .numberOfBars = 2}, - {.distance = 10, .numberOfBars = 2}, - {.distance = 20, .numberOfBars = 2}, - {.distance = 50, .numberOfBars = 2}, - {.distance = 75, .numberOfBars = 3}, - {.distance = 100, .numberOfBars = 2}, - {.distance = 150, .numberOfBars = 2}, - {.distance = 200, .numberOfBars = 2}, - {.distance = 300, .numberOfBars = 3}, - {.distance = 500, .numberOfBars = 2}, - {.distance = 1000, .numberOfBars = 2}, - {.distance = 1500, .numberOfBars = 2}, - {.distance = 3000, .numberOfBars = 3}, - {.distance = 5000, .numberOfBars = 2}, - {.distance = 10000, .numberOfBars = 2}, - {.distance = 20000, .numberOfBars = 2}, - {.distance = 30000, .numberOfBars = 3}, - {.distance = 50000, .numberOfBars = 2}, - {.distance = 100000, .numberOfBars = 2}, - {.distance = 200000, .numberOfBars = 2}, - {.distance = 300000, .numberOfBars = 3}, - {.distance = 400000, .numberOfBars = 2}, - {.distance = 500000, .numberOfBars = 2}, - {.distance = 600000, .numberOfBars = 3}, + {.distance = 1, .numberOfBars = 2}, {.distance = 2, .numberOfBars = 2}, + {.distance = 4, .numberOfBars = 2}, {.distance = 10, .numberOfBars = 2}, + {.distance = 20, .numberOfBars = 2}, {.distance = 50, .numberOfBars = 2}, + {.distance = 75, .numberOfBars = 3}, {.distance = 100, .numberOfBars = 2}, + {.distance = 150, .numberOfBars = 2}, {.distance = 200, .numberOfBars = 2}, + {.distance = 300, .numberOfBars = 3}, {.distance = 500, .numberOfBars = 2}, + {.distance = 1000, .numberOfBars = 2}, {.distance = 1500, .numberOfBars = 2}, + {.distance = 3000, .numberOfBars = 3}, {.distance = 5000, .numberOfBars = 2}, + {.distance = 10000, .numberOfBars = 2}, {.distance = 20000, .numberOfBars = 2}, + {.distance = 30000, .numberOfBars = 3}, {.distance = 50000, .numberOfBars = 2}, + {.distance = 100000, .numberOfBars = 2}, {.distance = 200000, .numberOfBars = 2}, + {.distance = 300000, .numberOfBars = 3}, {.distance = 400000, .numberOfBars = 2}, + {.distance = 500000, .numberOfBars = 2}, {.distance = 600000, .numberOfBars = 3}, {.distance = 800000, .numberOfBars = 2}, }; -static const MLNRow MLNImperialTable[] ={ +static const MLNRow MLNImperialTable[] = { {.distance = 4, .numberOfBars = 2}, {.distance = 6, .numberOfBars = 2}, {.distance = 10, .numberOfBars = 2}, @@ -53,36 +40,36 @@ static const MLNRow MLNImperialTable[] ={ {.distance = 600, .numberOfBars = 3}, {.distance = 800, .numberOfBars = 2}, {.distance = 1000, .numberOfBars = 2}, - {.distance = 0.25f*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 0.5f*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 1*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 2*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 3*MLNFeetPerMile, .numberOfBars = 3}, - {.distance = 4*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 8*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 12*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 15*MLNFeetPerMile, .numberOfBars = 3}, - {.distance = 20*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 30*MLNFeetPerMile, .numberOfBars = 3}, - {.distance = 40*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 80*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 120*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 200*MLNFeetPerMile, .numberOfBars = 2}, - {.distance = 300*MLNFeetPerMile, .numberOfBars = 3}, - {.distance = 400*MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 0.25f * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 0.5f * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 1 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 2 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 3 * MLNFeetPerMile, .numberOfBars = 3}, + {.distance = 4 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 8 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 12 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 15 * MLNFeetPerMile, .numberOfBars = 3}, + {.distance = 20 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 30 * MLNFeetPerMile, .numberOfBars = 3}, + {.distance = 40 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 80 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 120 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 200 * MLNFeetPerMile, .numberOfBars = 2}, + {.distance = 300 * MLNFeetPerMile, .numberOfBars = 3}, + {.distance = 400 * MLNFeetPerMile, .numberOfBars = 2}, }; @class MLNScaleBarLabel; -@interface MLNScaleBar() +@interface MLNScaleBar () @property (nonatomic) NSArray *labelViews; @property (nonatomic) NSArray *bars; @property (nonatomic) UIView *containerView; @property (nonatomic) MLNDistanceFormatter *formatter; @property (nonatomic, assign) MLNRow row; @property (nonatomic, assign) CGFloat borderWidth; -@property (nonatomic) NSMutableDictionary* labelImageCache; -@property (nonatomic) MLNScaleBarLabel* prototypeLabel; +@property (nonatomic) NSMutableDictionary *labelImageCache; +@property (nonatomic) MLNScaleBarLabel *prototypeLabel; @property (nonatomic) CGFloat lastLabelWidth; @property (nonatomic) CGSize size; @property (nonatomic) BOOL recalculateSize; @@ -93,7 +80,7 @@ static const MLNRow MLNImperialTable[] ={ static const CGFloat MLNBarHeight = 4; static const CGFloat MLNFeetPerMeter = 3.28084; static const CGFloat MLNScaleBarLabelWidthHint = 30.0; -static const CGFloat MLNScaleBarMinimumBarWidth = 30.0; // Arbitrary +static const CGFloat MLNScaleBarMinimumBarWidth = 30.0; // Arbitrary @interface MLNScaleBarLabel : UILabel @property (nonatomic) BOOL shouldShowDarkStyles; @@ -102,41 +89,39 @@ static const CGFloat MLNScaleBarMinimumBarWidth = 30.0; // Arbitrary @implementation MLNScaleBarLabel - (void)setShouldShowDarkStyles:(BOOL)shouldShowDarkStyles { + if (_shouldShowDarkStyles != shouldShowDarkStyles) { + _shouldShowDarkStyles = shouldShowDarkStyles; - if (_shouldShowDarkStyles != shouldShowDarkStyles) { - _shouldShowDarkStyles = shouldShowDarkStyles; - - // Redraw labels - [self setNeedsDisplay]; - - } + // Redraw labels + [self setNeedsDisplay]; + } } - (void)drawTextInRect:(CGRect)rect { - CGSize shadowOffset = self.shadowOffset; + CGSize shadowOffset = self.shadowOffset; - CGContextRef context = UIGraphicsGetCurrentContext(); - CGContextSetLineWidth(context, 2); - CGContextSetLineJoin(context, kCGLineJoinRound); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetLineWidth(context, 2); + CGContextSetLineJoin(context, kCGLineJoinRound); - CGContextSetTextDrawingMode(context, kCGTextStroke); - if (_shouldShowDarkStyles) { - self.textColor = [UIColor blackColor]; - } else { - self.textColor = [UIColor whiteColor]; - } - [super drawTextInRect:rect]; + CGContextSetTextDrawingMode(context, kCGTextStroke); + if (_shouldShowDarkStyles) { + self.textColor = [UIColor blackColor]; + } else { + self.textColor = [UIColor whiteColor]; + } + [super drawTextInRect:rect]; - CGContextSetTextDrawingMode(context, kCGTextFill); - if (self.shouldShowDarkStyles) { - self.textColor = [UIColor whiteColor]; - } else { - self.textColor = [UIColor blackColor]; - } - self.shadowOffset = CGSizeMake(0, 0); - [super drawTextInRect:rect]; + CGContextSetTextDrawingMode(context, kCGTextFill); + if (self.shouldShowDarkStyles) { + self.textColor = [UIColor whiteColor]; + } else { + self.textColor = [UIColor blackColor]; + } + self.shadowOffset = CGSizeMake(0, 0); + [super drawTextInRect:rect]; - self.shadowOffset = shadowOffset; + self.shadowOffset = shadowOffset; } @end @@ -144,237 +129,237 @@ static const CGFloat MLNScaleBarMinimumBarWidth = 30.0; // Arbitrary @implementation MLNScaleBar - (instancetype)initWithCoder:(NSCoder *)decoder { - if (self = [super initWithCoder:decoder]) { - [self commonInit]; - } - return self; + if (self = [super initWithCoder:decoder]) { + [self commonInit]; + } + return self; } - (instancetype)initWithFrame:(CGRect)frame { - if (self = [super initWithFrame:frame]) { - [self commonInit]; - } - return self; + if (self = [super initWithFrame:frame]) { + [self commonInit]; + } + return self; } - (void)commonInit { - _size = CGSizeZero; + _size = CGSizeZero; - _primaryColor = [UIColor colorWithRed:18.0/255.0 green:45.0/255.0 blue:17.0/255.0 alpha:1]; - _secondaryColor = [UIColor colorWithRed:247.0/255.0 green:247.0/255.0 blue:247.0/255.0 alpha:1]; - _borderWidth = 1.0f; + _primaryColor = [UIColor colorWithRed:18.0 / 255.0 green:45.0 / 255.0 blue:17.0 / 255.0 alpha:1]; + _secondaryColor = [UIColor colorWithRed:247.0 / 255.0 + green:247.0 / 255.0 + blue:247.0 / 255.0 + alpha:1]; + _borderWidth = 1.0f; - self.clipsToBounds = NO; - self.hidden = YES; + self.clipsToBounds = NO; + self.hidden = YES; - // Default to current local - NSLocale *locale = [NSLocale currentLocale]; - _usesMetricSystem = [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue]; + // Default to current local + NSLocale *locale = [NSLocale currentLocale]; + _usesMetricSystem = [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue]; - _containerView = [[UIView alloc] init]; - _containerView.clipsToBounds = YES; - _containerView.backgroundColor = _secondaryColor; - _containerView.layer.borderColor = _primaryColor.CGColor; - _containerView.layer.borderWidth = _borderWidth / [[UIScreen mainScreen] scale]; + _containerView = [[UIView alloc] init]; + _containerView.clipsToBounds = YES; + _containerView.backgroundColor = _secondaryColor; + _containerView.layer.borderColor = _primaryColor.CGColor; + _containerView.layer.borderWidth = _borderWidth / [[UIScreen mainScreen] scale]; - _containerView.layer.cornerRadius = MLNBarHeight / 2.0; - _containerView.layer.masksToBounds = YES; + _containerView.layer.cornerRadius = MLNBarHeight / 2.0; + _containerView.layer.masksToBounds = YES; - [self addSubview:_containerView]; + [self addSubview:_containerView]; - _formatter = [[MLNDistanceFormatter alloc] init]; + _formatter = [[MLNDistanceFormatter alloc] init]; - // Image labels are now images - _labelImageCache = [[NSMutableDictionary alloc] init]; - _prototypeLabel = [[MLNScaleBarLabel alloc] init]; - _prototypeLabel.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium]; - _prototypeLabel.clipsToBounds = NO; - _prototypeLabel.shouldShowDarkStyles = _shouldShowDarkStyles; + // Image labels are now images + _labelImageCache = [[NSMutableDictionary alloc] init]; + _prototypeLabel = [[MLNScaleBarLabel alloc] init]; + _prototypeLabel.font = [UIFont systemFontOfSize:8 weight:UIFontWeightMedium]; + _prototypeLabel.clipsToBounds = NO; + _prototypeLabel.shouldShowDarkStyles = _shouldShowDarkStyles; - NSUInteger numberOfLabels = 4; - NSMutableArray *labelViews = [NSMutableArray arrayWithCapacity:numberOfLabels]; + NSUInteger numberOfLabels = 4; + NSMutableArray *labelViews = [NSMutableArray arrayWithCapacity:numberOfLabels]; - for (NSUInteger i = 0; i < numberOfLabels; i++) { - UIView *view = [[UIView alloc] init]; - view.bounds = CGRectZero; - view.clipsToBounds = NO; - view.contentMode = UIViewContentModeCenter; - view.hidden = YES; - [labelViews addObject:view]; - [self addSubview:view]; - } - _labelViews = [labelViews copy]; - _lastLabelWidth = MLNScaleBarLabelWidthHint; + for (NSUInteger i = 0; i < numberOfLabels; i++) { + UIView *view = [[UIView alloc] init]; + view.bounds = CGRectZero; + view.clipsToBounds = NO; + view.contentMode = UIViewContentModeCenter; + view.hidden = YES; + [labelViews addObject:view]; + [self addSubview:view]; + } + _labelViews = [labelViews copy]; + _lastLabelWidth = MLNScaleBarLabelWidthHint; - // Zero is a special case (no formatting) - [self addZeroLabel]; + // Zero is a special case (no formatting) + [self addZeroLabel]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetLabelImageCache) name:NSCurrentLocaleDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(resetLabelImageCache) + name:NSCurrentLocaleDidChangeNotification + object:nil]; } - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)resetLabelImageCache { - self.labelImageCache = [[NSMutableDictionary alloc] init]; - [self addZeroLabel]; + self.labelImageCache = [[NSMutableDictionary alloc] init]; + [self addZeroLabel]; } // MARK: - Dimensions - (void)setBorderWidth:(CGFloat)borderWidth { - _borderWidth = borderWidth; - _containerView.layer.borderWidth = borderWidth / [[UIScreen mainScreen] scale]; + _borderWidth = borderWidth; + _containerView.layer.borderWidth = borderWidth / [[UIScreen mainScreen] scale]; } // Determines the width of the bars NOT the size of the entire scale bar, // which includes space for (half) a label. // Uses the current set `row` - (CGFloat)actualWidth { - CGFloat unitsPerPoint = [self unitsPerPoint]; + CGFloat unitsPerPoint = [self unitsPerPoint]; - if (unitsPerPoint == 0.0) { - return 0.0; - } + if (unitsPerPoint == 0.0) { + return 0.0; + } - CGFloat width = self.row.distance / unitsPerPoint; + CGFloat width = self.row.distance / unitsPerPoint; - if (width <= MLNScaleBarMinimumBarWidth) { - return 0.0; - } + if (width <= MLNScaleBarMinimumBarWidth) { + return 0.0; + } - // Round, so that each bar section has an integer width - return self.row.numberOfBars * floor(width/self.row.numberOfBars); + // Round, so that each bar section has an integer width + return self.row.numberOfBars * floor(width / self.row.numberOfBars); } - (CGFloat)maximumWidth { - // TODO: Consider taking Scale Bar margins into account here. - CGFloat fullWidth = CGRectGetWidth(self.superview.bounds); - return floorf(fullWidth / 2); + // TODO: Consider taking Scale Bar margins into account here. + CGFloat fullWidth = CGRectGetWidth(self.superview.bounds); + return floorf(fullWidth / 2); } - (CGFloat)unitsPerPoint { - return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MLNFeetPerMeter; + return [self usesMetricSystem] ? self.metersPerPoint : self.metersPerPoint * MLNFeetPerMeter; } // MARK: - Convenience methods - (BOOL)usesRightToLeftLayout { - if (self.testingRightToLeftOverride) { - return [self.testingRightToLeftOverride boolValue]; - } + if (self.testingRightToLeftOverride) { + return [self.testingRightToLeftOverride boolValue]; + } - return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + return + [UIView + userInterfaceLayoutDirectionForSemanticContentAttribute:self.superview + .semanticContentAttribute] == + UIUserInterfaceLayoutDirectionRightToLeft; } - (MLNRow)preferredRow { - CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; + CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; - BOOL useMetric = [self usesMetricSystem]; + BOOL useMetric = [self usesMetricSystem]; - const MLNRow *row; - const MLNRow *table; - NSUInteger count; + const MLNRow *row; + const MLNRow *table; + NSUInteger count; - if (useMetric) { - row = table = MLNMetricTable; - count = sizeof(MLNMetricTable) / sizeof(MLNMetricTable[0]); - } - else { - row = table = MLNImperialTable; - count = sizeof(MLNImperialTable) / sizeof(MLNImperialTable[0]); + if (useMetric) { + row = table = MLNMetricTable; + count = sizeof(MLNMetricTable) / sizeof(MLNMetricTable[0]); + } else { + row = table = MLNImperialTable; + count = sizeof(MLNImperialTable) / sizeof(MLNImperialTable[0]); + } + + while (row < table + count) { + if (row->distance > maximumDistance) { + // use the previous row + NSAssert(row != table, @""); + return *(row - 1); } + ++row; + } - while (row < table + count) { - if (row->distance > maximumDistance) { - // use the previous row - NSAssert(row != table, @""); - return *(row - 1); - } - ++row; - } - - // Didn't find it, just return the first. - return *table; + // Didn't find it, just return the first. + return *table; } - // MARK: - Dark Mode Changes - (void)setShouldShowDarkStyles:(BOOL)shouldShowDarkStyles { + if (_shouldShowDarkStyles != shouldShowDarkStyles) { + _shouldShowDarkStyles = shouldShowDarkStyles; - if (_shouldShowDarkStyles != shouldShowDarkStyles) { - _shouldShowDarkStyles = shouldShowDarkStyles; - - // Redraw labels - _prototypeLabel.shouldShowDarkStyles = shouldShowDarkStyles; - [self resetLabelImageCache]; - [self updateLabels]; - - } + // Redraw labels + _prototypeLabel.shouldShowDarkStyles = shouldShowDarkStyles; + [self resetLabelImageCache]; + [self updateLabels]; + } } - // MARK: - Setters - (void)setUsesMetricSystem:(BOOL)usesMetricSystem { + if (_usesMetricSystem != usesMetricSystem) { + _usesMetricSystem = usesMetricSystem; - if (_usesMetricSystem != usesMetricSystem) { + // Find the current locale for the system, then check if we need to override this. E.g. Locale + // is in the United States but we want to see metric. + NSLocale *locale = [NSLocale currentLocale]; + BOOL currentLocaleUsesMetric = [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue]; + if (currentLocaleUsesMetric && !usesMetricSystem) { + // Our current locale is metric, but for some reason we don't want to show metric here, so we + // need to force a locale that isn't metric. + self.formatter.numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; - _usesMetricSystem = usesMetricSystem; - - // Find the current locale for the system, then check if we need to override this. E.g. Locale is in the United States but we want to see metric. - NSLocale *locale = [NSLocale currentLocale]; - BOOL currentLocaleUsesMetric = [[locale objectForKey:NSLocaleUsesMetricSystem] boolValue]; - if (currentLocaleUsesMetric && !usesMetricSystem) { - - // Our current locale is metric, but for some reason we don't want to show metric here, so we need to force a locale that isn't metric. - self.formatter.numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"]; - - } else if (!currentLocaleUsesMetric && usesMetricSystem) { - - // Our current locale is not metric, but we want to use the metric system for the scale bar. - // Use Canada as the identifier as they use a period as a decimal seperator - self.formatter.numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_CA"]; - - } else if ((currentLocaleUsesMetric && usesMetricSystem) || (!currentLocaleUsesMetric && !usesMetricSystem)) { - - // Fallback to the system locale. - self.formatter.numberFormatter.locale = locale; - - } - - [self resetLabelImageCache]; - [self updateVisibility]; - - self.recalculateSize = YES; - [self invalidateIntrinsicContentSize]; + } else if (!currentLocaleUsesMetric && usesMetricSystem) { + // Our current locale is not metric, but we want to use the metric system for the scale bar. + // Use Canada as the identifier as they use a period as a decimal seperator + self.formatter.numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_CA"]; + } else if ((currentLocaleUsesMetric && usesMetricSystem) || + (!currentLocaleUsesMetric && !usesMetricSystem)) { + // Fallback to the system locale. + self.formatter.numberFormatter.locale = locale; } -} - -- (void)setMetersPerPoint:(CLLocationDistance)metersPerPoint { - if (_metersPerPoint == metersPerPoint) { - return; - } - - _metersPerPoint = metersPerPoint; - + [self resetLabelImageCache]; [self updateVisibility]; self.recalculateSize = YES; [self invalidateIntrinsicContentSize]; + } +} + +- (void)setMetersPerPoint:(CLLocationDistance)metersPerPoint { + if (_metersPerPoint == metersPerPoint) { + return; + } + + _metersPerPoint = metersPerPoint; + + [self updateVisibility]; + + self.recalculateSize = YES; + [self invalidateIntrinsicContentSize]; } - (CGSize)intrinsicContentSize { - // Size is calculated elsewhere - since intrinsicContentSize is part of the - // constraint system, this should be done in updateConstraints - if (self.size.width < 0.0) { - return CGSizeZero; - } - return self.size; + // Size is calculated elsewhere - since intrinsicContentSize is part of the + // constraint system, this should be done in updateConstraints + if (self.size.width < 0.0) { + return CGSizeZero; + } + return self.size; } /// updateConstraints @@ -386,242 +371,244 @@ static const CGFloat MLNScaleBarMinimumBarWidth = 30.0; // Arbitrary /// label - (void)updateConstraints { - if (self.isHidden || !self.recalculateSize) { - [super updateConstraints]; - return; - } + if (self.isHidden || !self.recalculateSize) { + [super updateConstraints]; + return; + } - // TODO: Improve this (and the side-effects) - self.row = [self preferredRow]; + // TODO: Improve this (and the side-effects) + self.row = [self preferredRow]; - NSAssert(self.row.numberOfBars > 0, @""); + NSAssert(self.row.numberOfBars > 0, @""); - CGFloat totalBarWidth = self.actualWidth; + CGFloat totalBarWidth = self.actualWidth; - if (totalBarWidth <= 0.0) { - [super updateConstraints]; - return; - } + if (totalBarWidth <= 0.0) { + [super updateConstraints]; + return; + } - // Determine the "lastLabelWidth". This has changed to take a maximum of each - // label, to ensure that the size does not change in LTR & RTL layouts, and - // also to stop jiggling when the scale bar is on the right hand of the screen - // This will most likely be a constant, as we take a max using a "hint" for - // the initial value + // Determine the "lastLabelWidth". This has changed to take a maximum of each + // label, to ensure that the size does not change in LTR & RTL layouts, and + // also to stop jiggling when the scale bar is on the right hand of the screen + // This will most likely be a constant, as we take a max using a "hint" for + // the initial value - if (self.shouldLayoutBars) { - [self updateLabels]; - } + if (self.shouldLayoutBars) { + [self updateLabels]; + } - CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2); + CGFloat halfLabelWidth = ceil(self.lastLabelWidth / 2); - self.size = CGSizeMake(totalBarWidth + halfLabelWidth, 16); + self.size = CGSizeMake(totalBarWidth + halfLabelWidth, 16); - [self setNeedsLayout]; - [super updateConstraints]; // This calls intrinsicContentSize + [self setNeedsLayout]; + [super updateConstraints]; // This calls intrinsicContentSize } - (void)updateVisibility { - BOOL metric = [self usesMetricSystem]; + BOOL metric = [self usesMetricSystem]; - NSUInteger count = metric - ? sizeof(MLNMetricTable) / sizeof(MLNMetricTable[0]) - : sizeof(MLNImperialTable) / sizeof(MLNImperialTable[0]); + NSUInteger count = metric ? sizeof(MLNMetricTable) / sizeof(MLNMetricTable[0]) + : sizeof(MLNImperialTable) / sizeof(MLNImperialTable[0]); - CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; - CLLocationDistance allowedDistance = metric - ? MLNMetricTable[count-1].distance - : MLNImperialTable[count-1].distance; + CLLocationDistance maximumDistance = [self maximumWidth] * [self unitsPerPoint]; + CLLocationDistance allowedDistance = + metric ? MLNMetricTable[count - 1].distance : MLNImperialTable[count - 1].distance; - CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f; + CGFloat alpha = maximumDistance > allowedDistance ? .0f : 1.0f; - if (self.alpha != alpha) { - [UIView animateWithDuration:.2f delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ - self.alpha = alpha; - } completion:nil]; - } + if (self.alpha != alpha) { + [UIView animateWithDuration:.2f + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + self.alpha = alpha; + } + completion:nil]; + } } - (void)setRow:(MLNRow)row { - if (_row.distance == row.distance) { - return; - } + if (_row.distance == row.distance) { + return; + } - self.shouldLayoutBars = YES; + self.shouldLayoutBars = YES; - _row = row; + _row = row; } // MARK: - Views - (NSArray *)bars { - if (!_bars) { - NSMutableArray *bars = [NSMutableArray array]; - for (NSUInteger i = 0; i < self.row.numberOfBars; i++) { - UIView *bar = [[UIView alloc] init]; - [bars addObject:bar]; - [self.containerView addSubview:bar]; - } - _bars = bars; + if (!_bars) { + NSMutableArray *bars = [NSMutableArray array]; + for (NSUInteger i = 0; i < self.row.numberOfBars; i++) { + UIView *bar = [[UIView alloc] init]; + [bars addObject:bar]; + [self.containerView addSubview:bar]; } - return _bars; + _bars = bars; + } + return _bars; } // MARK: - Labels - (void)addZeroLabel { - NSDecimalNumber *zeroNumber = [NSDecimalNumber decimalNumberWithString:@"0"]; - NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; - NSString *text = [formatter stringFromNumber:zeroNumber]; + NSDecimalNumber *zeroNumber = [NSDecimalNumber decimalNumberWithString:@"0"]; + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + NSString *text = [formatter stringFromNumber:zeroNumber]; - UIImage* image = [self imageForLabelText:text]; - [self.labelImageCache setObject:image forKey:@(0)]; + UIImage *image = [self imageForLabelText:text]; + [self.labelImageCache setObject:image forKey:@(0)]; } -- (UIImage*)imageForLabelText:(NSString*)text { - self.prototypeLabel.text = text; - [self.prototypeLabel setNeedsDisplay]; - [self.prototypeLabel sizeToFit]; +- (UIImage *)imageForLabelText:(NSString *)text { + self.prototypeLabel.text = text; + [self.prototypeLabel setNeedsDisplay]; + [self.prototypeLabel sizeToFit]; - // Now render - UIGraphicsBeginImageContextWithOptions(self.prototypeLabel.bounds.size, NO, 0.0); - [self.prototypeLabel.layer renderInContext: UIGraphicsGetCurrentContext()]; - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return image; + // Now render + UIGraphicsBeginImageContextWithOptions(self.prototypeLabel.bounds.size, NO, 0.0); + [self.prototypeLabel.layer renderInContext:UIGraphicsGetCurrentContext()]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; } -- (UIImage*)cachedLabelImageForDistance:(CLLocationDistance)barDistance { - // Make a slightly nicer key, rather than something that's a double. - NSUInteger floorDist = (NSUInteger)(barDistance*100); +- (UIImage *)cachedLabelImageForDistance:(CLLocationDistance)barDistance { + // Make a slightly nicer key, rather than something that's a double. + NSUInteger floorDist = (NSUInteger)(barDistance * 100); - NSNumber *key = @(floorDist); - UIImage *cachedImage = [self.labelImageCache objectForKey:key]; + NSNumber *key = @(floorDist); + UIImage *cachedImage = [self.labelImageCache objectForKey:key]; - if (cachedImage) { - return cachedImage; - } + if (cachedImage) { + return cachedImage; + } - // Calc it - NSString *text = [self.formatter stringFromDistance:barDistance]; - UIImage *image = [self imageForLabelText:text]; + // Calc it + NSString *text = [self.formatter stringFromDistance:barDistance]; + UIImage *image = [self imageForLabelText:text]; - [self.labelImageCache setObject:image forKey:key]; + [self.labelImageCache setObject:image forKey:key]; - return image; + return image; } - (void)updateLabels { - NSEnumerator *viewEnumerator = [self.labelViews objectEnumerator]; - NSUInteger i = 0; - CLLocationDistance multiplier = (self.row.distance / self.row.numberOfBars); + NSEnumerator *viewEnumerator = [self.labelViews objectEnumerator]; + NSUInteger i = 0; + CLLocationDistance multiplier = (self.row.distance / self.row.numberOfBars); - if (![self usesMetricSystem]) { - multiplier /= MLNFeetPerMeter; - } + if (![self usesMetricSystem]) { + multiplier /= MLNFeetPerMeter; + } - for (; i <= self.row.numberOfBars; i++) { - UIView *labelView = [viewEnumerator nextObject]; - labelView.hidden = NO; + for (; i <= self.row.numberOfBars; i++) { + UIView *labelView = [viewEnumerator nextObject]; + labelView.hidden = NO; - CLLocationDistance barDistance = multiplier * i; - UIImage *image = [self cachedLabelImageForDistance:barDistance]; + CLLocationDistance barDistance = multiplier * i; + UIImage *image = [self cachedLabelImageForDistance:barDistance]; - self.lastLabelWidth = MAX(self.lastLabelWidth, image.size.width); + self.lastLabelWidth = MAX(self.lastLabelWidth, image.size.width); - labelView.layer.contents = (id)image.CGImage; - labelView.layer.contentsScale = image.scale; - } + labelView.layer.contents = (id)image.CGImage; + labelView.layer.contentsScale = image.scale; + } - // Hide the rest. - for (; i < self.labelViews.count; i++) { - UIView *labelView = [viewEnumerator nextObject]; - labelView.hidden = YES; - } + // Hide the rest. + for (; i < self.labelViews.count; i++) { + UIView *labelView = [viewEnumerator nextObject]; + labelView.hidden = YES; + } } // MARK: - Layout - (void)layoutSubviews { - [super layoutSubviews]; + [super layoutSubviews]; - if (!self.recalculateSize) { - return; - } + if (!self.recalculateSize) { + return; + } - self.recalculateSize = NO; + self.recalculateSize = NO; - // If size is 0, then we keep the existing layout (which will fade out) - if (self.size.width <= 0.0) { - return; - } + // If size is 0, then we keep the existing layout (which will fade out) + if (self.size.width <= 0.0) { + return; + } - CGFloat totalBarWidth = self.actualWidth; + CGFloat totalBarWidth = self.actualWidth; - if (totalBarWidth <= 0.0) { - return; - } + if (totalBarWidth <= 0.0) { + return; + } - if (self.shouldLayoutBars) { - self.shouldLayoutBars = NO; - [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)]; - _bars = nil; - } + if (self.shouldLayoutBars) { + self.shouldLayoutBars = NO; + [_bars makeObjectsPerformSelector:@selector(removeFromSuperview)]; + _bars = nil; + } - // Re-layout the component bars and labels of the scale bar - CGFloat intrinsicContentHeight = self.intrinsicContentSize.height; - CGFloat barWidth = totalBarWidth/self.bars.count; + // Re-layout the component bars and labels of the scale bar + CGFloat intrinsicContentHeight = self.intrinsicContentSize.height; + CGFloat barWidth = totalBarWidth / self.bars.count; - BOOL RTL = [self usesRightToLeftLayout]; - CGFloat halfLabelWidth = ceil(self.lastLabelWidth/2); - CGFloat barOffset = RTL ? halfLabelWidth : 0.0; + BOOL RTL = [self usesRightToLeftLayout]; + CGFloat halfLabelWidth = ceil(self.lastLabelWidth / 2); + CGFloat barOffset = RTL ? halfLabelWidth : 0.0; - self.containerView.frame = CGRectMake(barOffset, - intrinsicContentHeight - MLNBarHeight, - totalBarWidth, - MLNBarHeight); + self.containerView.frame = + CGRectMake(barOffset, intrinsicContentHeight - MLNBarHeight, totalBarWidth, MLNBarHeight); - [self layoutBarsWithWidth:barWidth]; + [self layoutBarsWithWidth:barWidth]; - CGFloat yPosition = round(0.5 * ( intrinsicContentHeight - MLNBarHeight)); - CGFloat barDelta = RTL ? -barWidth : barWidth; - [self layoutLabelsWithOffset:barOffset delta:barDelta yPosition:yPosition]; + CGFloat yPosition = round(0.5 * (intrinsicContentHeight - MLNBarHeight)); + CGFloat barDelta = RTL ? -barWidth : barWidth; + [self layoutLabelsWithOffset:barOffset delta:barDelta yPosition:yPosition]; } - (void)layoutBarsWithWidth:(CGFloat)barWidth { - NSUInteger i = 0; - for (UIView *bar in self.bars) { - CGFloat xPosition = barWidth * i; - bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor; - bar.frame = CGRectMake(xPosition, 0, barWidth, MLNBarHeight); - i++; - } + NSUInteger i = 0; + for (UIView *bar in self.bars) { + CGFloat xPosition = barWidth * i; + bar.backgroundColor = (i % 2 == 0) ? self.primaryColor : self.secondaryColor; + bar.frame = CGRectMake(xPosition, 0, barWidth, MLNBarHeight); + i++; + } } -- (void)layoutLabelsWithOffset:(CGFloat)barOffset delta:(CGFloat)barDelta yPosition:(CGFloat)yPosition { +- (void)layoutLabelsWithOffset:(CGFloat)barOffset + delta:(CGFloat)barDelta + yPosition:(CGFloat)yPosition { #if !defined(NS_BLOCK_ASSERTIONS) - NSUInteger countOfVisibleLabels = 0; - for (UIView *view in self.labelViews) { - if (!view.isHidden) { - countOfVisibleLabels++; - } + NSUInteger countOfVisibleLabels = 0; + for (UIView *view in self.labelViews) { + if (!view.isHidden) { + countOfVisibleLabels++; } - NSAssert(self.bars.count == countOfVisibleLabels - 1, @""); + } + NSAssert(self.bars.count == countOfVisibleLabels - 1, @""); #endif - CGFloat xPosition = barOffset; + CGFloat xPosition = barOffset; - if (barDelta < 0) { - xPosition -= (barDelta*self.bars.count); - } + if (barDelta < 0) { + xPosition -= (barDelta * self.bars.count); + } - for (UIView *label in self.labelViews) { - // Label frames have 0 size - though the layer contents use "center" and do - // not clip to bounds. This way we don't need to worry about positioning the - // label. (Though you won't see the label in the view debugger) - label.frame = CGRectMake(xPosition, yPosition, 0.0, 0.0); + for (UIView *label in self.labelViews) { + // Label frames have 0 size - though the layer contents use "center" and do + // not clip to bounds. This way we don't need to worry about positioning the + // label. (Though you won't see the label in the view debugger) + label.frame = CGRectMake(xPosition, yPosition, 0.0, 0.0); - xPosition += barDelta; - } + xPosition += barDelta; + } } @end diff --git a/platform/ios/src/UIColor+MLNAdditions.mm b/platform/ios/src/UIColor+MLNAdditions.mm index 7bfec79b2..6c24f05f7 100644 --- a/platform/ios/src/UIColor+MLNAdditions.mm +++ b/platform/ios/src/UIColor+MLNAdditions.mm @@ -2,44 +2,42 @@ @implementation UIColor (MLNAdditions) -- (mbgl::Color)mgl_color -{ - CGFloat r, g, b, a; - [self getRed:&r green:&g blue:&b alpha:&a]; - // UIColor provides non-premultiplied color components, so we have to premultiply each - // color component with the alpha value to transform it into a valid - // mbgl::Color which expects premultiplied color components. - return { static_cast(r*a), static_cast(g*a), static_cast(b*a), static_cast(a) }; +- (mbgl::Color)mgl_color { + CGFloat r, g, b, a; + [self getRed:&r green:&g blue:&b alpha:&a]; + // UIColor provides non-premultiplied color components, so we have to premultiply each + // color component with the alpha value to transform it into a valid + // mbgl::Color which expects premultiplied color components. + return {static_cast(r * a), static_cast(g * a), static_cast(b * a), + static_cast(a)}; } // Intended to be used when providing colors on the basis of an NSExpression -- (mbgl::Color)mgl_colorForPremultipliedValue -{ - CGFloat r, g, b, a; - [self getRed:&r green:&g blue:&b alpha:&a]; - return { static_cast(r), static_cast(g), static_cast(b), static_cast(a) }; +- (mbgl::Color)mgl_colorForPremultipliedValue { + CGFloat r, g, b, a; + [self getRed:&r green:&g blue:&b alpha:&a]; + return {static_cast(r), static_cast(g), static_cast(b), + static_cast(a)}; } -- (mbgl::style::PropertyValue)mgl_colorPropertyValue -{ - mbgl::Color color = self.mgl_color; - return {{ color.r, color.g, color.b, color.a }}; +- (mbgl::style::PropertyValue)mgl_colorPropertyValue { + mbgl::Color color = self.mgl_color; + return {{color.r, color.g, color.b, color.a}}; } -+ (UIColor *)mgl_colorWithColor:(mbgl::Color)color -{ - // If there is no alpha value, return original color values. - if (color.a == 0.0f) { - return [UIColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; - } ++ (UIColor *)mgl_colorWithColor:(mbgl::Color)color { + // If there is no alpha value, return original color values. + if (color.a == 0.0f) { + return [UIColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; + } - // mbgl::Color provides premultiplied color components, so we have to convert color - // components to non-premultiplied values to return a valid UIColor object. - float red = static_cast((color.r / color.a)); - float green = static_cast((color.g / color.a)); - float blue = static_cast((color.b / color.a)); + // mbgl::Color provides premultiplied color components, so we have to convert color + // components to non-premultiplied values to return a valid UIColor object. + float red = static_cast((color.r / color.a)); + float green = static_cast((color.g / color.a)); + float blue = static_cast((color.b / color.a)); - return [UIColor colorWithRed:red green:green blue:blue alpha:color.a]; + return [UIColor colorWithRed:red green:green blue:blue alpha:color.a]; } @end @@ -47,48 +45,49 @@ @implementation NSExpression (MLNColorAdditions) + (NSExpression *)mgl_expressionForRGBComponents:(NSArray *)components { - if (UIColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } + if (UIColor *color = [self mgl_colorWithRGBComponents:components]) { + return [NSExpression expressionForConstantValue:color]; + } - NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]]; - NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:[components arrayByAddingObject:alpha]]; + NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]]; + NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0]; + return [NSExpression expressionForFunction:color + selectorName:@"colorWithRed:green:blue:alpha:" + arguments:[components arrayByAddingObject:alpha]]; } + (NSExpression *)mgl_expressionForRGBAComponents:(NSArray *)components { - if (UIColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } + if (UIColor *color = [self mgl_colorWithRGBComponents:components]) { + return [NSExpression expressionForConstantValue:color]; + } - NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:components]; + NSExpression *color = [NSExpression expressionForConstantValue:[UIColor class]]; + return [NSExpression expressionForFunction:color + selectorName:@"colorWithRed:green:blue:alpha:" + arguments:components]; } + (UIColor *)mgl_colorWithRGBComponents:(NSArray *)components { - if (components.count < 3 || components.count > 4) { - return nil; + if (components.count < 3 || components.count > 4) { + return nil; + } + + for (NSExpression *component in components) { + if (component.expressionType != NSConstantValueExpressionType) { + return nil; } - for (NSExpression *component in components) { - if (component.expressionType != NSConstantValueExpressionType) { - return nil; - } - - NSNumber *number = (NSNumber *)component.constantValue; - if (![number isKindOfClass:[NSNumber class]]) { - return nil; - } + NSNumber *number = (NSNumber *)component.constantValue; + if (![number isKindOfClass:[NSNumber class]]) { + return nil; } + } - return [UIColor colorWithRed:[components[0].constantValue doubleValue] / 255.0 - green:[components[1].constantValue doubleValue] / 255.0 - blue:[components[2].constantValue doubleValue] / 255.0 - alpha:components.count == 3 ? 1.0 : [components[3].constantValue doubleValue]]; + return [UIColor + colorWithRed:[components[0].constantValue doubleValue] / 255.0 + green:[components[1].constantValue doubleValue] / 255.0 + blue:[components[2].constantValue doubleValue] / 255.0 + alpha:components.count == 3 ? 1.0 : [components[3].constantValue doubleValue]]; } @end diff --git a/platform/ios/src/UIImage+MLNAdditions.mm b/platform/ios/src/UIImage+MLNAdditions.mm index fdd4bf0ae..eb8870a8c 100644 --- a/platform/ios/src/UIImage+MLNAdditions.mm +++ b/platform/ios/src/UIImage+MLNAdditions.mm @@ -1,128 +1,125 @@ -#import "UIImage+MLNAdditions.h" +#import + #import "NSBundle+MLNAdditions.h" +#include #include const MLNExceptionName MLNResourceNotFoundException = @"MLNResourceNotFoundException"; BOOL MLNEdgeInsetsIsZero(UIEdgeInsets edgeInsets) { - return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && + edgeInsets.bottom == 0; } @implementation UIImage (MLNAdditions) -- (nullable instancetype)initWithMLNStyleImage:(const mbgl::style::Image &)styleImage -{ - CGImageRef image = CGImageCreateWithMLNPremultipliedImage(styleImage.getImage().clone()); - if (!image) { - return nil; +- (nullable instancetype)initWithMLNStyleImage:(const mbgl::style::Image &)styleImage { + CGImageRef image = CGImageCreateWithMLNPremultipliedImage(styleImage.getImage().clone()); + if (!image) { + return nil; + } + + CGFloat scale = styleImage.getPixelRatio(); + if (self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]) { + if (styleImage.isSdf()) { + self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } - CGFloat scale = styleImage.getPixelRatio(); - if (self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]) - { - if (styleImage.isSdf()) - { - self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; - } - - if (auto content = styleImage.getContent()) - { - UIEdgeInsets capInsets = UIEdgeInsetsMake(content->top / scale, - content->left / scale, - self.size.height - content->bottom / scale, - self.size.width - content->right / scale); - UIImageResizingMode resizingMode = UIImageResizingModeTile; - if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) - { - resizingMode = UIImageResizingModeStretch; - } - self = [self resizableImageWithCapInsets:capInsets resizingMode:resizingMode]; - } - if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) - { - self = [self resizableImageWithCapInsets:self.capInsets resizingMode:UIImageResizingModeStretch]; - } + if (auto content = styleImage.getContent()) { + UIEdgeInsets capInsets = UIEdgeInsetsMake(content->top / scale, content->left / scale, + self.size.height - content->bottom / scale, + self.size.width - content->right / scale); + UIImageResizingMode resizingMode = UIImageResizingModeTile; + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) { + resizingMode = UIImageResizingModeStretch; + } + self = [self resizableImageWithCapInsets:capInsets resizingMode:resizingMode]; } - CGImageRelease(image); - return self; + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) { + self = [self resizableImageWithCapInsets:self.capInsets + resizingMode:UIImageResizingModeStretch]; + } + } + CGImageRelease(image); + return self; } -- (nullable instancetype)initWithMLNPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage scale:(CGFloat)scale -{ - CGImageRef image = CGImageCreateWithMLNPremultipliedImage(mbglImage.clone()); - if (!image) { - return nil; - } +- (nullable instancetype)initWithMLNPremultipliedImage:(const mbgl::PremultipliedImage &&)mbglImage + scale:(CGFloat)scale { + CGImageRef image = CGImageCreateWithMLNPremultipliedImage(mbglImage.clone()); + if (!image) { + return nil; + } - self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]; + self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]; - CGImageRelease(image); - return self; + CGImageRelease(image); + return self; } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { - mbgl::style::ImageStretches stretchX, stretchY; - if (self.resizingMode == UIImageResizingModeStretch) - { - stretchX.push_back({ - self.capInsets.left * self.scale, (self.size.width - self.capInsets.right) * self.scale, - }); - stretchY.push_back({ - self.capInsets.top * self.scale, (self.size.height - self.capInsets.bottom) * self.scale, - }); - } + mbgl::style::ImageStretches stretchX, stretchY; + if (self.resizingMode == UIImageResizingModeStretch) { + stretchX.push_back({ + self.capInsets.left * self.scale, + (self.size.width - self.capInsets.right) * self.scale, + }); + stretchY.push_back({ + self.capInsets.top * self.scale, + (self.size.height - self.capInsets.bottom) * self.scale, + }); + } - std::optional imageContent; - if (!MLNEdgeInsetsIsZero(self.capInsets)) - { - imageContent = (mbgl::style::ImageContent){ - .left = static_cast(self.capInsets.left * self.scale), - .top = static_cast(self.capInsets.top * self.scale), - .right = static_cast((self.size.width - self.capInsets.right) * self.scale), - .bottom = static_cast((self.size.height - self.capInsets.bottom) * self.scale), - }; - } + std::optional imageContent; + if (!MLNEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * self.scale), + .top = static_cast(self.capInsets.top * self.scale), + .right = static_cast((self.size.width - self.capInsets.right) * self.scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * self.scale), + }; + } - BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; - return std::make_unique([identifier UTF8String], - self.mgl_premultipliedImage, - static_cast(self.scale), - isTemplate, - stretchX, stretchY, - imageContent); + BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; + return std::make_unique([identifier UTF8String], self.mgl_premultipliedImage, + static_cast(self.scale), isTemplate, stretchX, + stretchY, imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage { - return MLNPremultipliedImageFromCGImage(self.CGImage); + return MLNPremultipliedImageFromCGImage(self.CGImage); } + (UIImage *)mgl_resourceImageNamed:(NSString *)imageName { - UIImage *image = [UIImage imageNamed:imageName - inBundle:[NSBundle mgl_frameworkBundle] - compatibleWithTraitCollection:nil]; + UIImage *image = [UIImage imageNamed:imageName + inBundle:[NSBundle mgl_frameworkBundle] + compatibleWithTraitCollection:nil]; - if (!image) { - [NSException raise:MLNResourceNotFoundException format:@"The resource named “%@” could not be found in the Mapbox framework bundle.", imageName]; - } + if (!image) { + [NSException + raise:MLNResourceNotFoundException + format:@"The resource named “%@” could not be found in the Mapbox framework bundle.", + imageName]; + } - return image; + return image; } -+(UIImage *)NormalizedImage:(UIImage *)sourceImage { - CGSize scaledSize = CGSizeMake(sourceImage.size.width * sourceImage.scale, sourceImage.size.height * sourceImage.scale); - UIGraphicsBeginImageContextWithOptions(scaledSize, NO, UIScreen.mainScreen.scale); - [sourceImage drawInRect:(CGRect){ .origin = CGPointZero, .size = scaledSize }]; - UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return normalizedImage; ++ (UIImage *)NormalizedImage:(UIImage *)sourceImage { + CGSize scaledSize = CGSizeMake(sourceImage.size.width * sourceImage.scale, + sourceImage.size.height * sourceImage.scale); + UIGraphicsBeginImageContextWithOptions(scaledSize, NO, UIScreen.mainScreen.scale); + [sourceImage drawInRect:(CGRect){.origin = CGPointZero, .size = scaledSize}]; + UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return normalizedImage; } - -- (BOOL)isDataEqualTo:(UIImage*)otherImage { - NSData *leftData = UIImagePNGRepresentation([UIImage NormalizedImage:self]); - NSData *rightData = UIImagePNGRepresentation([UIImage NormalizedImage:otherImage]); - return [leftData isEqualToData:rightData]; +- (BOOL)isDataEqualTo:(UIImage *)otherImage { + NSData *leftData = UIImagePNGRepresentation([UIImage NormalizedImage:self]); + NSData *rightData = UIImagePNGRepresentation([UIImage NormalizedImage:otherImage]); + return [leftData isEqualToData:rightData]; } @end diff --git a/platform/ios/test/MLNMapViewCompassViewTests.mm b/platform/ios/test/MLNMapViewCompassViewTests.mm index 9c3e9ca92..a4f9470c6 100644 --- a/platform/ios/test/MLNMapViewCompassViewTests.mm +++ b/platform/ios/test/MLNMapViewCompassViewTests.mm @@ -14,85 +14,97 @@ @implementation MLNCompassButtonTests - (void)setUp { - [super setUp]; + [super setUp]; - [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.mapView = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; } - (void)tearDown { - self.mapView = nil; - [MLNSettings setApiKey:nil]; + self.mapView = nil; + [MLNSettings setApiKey:nil]; - [super tearDown]; + [super tearDown]; } - (void)testCompassButton { - XCTAssertNotNil(self.mapView.compassView); - XCTAssertTrue([self.mapView.compassView isKindOfClass:[MLNCompassButton class]]); - XCTAssertTrue(self.mapView.compassView.userInteractionEnabled); - XCTAssertEqual(self.mapView.compassView.gestureRecognizers.count, (unsigned long)1); - XCTAssertEqual(self.mapView.compassView.accessibilityTraits, UIAccessibilityTraitButton); - XCTAssertNotNil(self.mapView.compassView.accessibilityLabel); - XCTAssertNotNil(self.mapView.compassView.accessibilityHint); + XCTAssertNotNil(self.mapView.compassView); + XCTAssertTrue([self.mapView.compassView isKindOfClass:[MLNCompassButton class]]); + XCTAssertTrue(self.mapView.compassView.userInteractionEnabled); + XCTAssertEqual(self.mapView.compassView.gestureRecognizers.count, (unsigned long)1); + XCTAssertEqual(self.mapView.compassView.accessibilityTraits, UIAccessibilityTraitButton); + XCTAssertNotNil(self.mapView.compassView.accessibilityLabel); + XCTAssertNotNil(self.mapView.compassView.accessibilityHint); } - (void)testVisibilityAdaptive { - XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityAdaptive, @"Adaptive should be the default visibility setting."); - XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible."); + XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityAdaptive, + @"Adaptive should be the default visibility setting."); + XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible."); - self.mapView.zoomLevel = 15.f; - [self.mapView setDirection:45.f animated:false]; - XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); - XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should become visible when direction changes."); + self.mapView.zoomLevel = 15.f; + [self.mapView setDirection:45.f animated:false]; + XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); + XCTAssertEqual(self.mapView.compassView.alpha, 1, + @"Compass should become visible when direction changes."); - [self.mapView resetNorthAnimated:NO]; - XCTAssertEqual(self.mapView.direction, 0); - XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should hide itself when direction is north."); + [self.mapView resetNorthAnimated:NO]; + XCTAssertEqual(self.mapView.direction, 0); + XCTAssertEqual(self.mapView.compassView.alpha, 0, + @"Compass should hide itself when direction is north."); } - (void)testVisibilityHidden { - self.mapView.compassView.compassVisibility = MLNOrnamentVisibilityHidden; - XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityHidden); - XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible."); + self.mapView.compassView.compassVisibility = MLNOrnamentVisibilityHidden; + XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityHidden); + XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should not initially be visible."); - self.mapView.zoomLevel = 15.f; - [self.mapView setDirection:45.f animated:false]; - XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); - XCTAssertEqual(self.mapView.compassView.alpha, 0, @"Compass should stay hidden when direction changes."); + self.mapView.zoomLevel = 15.f; + [self.mapView setDirection:45.f animated:false]; + XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); + XCTAssertEqual(self.mapView.compassView.alpha, 0, + @"Compass should stay hidden when direction changes."); } - (void)testVisibilityVisible { - self.mapView.compassView.compassVisibility = MLNOrnamentVisibilityVisible; - XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityVisible); - XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should initially be visible."); + self.mapView.compassView.compassVisibility = MLNOrnamentVisibilityVisible; + XCTAssertEqual(self.mapView.compassView.compassVisibility, MLNOrnamentVisibilityVisible); + XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should initially be visible."); - self.mapView.zoomLevel = 15.f; - [self.mapView setDirection:45.f animated:false]; - XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); - XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should continue to be visible when direction changes."); + self.mapView.zoomLevel = 15.f; + [self.mapView setDirection:45.f animated:false]; + XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001); + XCTAssertEqual(self.mapView.compassView.alpha, 1, + @"Compass should continue to be visible when direction changes."); - [self.mapView resetNorthAnimated:NO]; - XCTAssertEqual(self.mapView.direction, 0); - XCTAssertEqual(self.mapView.compassView.alpha, 1, @"Compass should continue to be visible when direction is north."); + [self.mapView resetNorthAnimated:NO]; + XCTAssertEqual(self.mapView.direction, 0); + XCTAssertEqual(self.mapView.compassView.alpha, 1, + @"Compass should continue to be visible when direction is north."); } - (void)testCompassRotation { - self.mapView.zoomLevel = 15; + self.mapView.zoomLevel = 15; - for (NSNumber *degrees in @[@-999, @-359, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @360, @999]) { - self.mapView.direction = [degrees doubleValue]; - CGFloat wrappedDirection = mbgl::util::wrap(-self.mapView.direction, 0., 360.); - CGAffineTransform rotation = CGAffineTransformMakeRotation(MLNRadiansFromDegrees(wrappedDirection)); - XCTAssertTrue(CGAffineTransformEqualToTransform(self.mapView.compassView.transform, rotation), - @"Compass transform direction %f° should equal wrapped transform direction %f° (~%.f°).", [self degreesFromAffineTransform:self.mapView.compassView.transform], [self degreesFromAffineTransform:rotation], wrappedDirection); - } + for (NSNumber *degrees in + @[ @-999, @-359, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @360, @999 ]) { + self.mapView.direction = [degrees doubleValue]; + CGFloat wrappedDirection = mbgl::util::wrap(-self.mapView.direction, 0., 360.); + CGAffineTransform rotation = + CGAffineTransformMakeRotation(MLNRadiansFromDegrees(wrappedDirection)); + XCTAssertTrue( + CGAffineTransformEqualToTransform(self.mapView.compassView.transform, rotation), + @"Compass transform direction %f° should equal wrapped transform direction %f° (~%.f°).", + [self degreesFromAffineTransform:self.mapView.compassView.transform], + [self degreesFromAffineTransform:rotation], wrappedDirection); + } } - (CGFloat)degreesFromAffineTransform:(CGAffineTransform)transform { - CGFloat angle = atan2f(transform.b, transform.a); - return MLNDegreesFromRadians(angle); + CGFloat angle = atan2f(transform.b, transform.a); + return MLNDegreesFromRadians(angle); } @end diff --git a/platform/ios/test/MLNMapViewDirectionTests.mm b/platform/ios/test/MLNMapViewDirectionTests.mm index b3979ca60..b6c5dd130 100644 --- a/platform/ios/test/MLNMapViewDirectionTests.mm +++ b/platform/ios/test/MLNMapViewDirectionTests.mm @@ -9,7 +9,6 @@ - (void)resetNorthAnimated:(BOOL)animated; @end - @interface MLNMapViewDirectionTests : XCTestCase @property (nonatomic) MLNMapView *mapView; @end @@ -17,110 +16,139 @@ @implementation MLNMapViewDirectionTests - (void)setUp { - [super setUp]; + [super setUp]; - [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 64, 64) styleURL:styleURL]; + [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 64, 64) styleURL:styleURL]; } - (void)tearDown { - self.mapView = nil; - [MLNSettings setApiKey:nil]; - [super tearDown]; + self.mapView = nil; + [MLNSettings setApiKey:nil]; + [super tearDown]; } - (void)testDirection { - self.mapView.zoomLevel = 0; - self.mapView.direction = 30; - XCTAssertEqual(self.mapView.direction, 0, @"Rotation is not allowed at world-scale zoom levels."); + self.mapView.zoomLevel = 0; + self.mapView.direction = 30; + XCTAssertEqual(self.mapView.direction, 0, @"Rotation is not allowed at world-scale zoom levels."); - self.mapView.zoomLevel = 15; - CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; + self.mapView.zoomLevel = 15; + CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; - for (NSNumber *degrees in @[@-999, @-359, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @360, @999]) { - double inputDegrees = [degrees doubleValue]; - double wrappedDegrees = mbgl::util::wrap(inputDegrees, 0., 360.); - self.mapView.direction = inputDegrees; - XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedDegrees, 0.001); + for (NSNumber *degrees in + @[ @-999, @-359, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @360, @999 ]) { + double inputDegrees = [degrees doubleValue]; + double wrappedDegrees = mbgl::util::wrap(inputDegrees, 0., 360.); + self.mapView.direction = inputDegrees; + XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedDegrees, 0.001); - XCTAssertEqual(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, "@Map center coordinate latitude should remain constant when direction is set to %@°.", degrees); - XCTAssertEqual(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, @"Map center coordinate longitude should remain constant when direction is set to %@°.", degrees); - } + XCTAssertEqual( + originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, + "@Map center coordinate latitude should remain constant when direction is set to %@°.", + degrees); + XCTAssertEqual( + originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, + @"Map center coordinate longitude should remain constant when direction is set to %@°.", + degrees); + } - [self.mapView resetNorthAnimated:NO]; - XCTAssertEqual(self.mapView.direction, 0, @"Reset-to-north should set direction to 0°."); - XCTAssertEqual(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, "@Map center coordinate latitude should remain constant when direction is reset."); - XCTAssertEqual(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, @"Map center coordinate latitude should remain constant when direction is reset."); + [self.mapView resetNorthAnimated:NO]; + XCTAssertEqual(self.mapView.direction, 0, @"Reset-to-north should set direction to 0°."); + XCTAssertEqual(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, + "@Map center coordinate latitude should remain constant when direction is reset."); + XCTAssertEqual(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, + @"Map center coordinate latitude should remain constant when direction is reset."); } - (void)testRotateEnabled { - self.mapView.zoomLevel = 10; + self.mapView.zoomLevel = 10; - UIRotationGestureRecognizerMock *gesture = [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - gesture.state = UIGestureRecognizerStateBegan; + UIRotationGestureRecognizerMock *gesture = + [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + gesture.rotation = MLNRadiansFromDegrees(30); + CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(gesture.rotation), 0., 360.); + + // Disabled + { + self.mapView.rotateEnabled = NO; + XCTAssertEqual(self.mapView.allowsRotating, NO); + + [self.mapView handleRotateGesture:gesture]; + XCTAssertNotEqual(self.mapView.direction, wrappedRotation, + @"Gestural rotation should not work when rotation is disabled."); + + self.mapView.direction = 45.f; + XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001, + @"Programmatic rotation is allowed when rotateEnabled = NO."); + } + + // Enabled + { + [self.mapView resetNorthAnimated:NO]; + self.mapView.rotateEnabled = YES; + XCTAssertEqual(self.mapView.allowsRotating, YES); + + gesture.state = UIGestureRecognizerStateChanged; gesture.rotation = MLNRadiansFromDegrees(30); - CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(gesture.rotation), 0., 360.); - - // Disabled - { - self.mapView.rotateEnabled = NO; - XCTAssertEqual(self.mapView.allowsRotating, NO); - - [self.mapView handleRotateGesture:gesture]; - XCTAssertNotEqual(self.mapView.direction, wrappedRotation, @"Gestural rotation should not work when rotation is disabled."); - - self.mapView.direction = 45.f; - XCTAssertEqualWithAccuracy(self.mapView.direction, 45, 0.001, @"Programmatic rotation is allowed when rotateEnabled = NO."); - } - - // Enabled - { - [self.mapView resetNorthAnimated:NO]; - self.mapView.rotateEnabled = YES; - XCTAssertEqual(self.mapView.allowsRotating, YES); - - gesture.state = UIGestureRecognizerStateChanged; - gesture.rotation = MLNRadiansFromDegrees(30); - [self.mapView handleRotateGesture:gesture]; - XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, @"Gestural rotation should work when rotation is enabled."); - } + [self.mapView handleRotateGesture:gesture]; + XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, + @"Gestural rotation should work when rotation is enabled."); + } } - (void)testRotationGesture { - self.mapView.zoomLevel = 15; - CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; + self.mapView.zoomLevel = 15; + CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; - UIRotationGestureRecognizerMock *gesture = [[UIRotationGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; - gesture.state = UIGestureRecognizerStateBegan; - gesture.rotation = 0; - [self.mapView addGestureRecognizer:gesture]; + UIRotationGestureRecognizerMock *gesture = + [[UIRotationGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + gesture.rotation = 0; + [self.mapView addGestureRecognizer:gesture]; + [self.mapView handleRotateGesture:gesture]; + XCTAssertEqual(self.mapView.direction, gesture.rotation); + + for (NSNumber *degrees in + @[ @-999, @-360, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @359, @999 ]) { + gesture.state = UIGestureRecognizerStateChanged; + gesture.rotation = MLNRadiansFromDegrees([degrees doubleValue]); [self.mapView handleRotateGesture:gesture]; - XCTAssertEqual(self.mapView.direction, gesture.rotation); + CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(gesture.rotation), 0., 360.); + XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, + @"Map direction should match gesture rotation for input of %@°.", + degrees); - for (NSNumber *degrees in @[@-999, @-360, @-240, @-180, @-90, @-45, @0, @45, @90, @180, @240, @359, @999]) { - gesture.state = UIGestureRecognizerStateChanged; - gesture.rotation = MLNRadiansFromDegrees([degrees doubleValue]); - [self.mapView handleRotateGesture:gesture]; - CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(gesture.rotation), 0., 360.); - XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, @"Map direction should match gesture rotation for input of %@°.", degrees); - - // Given a hypothetical rotation around the exact center of the map, the center coordinate should remain the same. - // See above where we override -[UIRotationGestureRecognizer locationInView:] to always return the center of the target view. - XCTAssertEqualWithAccuracy(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, "@Map center coordinate latitude should remain constant during rotation of %@°.", degrees); - XCTAssertEqualWithAccuracy(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, @"Map center coordinate longitude should remain constant during rotation of %@°.", degrees); - } + // Given a hypothetical rotation around the exact center of the map, the center coordinate + // should remain the same. See above where we override -[UIRotationGestureRecognizer + // locationInView:] to always return the center of the target view. + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, + "@Map center coordinate latitude should remain constant during rotation of %@°.", degrees); + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, + @"Map center coordinate longitude should remain constant during rotation of %@°.", degrees); + } } - (void)testResetPosition { - [self.mapView resetPosition]; - MLNMapCamera *defaultCamera = [MLNMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(0, 0) altitude:self.mapView.camera.altitude pitch:0 heading:0]; - XCTAssertTrue([self.mapView.camera isEqualToMapCamera:defaultCamera], @"Map camera %@ should be equal to default camera %@.", self.mapView.camera, defaultCamera); + [self.mapView resetPosition]; + MLNMapCamera *defaultCamera = + [MLNMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(0, 0) + altitude:self.mapView.camera.altitude + pitch:0 + heading:0]; + XCTAssertTrue([self.mapView.camera isEqualToMapCamera:defaultCamera], + @"Map camera %@ should be equal to default camera %@.", self.mapView.camera, + defaultCamera); } - (CGFloat)degreesFromAffineTransform:(CGAffineTransform)transform { - CGFloat angle = atan2f(transform.b, transform.a); - return MLNDegreesFromRadians(angle); + CGFloat angle = atan2f(transform.b, transform.a); + return MLNDegreesFromRadians(angle); } @end diff --git a/platform/ios/test/MLNMapViewGestureRecognizerTests.mm b/platform/ios/test/MLNMapViewGestureRecognizerTests.mm index 0bb636ae2..eee43313f 100644 --- a/platform/ios/test/MLNMapViewGestureRecognizerTests.mm +++ b/platform/ios/test/MLNMapViewGestureRecognizerTests.mm @@ -2,11 +2,11 @@ #import #import "../../darwin/src/MLNGeometry_Private.h" -#import "MLNMockGestureRecognizers.h" #import "MLNMapView_Private.h" +#import "MLNMockGestureRecognizers.h" -#include #include +#include @interface MLNMapView (MLNMapViewGestureRecognizerTests) @@ -36,252 +36,309 @@ @implementation MLNMapViewGestureRecognizerTests - (void)setUp { - [super setUp]; + [super setUp]; - [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.screenBounds = UIScreen.mainScreen.bounds; - self.mapView = [[MLNMapView alloc] initWithFrame:self.screenBounds styleURL:styleURL]; - self.mapView.zoomLevel = 16; - self.mapView.delegate = self; + [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.screenBounds = UIScreen.mainScreen.bounds; + self.mapView = [[MLNMapView alloc] initWithFrame:self.screenBounds styleURL:styleURL]; + self.mapView.zoomLevel = 16; + self.mapView.delegate = self; - self.viewController = [[UIViewController alloc] init]; - self.viewController.view = [[UIView alloc] initWithFrame:self.screenBounds]; - [self.viewController.view addSubview:self.mapView]; - self.window = [[UIWindow alloc] initWithFrame:self.screenBounds]; - [self.window addSubview:self.viewController.view]; - [self.window makeKeyAndVisible]; + self.viewController = [[UIViewController alloc] init]; + self.viewController.view = [[UIView alloc] initWithFrame:self.screenBounds]; + [self.viewController.view addSubview:self.mapView]; + self.window = [[UIWindow alloc] initWithFrame:self.screenBounds]; + [self.window addSubview:self.viewController.view]; + [self.window makeKeyAndVisible]; - if (!self.mapView.style) { - _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self waitForExpectationsWithTimeout:10 handler:nil]; - } + if (!self.mapView.style) { + _styleLoadingExpectation = + [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + } } - (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style { - XCTAssertNotNil(mapView.style); - XCTAssertEqual(mapView.style, style); + XCTAssertNotNil(mapView.style); + XCTAssertEqual(mapView.style, style); - [_styleLoadingExpectation fulfill]; + [_styleLoadingExpectation fulfill]; } - (void)testHandlePinchGestureContentInset { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UIPinchGestureRecognizerMock *pinchGesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - pinchGesture.state = UIGestureRecognizerStateBegan; - pinchGesture.scale = 1.0; - [self.mapView handlePinchGesture:pinchGesture]; - XCTAssertNotEqual(padding, cameraPadding); + UIPinchGestureRecognizerMock *pinchGesture = + [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + pinchGesture.state = UIGestureRecognizerStateBegan; + pinchGesture.scale = 1.0; + [self.mapView handlePinchGesture:pinchGesture]; + XCTAssertNotEqual(padding, cameraPadding); - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - pinchGesture.state = UIGestureRecognizerStateChanged; - [self.mapView handlePinchGesture:pinchGesture]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + pinchGesture.state = UIGestureRecognizerStateChanged; + [self.mapView handlePinchGesture:pinchGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); - pinchGesture.state = UIGestureRecognizerStateEnded; - [self.mapView handlePinchGesture:pinchGesture]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + pinchGesture.state = UIGestureRecognizerStateEnded; + [self.mapView handlePinchGesture:pinchGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } - (void)testHandleRotateGestureContentInset { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UIRotationGestureRecognizerMock *rotateGesture = [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - rotateGesture.state = UIGestureRecognizerStateBegan; - rotateGesture.rotation = 1; - [self.mapView handleRotateGesture:rotateGesture]; - XCTAssertNotEqual(padding, cameraPadding); + UIRotationGestureRecognizerMock *rotateGesture = + [[UIRotationGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + rotateGesture.state = UIGestureRecognizerStateBegan; + rotateGesture.rotation = 1; + [self.mapView handleRotateGesture:rotateGesture]; + XCTAssertNotEqual(padding, cameraPadding); - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - rotateGesture.state = UIGestureRecognizerStateChanged; - [self.mapView handleRotateGesture:rotateGesture]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + rotateGesture.state = UIGestureRecognizerStateChanged; + [self.mapView handleRotateGesture:rotateGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); - rotateGesture.state = UIGestureRecognizerStateEnded; - [self.mapView handleRotateGesture:rotateGesture]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + rotateGesture.state = UIGestureRecognizerStateEnded; + [self.mapView handleRotateGesture:rotateGesture]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } - (void)testHandleDoubleTapGestureContentInset { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UITapGestureRecognizerMock *doubleTapGesture = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - doubleTapGesture.mockTappedView = self.mapView; - doubleTapGesture.mockTappedPoint = CGPointMake(1.0, 1.0); + UITapGestureRecognizerMock *doubleTapGesture = + [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + doubleTapGesture.mockTappedView = self.mapView; + doubleTapGesture.mockTappedPoint = CGPointMake(1.0, 1.0); - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - [self.mapView handleDoubleTapGesture:doubleTapGesture]; - _doubleTapExpectation = [self expectationWithDescription:@"Double tap gesture animation."]; + [self.mapView handleDoubleTapGesture:doubleTapGesture]; + _doubleTapExpectation = [self expectationWithDescription:@"Double tap gesture animation."]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self->_doubleTapExpectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [self->_doubleTapExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } - (void)testHandleTwoFingerTapGesture { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UITapGestureRecognizerMock *twoFingerTap = [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - twoFingerTap.mockTappedView = self.mapView; - twoFingerTap.mockTappedPoint = CGPointMake(1.0, 1.0); + UITapGestureRecognizerMock *twoFingerTap = + [[UITapGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + twoFingerTap.mockTappedView = self.mapView; + twoFingerTap.mockTappedPoint = CGPointMake(1.0, 1.0); - [self.mapView handleTwoFingerTapGesture:twoFingerTap]; - _twoFingerExpectation = [self expectationWithDescription:@"Two Finger tap gesture animation."]; + [self.mapView handleTwoFingerTapGesture:twoFingerTap]; + _twoFingerExpectation = [self expectationWithDescription:@"Two Finger tap gesture animation."]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self->_twoFingerExpectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [self->_twoFingerExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } - (void)testHandleQuickZoomGesture { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UILongPressGestureRecognizerMock *quickZoom = [[UILongPressGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - quickZoom.state = UIGestureRecognizerStateBegan; - [self.mapView handleQuickZoomGesture:quickZoom]; - XCTAssertNotEqual(padding, cameraPadding); + UILongPressGestureRecognizerMock *quickZoom = + [[UILongPressGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + quickZoom.state = UIGestureRecognizerStateBegan; + [self.mapView handleQuickZoomGesture:quickZoom]; + XCTAssertNotEqual(padding, cameraPadding); - quickZoom.state = UIGestureRecognizerStateChanged; - quickZoom.mockTappedPoint = CGPointMake(self.mapView.frame.size.width / 2, self.mapView.frame.size.height / 2); - [self.mapView handleQuickZoomGesture:quickZoom]; - _quickZoomExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; + quickZoom.state = UIGestureRecognizerStateChanged; + quickZoom.mockTappedPoint = + CGPointMake(self.mapView.frame.size.width / 2, self.mapView.frame.size.height / 2); + [self.mapView handleQuickZoomGesture:quickZoom]; + _quickZoomExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self->_quickZoomExpectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [self->_quickZoomExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); - quickZoom.state = UIGestureRecognizerStateEnded; - [self.mapView handleQuickZoomGesture:quickZoom]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + quickZoom.state = UIGestureRecognizerStateEnded; + [self.mapView handleQuickZoomGesture:quickZoom]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } - (void)testHandleTwoFingerDragGesture { - UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); - self.mapView.contentInset = contentInset; - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); - auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(padding, cameraPadding, @"MLNMapView's contentInset property should match camera's padding."); - XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + UIEdgeInsets contentInset = UIEdgeInsetsMake(1, 1, 1, 1); + self.mapView.contentInset = contentInset; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.mapView.contentInset); + auto cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(padding, cameraPadding, + @"MLNMapView's contentInset property should match camera's padding."); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - contentInset = UIEdgeInsetsMake(20, 20, 20, 20); - [self.mapView setCamera:self.mapView.camera withDuration:0.0 animationTimingFunction:nil edgePadding:contentInset completionHandler:nil]; - XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); + contentInset = UIEdgeInsetsMake(20, 20, 20, 20); + [self.mapView setCamera:self.mapView.camera + withDuration:0.0 + animationTimingFunction:nil + edgePadding:contentInset + completionHandler:nil]; + XCTAssertFalse(UIEdgeInsetsEqualToEdgeInsets(self.mapView.contentInset, contentInset)); - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertNotEqual(padding, cameraPadding); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertNotEqual(padding, cameraPadding); - UIPanGestureRecognizerMock *twoFingerDrag = [[UIPanGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - twoFingerDrag.state = UIGestureRecognizerStateBegan; - twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, self.mapView.frame.size.height/2); - twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), self.mapView.frame.size.height/2); - twoFingerDrag.numberOfTouches = 2; - [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; - XCTAssertNotEqual(padding, cameraPadding); + UIPanGestureRecognizerMock *twoFingerDrag = + [[UIPanGestureRecognizerMock alloc] initWithTarget:nil action:nil]; + twoFingerDrag.state = UIGestureRecognizerStateBegan; + twoFingerDrag.firstFingerPoint = + CGPointMake(self.mapView.frame.size.width / 3, self.mapView.frame.size.height / 2); + twoFingerDrag.secondFingerPoint = + CGPointMake((self.mapView.frame.size.width / 2), self.mapView.frame.size.height / 2); + twoFingerDrag.numberOfTouches = 2; + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + XCTAssertNotEqual(padding, cameraPadding); - twoFingerDrag.state = UIGestureRecognizerStateChanged; - twoFingerDrag.firstFingerPoint = CGPointMake(self.mapView.frame.size.width / 3, (self.mapView.frame.size.height/2)-10); - twoFingerDrag.secondFingerPoint = CGPointMake((self.mapView.frame.size.width / 2), (self.mapView.frame.size.height/2)-10); - [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; - _twoFingerDragExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; + twoFingerDrag.state = UIGestureRecognizerStateChanged; + twoFingerDrag.firstFingerPoint = + CGPointMake(self.mapView.frame.size.width / 3, (self.mapView.frame.size.height / 2) - 10); + twoFingerDrag.secondFingerPoint = + CGPointMake((self.mapView.frame.size.width / 2), (self.mapView.frame.size.height / 2) - 10); + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + _twoFingerDragExpectation = [self expectationWithDescription:@"Quick zoom gesture animation."]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self->_twoFingerDragExpectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10 handler:nil]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + [self->_twoFingerDragExpectation fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; - mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; + mbgl::EdgeInsets edgePadding = MLNEdgeInsetsFromNSEdgeInsets(contentInset) + padding; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); - twoFingerDrag.state = UIGestureRecognizerStateEnded; - [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; - cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; - XCTAssertEqual(edgePadding, cameraPadding, @"When a gesture recognizer is performed camera paddings must not be changed."); + twoFingerDrag.state = UIGestureRecognizerStateEnded; + [self.mapView handleTwoFingerDragGesture:twoFingerDrag]; + cameraPadding = self.mapView.mbglMap.getCameraOptions().padding; + XCTAssertEqual(edgePadding, cameraPadding, + @"When a gesture recognizer is performed camera paddings must not be changed."); } @end diff --git a/platform/ios/test/MLNMapViewZoomTests.mm b/platform/ios/test/MLNMapViewZoomTests.mm index bdd8c4cb2..7812ce927 100644 --- a/platform/ios/test/MLNMapViewZoomTests.mm +++ b/platform/ios/test/MLNMapViewZoomTests.mm @@ -18,175 +18,203 @@ @implementation MLNMapViewZoomTests - (void)setUp { - [super setUp]; + [super setUp]; - [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" + withExtension:@"json"]; + self.mapView = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; } - (void)tearDown { - self.mapView = nil; - [MLNSettings setApiKey:nil]; - [super tearDown]; + self.mapView = nil; + [MLNSettings setApiKey:nil]; + [super tearDown]; } - (void)testZoom { - CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; + CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; - for (NSNumber *zoom in @[@1, @5, @10, @15, @22]) { - self.mapView.zoomLevel = [zoom doubleValue]; - XCTAssertEqual(self.mapView.zoomLevel, [zoom doubleValue], @"Map zoom should match z%@.", zoom); + for (NSNumber *zoom in @[ @1, @5, @10, @15, @22 ]) { + self.mapView.zoomLevel = [zoom doubleValue]; + XCTAssertEqual(self.mapView.zoomLevel, [zoom doubleValue], @"Map zoom should match z%@.", zoom); - XCTAssertEqualWithAccuracy(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, "@Map center coordinate latitude should remain constant during zoom to z%@.", zoom); - XCTAssertEqualWithAccuracy(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, @"Map center coordinate longitude should remain constant during zoom to z%@.", zoom); - } + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, + "@Map center coordinate latitude should remain constant during zoom to z%@.", zoom); + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, + @"Map center coordinate longitude should remain constant during zoom to z%@.", zoom); + } } - (void)testZoomEnabled { - UIPinchGestureRecognizerMock *gesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil action:nil]; - gesture.state = UIGestureRecognizerStateBegan; - gesture.scale = 10.f; + UIPinchGestureRecognizerMock *gesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:nil + action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + gesture.scale = 10.f; + [self.mapView handlePinchGesture:gesture]; + gesture.state = UIGestureRecognizerStateChanged; + + // Disabled + { + self.mapView.zoomEnabled = NO; + XCTAssertEqual(self.mapView.allowsZooming, NO); + [self.mapView handlePinchGesture:gesture]; - gesture.state = UIGestureRecognizerStateChanged; + XCTAssertNotEqual(self.mapView.zoomLevel, log2(gesture.scale), + @"Gestural zoom should not work when zoom is disabled."); - // Disabled - { - self.mapView.zoomEnabled = NO; - XCTAssertEqual(self.mapView.allowsZooming, NO); + self.mapView.zoomLevel = 15.f; + XCTAssertEqualWithAccuracy(self.mapView.zoomLevel, 15, 0.001, + @"Programmatic zoom is allowed when zoomEnabled = NO."); + } - [self.mapView handlePinchGesture:gesture]; - XCTAssertNotEqual(self.mapView.zoomLevel, log2(gesture.scale), @"Gestural zoom should not work when zoom is disabled."); + // Enabled + { + // No need to reset the map zoom or gesture scale, since gesture scale hasn't been applied yet + // and the map zoom will be overriden when the gesture is handled. + self.mapView.zoomEnabled = YES; + XCTAssertEqual(self.mapView.allowsZooming, YES); - self.mapView.zoomLevel = 15.f; - XCTAssertEqualWithAccuracy(self.mapView.zoomLevel, 15, 0.001, @"Programmatic zoom is allowed when zoomEnabled = NO."); - } - - // Enabled - { - // No need to reset the map zoom or gesture scale, since gesture scale hasn't been applied yet and the map zoom will be overriden when the gesture is handled. - self.mapView.zoomEnabled = YES; - XCTAssertEqual(self.mapView.allowsZooming, YES); - - [self.mapView handlePinchGesture:gesture]; - XCTAssertEqualWithAccuracy(self.mapView.zoomLevel, log2(gesture.scale), 0.001, @"Gestural zoom should work when zoom is enabled."); - } + [self.mapView handlePinchGesture:gesture]; + XCTAssertEqualWithAccuracy(self.mapView.zoomLevel, log2(gesture.scale), 0.001, + @"Gestural zoom should work when zoom is enabled."); + } } - (void)testPinchGesture { - CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; + CLLocationCoordinate2D originalCenterCoordinate = self.mapView.centerCoordinate; - UIPinchGestureRecognizerMock *gesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; - gesture.state = UIGestureRecognizerStateBegan; - gesture.scale = 0; - gesture.locationInViewOverride = self.mapView.center; + UIPinchGestureRecognizerMock *gesture = + [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + gesture.scale = 0; + gesture.locationInViewOverride = self.mapView.center; + [self.mapView handlePinchGesture:gesture]; + XCTAssertEqual(self.mapView.zoomLevel, 0); + + for (NSNumber *zoom in @[ @1, @5, @10, @15, @22 ]) { + gesture.state = UIGestureRecognizerStateChanged; + gesture.scale = MLNScaleFromZoomLevel([zoom doubleValue]); [self.mapView handlePinchGesture:gesture]; - XCTAssertEqual(self.mapView.zoomLevel, 0); + XCTAssertEqual(self.mapView.zoomLevel, [zoom doubleValue], + @"Map zoom should match gesture to z%@.", zoom); - for (NSNumber *zoom in @[@1, @5, @10, @15, @22]) { - gesture.state = UIGestureRecognizerStateChanged; - gesture.scale = MLNScaleFromZoomLevel([zoom doubleValue]); - [self.mapView handlePinchGesture:gesture]; - XCTAssertEqual(self.mapView.zoomLevel, [zoom doubleValue], @"Map zoom should match gesture to z%@.", zoom); - - // Given a hypothetical zoom into the exact center of the map, the center coordinate should remain the same. - XCTAssertEqualWithAccuracy(originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, "@Map center coordinate latitude should remain constant during zoom to z%@.", zoom); - XCTAssertEqualWithAccuracy(originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, @"Map center coordinate longitude should remain constant during zoom to z%@.", zoom); - } + // Given a hypothetical zoom into the exact center of the map, the center coordinate should + // remain the same. + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, + "@Map center coordinate latitude should remain constant during zoom to z%@.", zoom); + XCTAssertEqualWithAccuracy( + originalCenterCoordinate.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, + @"Map center coordinate longitude should remain constant during zoom to z%@.", zoom); + } } // Regression test for: https://github.com/mapbox/mapbox-gl-native/issues/14977 - (void)testPinchGestureOffCenter { - self.mapView.zoomLevel = 15; + self.mapView.zoomLevel = 15; - // Set up pinch gesture at z15 with an origin of 0,0. - UIPinchGestureRecognizerMock *gesture = [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; - gesture.state = UIGestureRecognizerStateBegan; - gesture.scale = 0; - gesture.locationInViewOverride = CGPointMake(0, 0); - [self.mapView handlePinchGesture:gesture]; - XCTAssertEqual(self.mapView.zoomLevel, 15); + // Set up pinch gesture at z15 with an origin of 0,0. + UIPinchGestureRecognizerMock *gesture = + [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + gesture.state = UIGestureRecognizerStateBegan; + gesture.scale = 0; + gesture.locationInViewOverride = CGPointMake(0, 0); + [self.mapView handlePinchGesture:gesture]; + XCTAssertEqual(self.mapView.zoomLevel, 15); - // Set a map rotation so that we can later check if un-rotating happens around the correct center point. - self.mapView.direction = 45; + // Set a map rotation so that we can later check if un-rotating happens around the correct center + // point. + self.mapView.direction = 45; - // Zoom to z18 at the off-center origin. - gesture.state = UIGestureRecognizerStateChanged; - gesture.scale = MLNScaleFromZoomLevel(3); - [self.mapView handlePinchGesture:gesture]; - XCTAssertEqual(self.mapView.zoomLevel, 18, @"Map zoom should match original zoom plus gesture zoom."); + // Zoom to z18 at the off-center origin. + gesture.state = UIGestureRecognizerStateChanged; + gesture.scale = MLNScaleFromZoomLevel(3); + [self.mapView handlePinchGesture:gesture]; + XCTAssertEqual(self.mapView.zoomLevel, 18, + @"Map zoom should match original zoom plus gesture zoom."); - // Check that the center coordinate remains the same when direction is reset to north. - CLLocationCoordinate2D centerCoordinateBeforeReset = self.mapView.centerCoordinate; - CLLocationCoordinate2D manuallyDerivedCenterCoordinate = [self.mapView convertPoint:self.mapView.center toCoordinateFromView:nil]; - XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.latitude, manuallyDerivedCenterCoordinate.latitude, 0.0000001, "@Map center latitude should be equal to manually derived latitude."); - XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.longitude, manuallyDerivedCenterCoordinate.longitude, 0.0000001, @"Map center longitude should be equal to manually derived longitude."); + // Check that the center coordinate remains the same when direction is reset to north. + CLLocationCoordinate2D centerCoordinateBeforeReset = self.mapView.centerCoordinate; + CLLocationCoordinate2D manuallyDerivedCenterCoordinate = + [self.mapView convertPoint:self.mapView.center toCoordinateFromView:nil]; + XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.latitude, + manuallyDerivedCenterCoordinate.latitude, 0.0000001, + "@Map center latitude should be equal to manually derived latitude."); + XCTAssertEqualWithAccuracy( + centerCoordinateBeforeReset.longitude, manuallyDerivedCenterCoordinate.longitude, 0.0000001, + @"Map center longitude should be equal to manually derived longitude."); - self.mapView.direction = 0; - XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, "@Map center coordinate latitude should remain constant after resetting to north."); - XCTAssertEqualWithAccuracy(centerCoordinateBeforeReset.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, @"Map center coordinate longitude should remain constant after resetting to north."); + self.mapView.direction = 0; + XCTAssertEqualWithAccuracy( + centerCoordinateBeforeReset.latitude, self.mapView.centerCoordinate.latitude, 0.0000001, + "@Map center coordinate latitude should remain constant after resetting to north."); + XCTAssertEqualWithAccuracy( + centerCoordinateBeforeReset.longitude, self.mapView.centerCoordinate.longitude, 0.0000001, + @"Map center coordinate longitude should remain constant after resetting to north."); } - (void)testPinchAndZoom { + [[NSUserDefaults standardUserDefaults] setObject:@3 forKey:@"MLNRotationThresholdWhileZooming"]; + self.mapView.rotationThresholdWhileZooming = 3; + self.mapView.zoomLevel = 15; + UIPinchGestureRecognizerMock *pinch = + [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + [self.mapView addGestureRecognizer:pinch]; + pinch.state = UIGestureRecognizerStateBegan; + pinch.velocity = 5.0; + pinch.locationInViewOverride = CGPointMake(0, 0); + [self.mapView handlePinchGesture:pinch]; - [[NSUserDefaults standardUserDefaults] setObject:@3 forKey:@"MLNRotationThresholdWhileZooming"]; - self.mapView.rotationThresholdWhileZooming = 3; - self.mapView.zoomLevel = 15; - UIPinchGestureRecognizerMock *pinch = [[UIPinchGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; - [self.mapView addGestureRecognizer:pinch]; - pinch.state = UIGestureRecognizerStateBegan; - pinch.velocity = 5.0; - pinch.locationInViewOverride = CGPointMake(0, 0); - [self.mapView handlePinchGesture:pinch]; + XCTAssertTrue(self.mapView.isZooming); - XCTAssertTrue(self.mapView.isZooming); + UIRotationGestureRecognizerMock *rotate = + [[UIRotationGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; + rotate.state = UIGestureRecognizerStateBegan; + rotate.rotation = MLNRadiansFromDegrees(1); + [self.mapView addGestureRecognizer:rotate]; + [self.mapView handleRotateGesture:rotate]; - UIRotationGestureRecognizerMock *rotate = [[UIRotationGestureRecognizerMock alloc] initWithTarget:self.mapView action:nil]; - rotate.state = UIGestureRecognizerStateBegan; - rotate.rotation = MLNRadiansFromDegrees(1); - [self.mapView addGestureRecognizer:rotate]; - [self.mapView handleRotateGesture:rotate]; + // Both the rotation and direction should be zero since the rotation threshold hasn't been met. + XCTAssertEqual(rotate.rotation, 0); + XCTAssertEqual(self.mapView.direction, 0); - // Both the rotation and direction should be zero since the rotation threshold hasn't been met. - XCTAssertEqual(rotate.rotation, 0); - XCTAssertEqual(self.mapView.direction, 0); + // The direction should be `0`. The default rotation threshold is `3`. + XCTAssertEqual(self.mapView.direction, 0); + rotate.state = UIGestureRecognizerStateChanged; + rotate.rotation = MLNRadiansFromDegrees(2); + [self.mapView handleRotateGesture:rotate]; - // The direction should be `0`. The default rotation threshold is `3`. - XCTAssertEqual(self.mapView.direction, 0); + // The direction should be `0`. The default rotation threshold is `3`. + XCTAssertEqual(self.mapView.direction, 0); + + for (NSNumber *degrees in + @[ @-90, @-10, @10, @10, @30, @90, @180, @240, @460, @500, @590, @800 ]) { rotate.state = UIGestureRecognizerStateChanged; - rotate.rotation = MLNRadiansFromDegrees(2); + rotate.rotation = MLNRadiansFromDegrees([degrees doubleValue]); [self.mapView handleRotateGesture:rotate]; - // The direction should be `0`. The default rotation threshold is `3`. - XCTAssertEqual(self.mapView.direction, 0); + CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(rotate.rotation), 0., 360.); - for (NSNumber *degrees in @[@-90, @-10, @10, @10, @30, @90, @180, @240, @460, @500, @590, @800]) { - rotate.state = UIGestureRecognizerStateChanged; - rotate.rotation = MLNRadiansFromDegrees([degrees doubleValue]); - [self.mapView handleRotateGesture:rotate]; + // Check that the direction property now matches the gesture's rotation. + XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, + @"Map direction should match gesture rotation for input of %@°.", + degrees); + } - CGFloat wrappedRotation = mbgl::util::wrap(-MLNDegreesFromRadians(rotate.rotation), 0., 360.); + rotate.state = UIGestureRecognizerStateEnded; + pinch.state = UIGestureRecognizerStateEnded; + [self.mapView handleRotateGesture:rotate]; + [self.mapView handlePinchGesture:pinch]; - // Check that the direction property now matches the gesture's rotation. - XCTAssertEqualWithAccuracy(self.mapView.direction, wrappedRotation, 0.001, @"Map direction should match gesture rotation for input of %@°.", degrees); - } - - rotate.state = UIGestureRecognizerStateEnded; - pinch.state = UIGestureRecognizerStateEnded; - - [self.mapView handleRotateGesture:rotate]; - [self.mapView handlePinchGesture:pinch]; - - XCTAssertFalse(self.mapView.isZooming); + XCTAssertFalse(self.mapView.isZooming); } -NS_INLINE CGFloat MLNScaleFromZoomLevel(double zoom) { - return pow(2, zoom); -} +NS_INLINE CGFloat MLNScaleFromZoomLevel(double zoom) { return pow(2, zoom); } -__unused NS_INLINE double MLNZoomLevelFromScale(CGFloat scale) { - return log2(scale); -} +__unused NS_INLINE double MLNZoomLevelFromScale(CGFloat scale) { return log2(scale); } @end diff --git a/platform/ios/test/core/iosTestRunner.mm b/platform/ios/test/core/iosTestRunner.mm index 30f430b84..a23de54e6 100644 --- a/platform/ios/test/core/iosTestRunner.mm +++ b/platform/ios/test/core/iosTestRunner.mm @@ -16,41 +16,42 @@ @implementation IosTestRunner --(instancetype)init -{ - self = [super init]; - if (self) { - self.testStatus = NO; - self.runner = new TestRunner(); +- (instancetype)init { + self = [super init]; + if (self) { + self.testStatus = NO; + self.runner = new TestRunner(); - NSFileManager *fileManager = [NSFileManager defaultManager]; + NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *xcTestBundleRoot = [[NSBundle bundleForClass:[self class]] resourcePath]; - NSString *testDataDir = [xcTestBundleRoot stringByAppendingString:@"/TestData.bundle"]; + NSString *xcTestBundleRoot = [[NSBundle bundleForClass:[self class]] resourcePath]; + NSString *testDataDir = [xcTestBundleRoot stringByAppendingString:@"/TestData.bundle"]; - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDir = [paths objectAtIndex: 0]; + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDir = [paths objectAtIndex:0]; - NSString *destinationPath = [documentsDir stringByAppendingPathComponent: @"test"]; + NSString *destinationPath = [documentsDir stringByAppendingPathComponent:@"test"]; - [self copyFileAtPath:testDataDir toPath:destinationPath]; - [self copyFileAtPath:[testDataDir stringByAppendingPathComponent: @"scripts"] toPath:[documentsDir stringByAppendingPathComponent: @"scripts"]]; + [self copyFileAtPath:testDataDir toPath:destinationPath]; + [self copyFileAtPath:[testDataDir stringByAppendingPathComponent:@"scripts"] + toPath:[documentsDir stringByAppendingPathComponent:@"scripts"]]; - NSLog(@"Starting test"); - std::string basePath = std::string([documentsDir UTF8String]); - self.testStatus = self.runner->startTest(basePath) ? YES : NO; - self.resultPath = [destinationPath stringByAppendingPathComponent: @"results.xml"]; + NSLog(@"Starting test"); + std::string basePath = std::string([documentsDir UTF8String]); + self.testStatus = self.runner->startTest(basePath) ? YES : NO; + self.resultPath = [destinationPath stringByAppendingPathComponent:@"results.xml"]; - BOOL fileFound = [fileManager fileExistsAtPath: self.resultPath]; - if (fileFound == NO) { - NSLog(@"Test result file '%@' does not exist", self.resultPath); - self.testStatus = NO; - } - - delete self.runner; - self.runner = nullptr; + BOOL fileFound = [fileManager fileExistsAtPath:self.resultPath]; + if (fileFound == NO) { + NSLog(@"Test result file '%@' does not exist", self.resultPath); + self.testStatus = NO; } - return self; + + delete self.runner; + self.runner = nullptr; + } + return self; } /** @@ -60,32 +61,32 @@ @param destinationPath The destination path where the file should be copied. */ - (void)copyFileAtPath:(NSString *)sourcePath toPath:(NSString *)destinationPath { - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = nil; - BOOL success; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + BOOL success; - // delete destinationPath if it exists - if ([fileManager fileExistsAtPath:destinationPath]) { - success = [fileManager removeItemAtPath: destinationPath error: &error]; - if (!success) { - NSAssert1(0, @"Failed to delete '%@'.", destinationPath); - } - } - - success = [fileManager copyItemAtPath:sourcePath toPath:destinationPath error:&error]; + // delete destinationPath if it exists + if ([fileManager fileExistsAtPath:destinationPath]) { + success = [fileManager removeItemAtPath:destinationPath error:&error]; if (!success) { - NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]); - NSLog(@"Failed to copy %@ file, error %@", sourcePath, [error localizedDescription]); - } else { - NSLog(@"File copied %@ OK", sourcePath); + NSAssert1(0, @"Failed to delete '%@'.", destinationPath); } + } + + success = [fileManager copyItemAtPath:sourcePath toPath:destinationPath error:&error]; + if (!success) { + NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]); + NSLog(@"Failed to copy %@ file, error %@", sourcePath, [error localizedDescription]); + } else { + NSLog(@"File copied %@ OK", sourcePath); + } } -- (NSString*) getResultPath { - return self.resultPath; +- (NSString *)getResultPath { + return self.resultPath; } -- (BOOL) getTestStatus { - return self.testStatus; +- (BOOL)getTestStatus { + return self.testStatus; } @end diff --git a/platform/macos/src/MLNAttributionButton.mm b/platform/macos/src/MLNAttributionButton.mm index 680233b78..6a1d92dc7 100644 --- a/platform/macos/src/MLNAttributionButton.mm +++ b/platform/macos/src/MLNAttributionButton.mm @@ -7,49 +7,49 @@ @implementation MLNAttributionButton - (instancetype)initWithAttributionInfo:(MLNAttributionInfo *)info { - if (self = [super initWithFrame:NSZeroRect]) { - self.bordered = NO; - self.bezelStyle = NSBezelStyleBadge; + if (self = [super initWithFrame:NSZeroRect]) { + self.bordered = NO; + self.bezelStyle = NSBezelStyleBadge; - // Extract any prefix consisting of intellectual property symbols. - NSScanner *scanner = [NSScanner scannerWithString:info.title.string]; - NSCharacterSet *symbolSet = [NSCharacterSet characterSetWithCharactersInString:@"©℗®℠™ &"]; - NSString *symbol; - [scanner scanCharactersFromSet:symbolSet intoString:&symbol]; + // Extract any prefix consisting of intellectual property symbols. + NSScanner *scanner = [NSScanner scannerWithString:info.title.string]; + NSCharacterSet *symbolSet = [NSCharacterSet characterSetWithCharactersInString:@"©℗®℠™ &"]; + NSString *symbol; + [scanner scanCharactersFromSet:symbolSet intoString:&symbol]; - // Remove the underline from the symbol for aesthetic reasons. - NSMutableAttributedString *title = info.title.mutableCopy; - [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, symbol.length)]; + // Remove the underline from the symbol for aesthetic reasons. + NSMutableAttributedString *title = info.title.mutableCopy; + [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, symbol.length)]; - self.attributedTitle = title; - [self sizeToFit]; + self.attributedTitle = title; + [self sizeToFit]; - _URL = info.URL; - if (_URL) { - self.toolTip = _URL.absoluteString; - } - - self.target = self; - self.action = @selector(openURL:); + _URL = info.URL; + if (_URL) { + self.toolTip = _URL.absoluteString; } - return self; + + self.target = self; + self.action = @selector(openURL:); + } + return self; } - (BOOL)wantsLayer { - return YES; + return YES; } - (void)resetCursorRects { - if (self.URL) { - // The whole button gets a pointing hand cursor, just like a hyperlink. - [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]]; - } + if (self.URL) { + // The whole button gets a pointing hand cursor, just like a hyperlink. + [self addCursorRect:self.bounds cursor:[NSCursor pointingHandCursor]]; + } } - (IBAction)openURL:(__unused id)sender { - if (self.URL) { - [[NSWorkspace sharedWorkspace] openURL:self.URL]; - } + if (self.URL) { + [[NSWorkspace sharedWorkspace] openURL:self.URL]; + } } @end diff --git a/platform/macos/src/MLNMapView+IBAdditions.mm b/platform/macos/src/MLNMapView+IBAdditions.mm index bf8bcfab0..2888d070e 100644 --- a/platform/macos/src/MLNMapView+IBAdditions.mm +++ b/platform/macos/src/MLNMapView+IBAdditions.mm @@ -7,114 +7,114 @@ @implementation MLNMapView (IBAdditions) + (NSSet *)keyPathsForValuesAffectingStyleURL__ { - return [NSSet setWithObject:@"styleURL"]; + return [NSSet setWithObject:@"styleURL"]; } - (nullable NSString *)styleURL__ { - return self.styleURL.absoluteString; + return self.styleURL.absoluteString; } - (void)setStyleURL__:(nullable NSString *)URLString { - URLString = [URLString stringByTrimmingCharactersInSet: - [NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil; - if (URLString.length && !url) { - [NSException raise:MLNInvalidStyleURLException - format:@"“%@” is not a valid style URL.", URLString]; - } - self.styleURL = url; + URLString = + [URLString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSURL *url = URLString.length ? [NSURL URLWithString:URLString] : nil; + if (URLString.length && !url) { + [NSException raise:MLNInvalidStyleURLException + format:@"“%@” is not a valid style URL.", URLString]; + } + self.styleURL = url; } + (NSSet *)keyPathsForValuesAffectingLatitude { - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)latitude { - return self.centerCoordinate.latitude; + return self.centerCoordinate.latitude; } - (void)setLatitude:(double)latitude { - if (!isnan(self.pendingLongitude)) { - // With both components present, set the real center coordinate and - // forget the pending parts. - self.centerCoordinate = CLLocationCoordinate2DMake(latitude, self.pendingLongitude); - self.pendingLatitude = NAN; - self.pendingLongitude = NAN; - } else { - // Not enough info to make a valid center coordinate yet. Stash this - // latitude away until the longitude is set too. - self.pendingLatitude = latitude; - } + if (!isnan(self.pendingLongitude)) { + // With both components present, set the real center coordinate and + // forget the pending parts. + self.centerCoordinate = CLLocationCoordinate2DMake(latitude, self.pendingLongitude); + self.pendingLatitude = NAN; + self.pendingLongitude = NAN; + } else { + // Not enough info to make a valid center coordinate yet. Stash this + // latitude away until the longitude is set too. + self.pendingLatitude = latitude; + } } + (NSSet *)keyPathsForValuesAffectingLongitude { - return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; + return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil]; } - (double)longitude { - return self.centerCoordinate.longitude; + return self.centerCoordinate.longitude; } - (void)setLongitude:(double)longitude { - if (!isnan(self.pendingLatitude)) { - // With both components present, set the real center coordinate and - // forget the pending parts. - self.centerCoordinate = CLLocationCoordinate2DMake(self.pendingLatitude, longitude); - self.pendingLatitude = NAN; - self.pendingLongitude = NAN; - } else { - // Not enough info to make a valid center coordinate yet. Stash this - // longitude away until the latitude is set too. - self.pendingLongitude = longitude; - } + if (!isnan(self.pendingLatitude)) { + // With both components present, set the real center coordinate and + // forget the pending parts. + self.centerCoordinate = CLLocationCoordinate2DMake(self.pendingLatitude, longitude); + self.pendingLatitude = NAN; + self.pendingLongitude = NAN; + } else { + // Not enough info to make a valid center coordinate yet. Stash this + // longitude away until the latitude is set too. + self.pendingLongitude = longitude; + } } + (NSSet *)keyPathsForValuesAffectingAllowsZooming { - return [NSSet setWithObject:@"zoomEnabled"]; + return [NSSet setWithObject:@"zoomEnabled"]; } - (BOOL)allowsZooming { - return self.zoomEnabled; + return self.zoomEnabled; } - (void)setAllowsZooming:(BOOL)allowsZooming { - self.zoomEnabled = allowsZooming; + self.zoomEnabled = allowsZooming; } + (NSSet *)keyPathsForValuesAffectingAllowsScrolling { - return [NSSet setWithObject:@"scrollEnabled"]; + return [NSSet setWithObject:@"scrollEnabled"]; } - (BOOL)allowsScrolling { - return self.scrollEnabled; + return self.scrollEnabled; } - (void)setAllowsScrolling:(BOOL)allowsScrolling { - self.scrollEnabled = allowsScrolling; + self.scrollEnabled = allowsScrolling; } + (NSSet *)keyPathsForValuesAffectingAllowsRotating { - return [NSSet setWithObject:@"rotateEnabled"]; + return [NSSet setWithObject:@"rotateEnabled"]; } - (BOOL)allowsRotating { - return self.rotateEnabled; + return self.rotateEnabled; } - (void)setAllowsRotating:(BOOL)allowsRotating { - self.rotateEnabled = allowsRotating; + self.rotateEnabled = allowsRotating; } + (NSSet *)keyPathsForValuesAffectingAllowsTilting { - return [NSSet setWithObject:@"pitchEnabled"]; + return [NSSet setWithObject:@"pitchEnabled"]; } - (BOOL)allowsTilting { - return self.pitchEnabled; + return self.pitchEnabled; } - (void)setAllowsTilting:(BOOL)allowsTilting { - self.pitchEnabled = allowsTilting; + self.pitchEnabled = allowsTilting; } @end diff --git a/platform/macos/src/MLNMapView+Impl.h b/platform/macos/src/MLNMapView+Impl.h index d314baf9c..c7215f240 100644 --- a/platform/macos/src/MLNMapView+Impl.h +++ b/platform/macos/src/MLNMapView+Impl.h @@ -9,7 +9,7 @@ typedef struct _CGLContextObject* CGLContextObj; class MLNMapViewImpl : public mbgl::MapObserver { - public: +public: static std::unique_ptr Create(MLNMapView*); MLNMapViewImpl(MLNMapView*); @@ -61,7 +61,7 @@ class MLNMapViewImpl : public mbgl::MapObserver { void onSpriteError(const std::optional&, std::exception_ptr) override; void onSpriteRequested(const std::optional&) override; - protected: +protected: /// Cocoa map view that this adapter bridges to. __weak MLNMapView* mapView = nullptr; }; diff --git a/platform/macos/src/MLNMapView+Impl.mm b/platform/macos/src/MLNMapView+Impl.mm index 0dacb5409..6828cba38 100644 --- a/platform/macos/src/MLNMapView+Impl.mm +++ b/platform/macos/src/MLNMapView+Impl.mm @@ -1,10 +1,10 @@ -#import "MLNMapView+Impl.h" #import +#import "MLNMapView+Impl.h" #import "NSBundle+MLNAdditions.h" #if MLN_RENDER_BACKEND_METAL #import "MLNMapView+Metal.h" -#else // MLN_RENDER_BACKEND_OPENGL +#else // MLN_RENDER_BACKEND_OPENGL #import "MLNMapView+OpenGL.h" #endif @@ -13,188 +13,199 @@ std::unique_ptr MLNMapViewImpl::Create(MLNMapView* nativeView) { #if MLN_RENDER_BACKEND_METAL - return std::make_unique(nativeView); -#else // MLN_RENDER_BACKEND_OPENGL - return std::make_unique(nativeView); + return std::make_unique(nativeView); +#else // MLN_RENDER_BACKEND_OPENGL + return std::make_unique(nativeView); #endif } -MLNMapViewImpl::MLNMapViewImpl(MLNMapView* nativeView_) : mapView(nativeView_) { -} +MLNMapViewImpl::MLNMapViewImpl(MLNMapView* nativeView_) : mapView(nativeView_) {} -void MLNMapViewImpl::render() { - [mapView renderSync]; -} +void MLNMapViewImpl::render() { [mapView renderSync]; } void MLNMapViewImpl::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraWillChangeAnimated:animated]; + bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; + [mapView cameraWillChangeAnimated:animated]; } -void MLNMapViewImpl::onCameraIsChanging() { - [mapView cameraIsChanging]; -} +void MLNMapViewImpl::onCameraIsChanging() { [mapView cameraIsChanging]; } void MLNMapViewImpl::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) { - bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; - [mapView cameraDidChangeAnimated:animated]; + bool animated = mode == mbgl::MapObserver::CameraChangeMode::Animated; + [mapView cameraDidChangeAnimated:animated]; } -void MLNMapViewImpl::onWillStartLoadingMap() { - [mapView mapViewWillStartLoadingMap]; -} +void MLNMapViewImpl::onWillStartLoadingMap() { [mapView mapViewWillStartLoadingMap]; } -void MLNMapViewImpl::onDidFinishLoadingMap() { - [mapView mapViewDidFinishLoadingMap]; -} +void MLNMapViewImpl::onDidFinishLoadingMap() { [mapView mapViewDidFinishLoadingMap]; } void MLNMapViewImpl::onDidFailLoadingMap(mbgl::MapLoadError mapError, const std::string& what) { - NSString *description; - MLNErrorCode code; - switch (mapError) { - case mbgl::MapLoadError::StyleParseError: - code = MLNErrorCodeParseStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"PARSE_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style is corrupted.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::StyleLoadError: - code = MLNErrorCodeLoadStyleFailed; - description = NSLocalizedStringWithDefaultValue(@"LOAD_STYLE_FAILED_DESC", nil, nil, @"The map failed to load because the style can't be loaded.", @"User-friendly error description"); - break; - case mbgl::MapLoadError::NotFoundError: - code = MLNErrorCodeNotFound; - description = NSLocalizedStringWithDefaultValue(@"STYLE_NOT_FOUND_DESC", nil, nil, @"The map failed to load because the style can’t be found or is incompatible.", @"User-friendly error description"); - break; - default: - code = MLNErrorCodeUnknown; - description = NSLocalizedStringWithDefaultValue(@"LOAD_MAP_FAILED_DESC", nil, nil, @"The map failed to load because an unknown error occurred.", @"User-friendly error description"); - } - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: description, - NSLocalizedFailureReasonErrorKey: @(what.c_str()), - }; - NSError *error = [NSError errorWithDomain:MLNErrorDomain code:code userInfo:userInfo]; - [mapView mapViewDidFailLoadingMapWithError:error]; + NSString* description; + MLNErrorCode code; + switch (mapError) { + case mbgl::MapLoadError::StyleParseError: + code = MLNErrorCodeParseStyleFailed; + description = NSLocalizedStringWithDefaultValue( + @"PARSE_STYLE_FAILED_DESC", nil, nil, + @"The map failed to load because the style is corrupted.", + @"User-friendly error description"); + break; + case mbgl::MapLoadError::StyleLoadError: + code = MLNErrorCodeLoadStyleFailed; + description = NSLocalizedStringWithDefaultValue( + @"LOAD_STYLE_FAILED_DESC", nil, nil, + @"The map failed to load because the style can't be loaded.", + @"User-friendly error description"); + break; + case mbgl::MapLoadError::NotFoundError: + code = MLNErrorCodeNotFound; + description = NSLocalizedStringWithDefaultValue( + @"STYLE_NOT_FOUND_DESC", nil, nil, + @"The map failed to load because the style can’t be found or is incompatible.", + @"User-friendly error description"); + break; + default: + code = MLNErrorCodeUnknown; + description = NSLocalizedStringWithDefaultValue( + @"LOAD_MAP_FAILED_DESC", nil, nil, + @"The map failed to load because an unknown error occurred.", + @"User-friendly error description"); + } + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : description, + NSLocalizedFailureReasonErrorKey : @(what.c_str()), + }; + NSError* error = [NSError errorWithDomain:MLNErrorDomain code:code userInfo:userInfo]; + [mapView mapViewDidFailLoadingMapWithError:error]; } -void MLNMapViewImpl::onWillStartRenderingFrame() { - [mapView mapViewWillStartRenderingFrame]; -} +void MLNMapViewImpl::onWillStartRenderingFrame() { [mapView mapViewWillStartRenderingFrame]; } void MLNMapViewImpl::onDidFinishRenderingFrame(const mbgl::MapObserver::RenderFrameStatus& status) { - bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered renderingStats:status.renderingStats]; + bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; + [mapView mapViewDidFinishRenderingFrameFullyRendered:fullyRendered + renderingStats:status.renderingStats]; } -void MLNMapViewImpl::onWillStartRenderingMap() { - [mapView mapViewWillStartRenderingMap]; -} +void MLNMapViewImpl::onWillStartRenderingMap() { [mapView mapViewWillStartRenderingMap]; } void MLNMapViewImpl::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) { - bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; - [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; + bool fullyRendered = mode == mbgl::MapObserver::RenderMode::Full; + [mapView mapViewDidFinishRenderingMapFullyRendered:fullyRendered]; } -void MLNMapViewImpl::onDidBecomeIdle() { - [mapView mapViewDidBecomeIdle]; -} +void MLNMapViewImpl::onDidBecomeIdle() { [mapView mapViewDidBecomeIdle]; } -void MLNMapViewImpl::onDidFinishLoadingStyle() { - [mapView mapViewDidFinishLoadingStyle]; -} +void MLNMapViewImpl::onDidFinishLoadingStyle() { [mapView mapViewDidFinishLoadingStyle]; } void MLNMapViewImpl::onSourceChanged(mbgl::style::Source& source) { - NSString *identifier = @(source.getID().c_str()); - MLNSource * nativeSource = [mapView.style sourceWithIdentifier:identifier]; - [mapView sourceDidChange:nativeSource]; + NSString* identifier = @(source.getID().c_str()); + MLNSource* nativeSource = [mapView.style sourceWithIdentifier:identifier]; + [mapView sourceDidChange:nativeSource]; } -bool MLNMapViewImpl::onCanRemoveUnusedStyleImage(const std::string &imageIdentifier) { - NSString *imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; - return [mapView shouldRemoveStyleImage:imageName]; +bool MLNMapViewImpl::onCanRemoveUnusedStyleImage(const std::string& imageIdentifier) { + NSString* imageName = [NSString stringWithUTF8String:imageIdentifier.c_str()]; + return [mapView shouldRemoveStyleImage:imageName]; } -void MLNMapViewImpl::onRegisterShaders(mbgl::gfx::ShaderRegistry& shaders) { +void MLNMapViewImpl::onRegisterShaders(mbgl::gfx::ShaderRegistry& shaders) {} +void MLNMapViewImpl::onPreCompileShader(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderWillCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onPreCompileShader(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderWillCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onPostCompileShader(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderDidCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onPostCompileShader(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderDidCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onShaderCompileFailed(mbgl::shaders::BuiltIn shaderID, + mbgl::gfx::Backend::Type backend, + const std::string& defines) { + NSString* definesCopy = [NSString stringWithUTF8String:defines.c_str()]; + [mapView shaderDidFailCompile:static_cast(shaderID) + backend:static_cast(backend) + defines:definesCopy]; } -void MLNMapViewImpl::onShaderCompileFailed(mbgl::shaders::BuiltIn shaderID, mbgl::gfx::Backend::Type backend, const std::string& defines) { - NSString *definesCopy = [NSString stringWithUTF8String:defines.c_str()]; - [mapView shaderDidFailCompile:static_cast(shaderID) backend:static_cast(backend) defines:definesCopy]; +void MLNMapViewImpl::onGlyphsLoaded(const mbgl::FontStack& fontStack, + const mbgl::GlyphRange& range) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); + + [mapView glyphsDidLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsLoaded(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); +void MLNMapViewImpl::onGlyphsError(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range, + std::exception_ptr error) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); - [mapView glyphsDidLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; + [mapView glyphsDidError:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsError(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range, std::exception_ptr error) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); +void MLNMapViewImpl::onGlyphsRequested(const mbgl::FontStack& fontStack, + const mbgl::GlyphRange& range) { + NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; + std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { + [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; + }); - [mapView glyphsDidError:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; + [mapView glyphsWillLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; } -void MLNMapViewImpl::onGlyphsRequested(const mbgl::FontStack& fontStack, const mbgl::GlyphRange& range) { - NSMutableArray* fontStackCopy = [[NSMutableArray alloc] init]; - std::for_each(fontStack.begin(), fontStack.end(), ^(const std::string& str) { - [fontStackCopy addObject:[NSString stringWithUTF8String:str.c_str()]]; - }); - - [mapView glyphsWillLoad:fontStackCopy range:NSMakeRange(range.first, range.second - range.first)]; -} - -void MLNMapViewImpl::onTileAction(mbgl::TileOperation operation, const mbgl::OverscaledTileID& tile, const std::string& sourceID) { - [mapView tileDidTriggerAction:MLNTileOperation(static_cast(operation)) - x:tile.canonical.x - y:tile.canonical.y - z:tile.canonical.z - wrap:tile.wrap - overscaledZ:tile.overscaledZ - sourceID:[NSString stringWithUTF8String:sourceID.c_str()]]; +void MLNMapViewImpl::onTileAction(mbgl::TileOperation operation, const mbgl::OverscaledTileID& tile, + const std::string& sourceID) { + [mapView tileDidTriggerAction:MLNTileOperation(static_cast(operation)) + x:tile.canonical.x + y:tile.canonical.y + z:tile.canonical.z + wrap:tile.wrap + overscaledZ:tile.overscaledZ + sourceID:[NSString stringWithUTF8String:sourceID.c_str()]]; } void MLNMapViewImpl::onSpriteLoaded(const std::optional& spriteID) { - if (!spriteID.has_value()) { - [mapView spriteDidLoad:nil url:nil]; - return; - } + if (!spriteID.has_value()) { + [mapView spriteDidLoad:nil url:nil]; + return; + } - [mapView spriteDidLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteDidLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } -void MLNMapViewImpl::onSpriteError(const std::optional& spriteID, std::exception_ptr error) { - if (!spriteID.has_value()) { - [mapView spriteDidError:nil url:nil]; - return; - } +void MLNMapViewImpl::onSpriteError(const std::optional& spriteID, + std::exception_ptr error) { + if (!spriteID.has_value()) { + [mapView spriteDidError:nil url:nil]; + return; + } - [mapView spriteDidError:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteDidError:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } void MLNMapViewImpl::onSpriteRequested(const std::optional& spriteID) { - if (!spriteID.has_value()) { - [mapView spriteWillLoad:nil url:nil]; - return; - } + if (!spriteID.has_value()) { + [mapView spriteWillLoad:nil url:nil]; + return; + } - [mapView spriteWillLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] - url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; + [mapView spriteWillLoad:[NSString stringWithUTF8String:spriteID.value().id.c_str()] + url:[NSString stringWithUTF8String:spriteID.value().spriteURL.c_str()]]; } diff --git a/platform/macos/src/MLNMapView+Metal.h b/platform/macos/src/MLNMapView+Metal.h index 55a6adb43..2b0e569f1 100644 --- a/platform/macos/src/MLNMapView+Metal.h +++ b/platform/macos/src/MLNMapView+Metal.h @@ -10,34 +10,34 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl, public mbgl::mtl::RendererBackend, public mbgl::gfx::Renderable { - public: +public: MLNMapViewMetalImpl(MLNMapView*); ~MLNMapViewMetalImpl() override; - public: +public: void restoreFramebufferBinding(); // Implementation of mbgl::gfx::RendererBackend - public: +public: mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; } - private: +private: void activate() override; void deactivate() override; // End implementation of mbgl::gfx::RendererBackend // Implementation of mbgl::gl::RendererBackend - public: +public: void updateAssumedState() override; // End implementation of mbgl::gl::Rendererbackend // Implementation of MLNMapViewImpl - public: +public: mbgl::gfx::RendererBackend& getRendererBackend() override { return *this; } mbgl::PremultipliedImage readStillImage() override; MLNBackendResource* getObject() override; - private: +private: bool presentsWithTransaction = false; }; diff --git a/platform/macos/src/MLNMapView+Metal.mm b/platform/macos/src/MLNMapView+Metal.mm index 8513803c6..f77e6c7f7 100644 --- a/platform/macos/src/MLNMapView+Metal.mm +++ b/platform/macos/src/MLNMapView+Metal.mm @@ -4,8 +4,8 @@ #import -#import #import +#import #import #import @@ -14,158 +14,156 @@ @end @implementation MLNMapViewImplDelegate { - MLNMapViewMetalImpl* _impl; + MLNMapViewMetalImpl* _impl; } - (instancetype)initWithImpl:(MLNMapViewMetalImpl*)impl { - if (self = [super init]) { - _impl = impl; - } - return self; + if (self = [super init]) { + _impl = impl; + } + return self; } -- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size {} +- (void)mtkView:(MTKView*)view drawableSizeWillChange:(CGSize)size { +} -- (void)drawInMTKView:(MTKView *)view { - _impl->render(); +- (void)drawInMTKView:(MTKView*)view { + _impl->render(); } @end class MLNMapViewMetalRenderableResource final : public mbgl::mtl::RenderableResource { public: - MLNMapViewMetalRenderableResource(MLNMapViewMetalImpl& backend_) - : backend(backend_), - delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]) { + MLNMapViewMetalRenderableResource(MLNMapViewMetalImpl& backend_) + : backend(backend_), delegate([[MLNMapViewImplDelegate alloc] initWithImpl:&backend]) {} + + void bind() override { + if (!commandQueue) { + commandQueue = [mtlView.device newCommandQueue]; } - void bind() override { - if (!commandQueue) { - commandQueue = [mtlView.device newCommandQueue]; - } - - if (!commandBuffer) { - commandBuffer = [commandQueue commandBuffer]; - commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); - } + if (!commandBuffer) { + commandBuffer = [commandQueue commandBuffer]; + commandBufferPtr = NS::RetainPtr((__bridge MTL::CommandBuffer*)commandBuffer); } + } - const mbgl::mtl::RendererBackend& getBackend() const override { return backend; } + const mbgl::mtl::RendererBackend& getBackend() const override { return backend; } - const mbgl::mtl::MTLCommandBufferPtr& getCommandBuffer() const override { - return commandBufferPtr; + const mbgl::mtl::MTLCommandBufferPtr& getCommandBuffer() const override { + return commandBufferPtr; + } + + virtual mbgl::mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { + // Create from render pass descriptor? + return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); + } + + const mbgl::mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { + if (!cachedRenderPassDescriptor) { + auto* mtlDesc = mtlView.currentRenderPassDescriptor; + cachedRenderPassDescriptor = NS::RetainPtr((__bridge MTL::RenderPassDescriptor*)mtlDesc); } + return cachedRenderPassDescriptor; + } - virtual mbgl::mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { - // Create from render pass descriptor? - return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); - } + void swap() override { + id currentDrawable = [mtlView currentDrawable]; + [commandBuffer presentDrawable:currentDrawable]; + [commandBuffer commit]; - const mbgl::mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override { - if (!cachedRenderPassDescriptor) { - auto* mtlDesc = mtlView.currentRenderPassDescriptor; - cachedRenderPassDescriptor = NS::RetainPtr((__bridge MTL::RenderPassDescriptor*)mtlDesc); - } - return cachedRenderPassDescriptor; - } + // Un-comment for synchronous, which can help troubleshoot rendering problems, + // particularly those related to resource tracking and multiple queued buffers. + //[commandBuffer waitUntilCompleted]; - void swap() override { - id currentDrawable = [mtlView currentDrawable]; - [commandBuffer presentDrawable:currentDrawable]; - [commandBuffer commit]; + commandBuffer = nil; + commandBufferPtr.reset(); - // Un-comment for synchronous, which can help troubleshoot rendering problems, - // particularly those related to resource tracking and multiple queued buffers. - //[commandBuffer waitUntilCompleted]; + cachedRenderPassDescriptor.reset(); + } - commandBuffer = nil; - commandBufferPtr.reset(); - - cachedRenderPassDescriptor.reset(); - } - - mbgl::Size framebufferSize() { - assert(mtlView); - return { static_cast(mtlView.drawableSize.width), - static_cast(mtlView.drawableSize.height) }; - } + mbgl::Size framebufferSize() { + assert(mtlView); + return {static_cast(mtlView.drawableSize.width), + static_cast(mtlView.drawableSize.height)}; + } private: - MLNMapViewMetalImpl& backend; - mbgl::mtl::MTLCommandBufferPtr commandBufferPtr; - mutable mbgl::mtl::MTLRenderPassDescriptorPtr cachedRenderPassDescriptor; + MLNMapViewMetalImpl& backend; + mbgl::mtl::MTLCommandBufferPtr commandBufferPtr; + mutable mbgl::mtl::MTLRenderPassDescriptorPtr cachedRenderPassDescriptor; public: - MLNMapViewImplDelegate* delegate = nil; - MTKView *mtlView = nil; - id commandBuffer; - id commandQueue; + MLNMapViewImplDelegate* delegate = nil; + MTKView* mtlView = nil; + id commandBuffer; + id commandQueue; - // We count how often the context was activated/deactivated so that we can truly deactivate it - // after the activation count drops to 0. - NSUInteger activationCount = 0; + // We count how often the context was activated/deactivated so that we can truly deactivate it + // after the activation count drops to 0. + NSUInteger activationCount = 0; }; MLNMapViewMetalImpl::MLNMapViewMetalImpl(MLNMapView* nativeView_) : MLNMapViewImpl(nativeView_), mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique), - mbgl::gfx::Renderable({ 0, 0 }, std::make_unique(*this)) { + mbgl::gfx::Renderable({0, 0}, std::make_unique(*this)) { + auto& resource = getResource(); + if (resource.mtlView) { + return; + } - auto& resource = getResource(); - if (resource.mtlView) { - return; - } + id device = (__bridge id)resource.getBackend().getDevice().get(); - id device = (__bridge id)resource.getBackend().getDevice().get(); + resource.mtlView = [[MTKView alloc] initWithFrame:mapView.bounds device:device]; + resource.mtlView.delegate = resource.delegate; + resource.mtlView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + resource.mtlView.colorPixelFormat = MTLPixelFormatBGRA8Unorm; + resource.mtlView.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; + resource.mtlView.layer.opaque = mapView.opaque; + resource.mtlView.enableSetNeedsDisplay = NO; + CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer); + metalLayer.presentsWithTransaction = presentsWithTransaction; - resource.mtlView = [[MTKView alloc] initWithFrame:mapView.bounds device:device]; - resource.mtlView.delegate = resource.delegate; - resource.mtlView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - resource.mtlView.colorPixelFormat = MTLPixelFormatBGRA8Unorm; - resource.mtlView.depthStencilPixelFormat = MTLPixelFormatDepth32Float_Stencil8; - resource.mtlView.layer.opaque = mapView.opaque; - resource.mtlView.enableSetNeedsDisplay = NO; - CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(resource.mtlView.layer, CAMetalLayer); - metalLayer.presentsWithTransaction = presentsWithTransaction; - - [mapView addSubview:resource.mtlView positioned:NSWindowBelow relativeTo:nil]; + [mapView addSubview:resource.mtlView positioned:NSWindowBelow relativeTo:nil]; } MLNMapViewMetalImpl::~MLNMapViewMetalImpl() = default; void MLNMapViewMetalImpl::activate() { - auto& resource = getResource(); - if (resource.activationCount++) { - return; - } + auto& resource = getResource(); + if (resource.activationCount++) { + return; + } } void MLNMapViewMetalImpl::deactivate() { - auto& resource = getResource(); - if (--resource.activationCount) { - return; - } + auto& resource = getResource(); + if (--resource.activationCount) { + return; + } } /// This function is called before we start rendering, when iOS invokes our rendering method. /// iOS already sets the correct framebuffer and viewport for us, so we need to update the /// context state with the anticipated values. void MLNMapViewMetalImpl::updateAssumedState() { - auto& resource = getResource(); - assumeFramebufferBinding(ImplicitFramebufferBinding); - assumeViewport(0, 0, resource.framebufferSize()); + auto& resource = getResource(); + assumeFramebufferBinding(ImplicitFramebufferBinding); + assumeViewport(0, 0, resource.framebufferSize()); } mbgl::PremultipliedImage MLNMapViewMetalImpl::readStillImage() { - // return readFramebuffer(mapView.framebufferSize); // TODO: RendererBackend::readFramebuffer - return {}; + // return readFramebuffer(mapView.framebufferSize); // TODO: RendererBackend::readFramebuffer + return {}; } MLNBackendResource* MLNMapViewMetalImpl::getObject() { - auto& resource = getResource(); + auto& resource = getResource(); - return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView - device:resource.mtlView.device - renderPassDescriptor:resource.mtlView.currentRenderPassDescriptor - commandBuffer:resource.commandBuffer]; + return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView + device:resource.mtlView.device + renderPassDescriptor:resource.mtlView.currentRenderPassDescriptor + commandBuffer:resource.commandBuffer]; } diff --git a/platform/macos/src/MLNMapView+OpenGL.mm b/platform/macos/src/MLNMapView+OpenGL.mm index de747afbc..434cd9c22 100644 --- a/platform/macos/src/MLNMapView+OpenGL.mm +++ b/platform/macos/src/MLNMapView+OpenGL.mm @@ -7,22 +7,19 @@ class MLNMapViewOpenGLRenderableResource final : public mbgl::gl::RenderableResource { public: - MLNMapViewOpenGLRenderableResource(MLNMapViewOpenGLImpl& backend_) : backend(backend_) { - } + MLNMapViewOpenGLRenderableResource(MLNMapViewOpenGLImpl& backend_) : backend(backend_) {} - void bind() override { - backend.restoreFramebufferBinding(); - } + void bind() override { backend.restoreFramebufferBinding(); } private: - MLNMapViewOpenGLImpl& backend; + MLNMapViewOpenGLImpl& backend; public: - // The current framebuffer of the NSOpenGLLayer we are painting to. - GLint fbo = 0; + // The current framebuffer of the NSOpenGLLayer we are painting to. + GLint fbo = 0; - // The reference counted count of activation calls - NSUInteger activationCount = 0; + // The reference counted count of activation calls + NSUInteger activationCount = 0; }; MLNMapViewOpenGLImpl::MLNMapViewOpenGLImpl(MLNMapView* nativeView_) @@ -30,60 +27,58 @@ MLNMapViewOpenGLImpl::MLNMapViewOpenGLImpl(MLNMapView* nativeView_) mbgl::gl::RendererBackend(mbgl::gfx::ContextMode::Unique), mbgl::gfx::Renderable(mapView.framebufferSize, std::make_unique(*this)) { - - // Install the OpenGL layer. Interface Builder’s synchronous drawing means - // we can’t display a map, so don’t even bother to have a map layer. - mapView.layer = - mapView.isTargetingInterfaceBuilder ? [CALayer layer] : [MLNOpenGLLayer layer]; + // Install the OpenGL layer. Interface Builder’s synchronous drawing means + // we can’t display a map, so don’t even bother to have a map layer. + mapView.layer = mapView.isTargetingInterfaceBuilder ? [CALayer layer] : [MLNOpenGLLayer layer]; } mbgl::gl::ProcAddress MLNMapViewOpenGLImpl::getExtensionFunctionPointer(const char* name) { - static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); - if (!framework) { - throw std::runtime_error("Failed to load OpenGL framework."); - } + static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); + if (!framework) { + throw std::runtime_error("Failed to load OpenGL framework."); + } - return reinterpret_cast(CFBundleGetFunctionPointerForName( - framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); + return reinterpret_cast(CFBundleGetFunctionPointerForName( + framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); } void MLNMapViewOpenGLImpl::activate() { - auto& resource = getResource(); - if (resource.activationCount++) { - return; - } + auto& resource = getResource(); + if (resource.activationCount++) { + return; + } - MLNOpenGLLayer* layer = (MLNOpenGLLayer*)mapView.layer; - [layer.openGLContext makeCurrentContext]; + MLNOpenGLLayer* layer = (MLNOpenGLLayer*)mapView.layer; + [layer.openGLContext makeCurrentContext]; } void MLNMapViewOpenGLImpl::deactivate() { - auto& resource = getResource(); - if (--resource.activationCount) { - return; - } + auto& resource = getResource(); + if (--resource.activationCount) { + return; + } - [NSOpenGLContext clearCurrentContext]; + [NSOpenGLContext clearCurrentContext]; } void MLNMapViewOpenGLImpl::updateAssumedState() { - auto& resource = getResource(); - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &resource.fbo); - assumeFramebufferBinding(resource.fbo); - assumeViewport(0, 0, mapView.framebufferSize); + auto& resource = getResource(); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &resource.fbo); + assumeFramebufferBinding(resource.fbo); + assumeViewport(0, 0, mapView.framebufferSize); } void MLNMapViewOpenGLImpl::restoreFramebufferBinding() { - auto& resource = getResource(); - setFramebufferBinding(resource.fbo); - setViewport(0, 0, mapView.framebufferSize); + auto& resource = getResource(); + setFramebufferBinding(resource.fbo); + setViewport(0, 0, mapView.framebufferSize); } mbgl::PremultipliedImage MLNMapViewOpenGLImpl::readStillImage() { - return readFramebuffer(mapView.framebufferSize); + return readFramebuffer(mapView.framebufferSize); } CGLContextObj MLNMapViewOpenGLImpl::getCGLContextObj() { - MLNOpenGLLayer* layer = (MLNOpenGLLayer*)mapView.layer; - return layer.openGLContext.CGLContextObj; + MLNOpenGLLayer* layer = (MLNOpenGLLayer*)mapView.layer; + return layer.openGLContext.CGLContextObj; } diff --git a/platform/macos/src/MLNMapView.mm b/platform/macos/src/MLNMapView.mm index cb5afa550..4726b1f8c 100644 --- a/platform/macos/src/MLNMapView.mm +++ b/platform/macos/src/MLNMapView.mm @@ -2,9 +2,9 @@ #import "MLNAttributionButton.h" #import "MLNCompassCell.h" -#import "MLNStyle.h" -#import "MLNRendererFrontend.h" #import "MLNRendererConfiguration.h" +#import "MLNRendererFrontend.h" +#import "MLNStyle.h" #import "MLNAnnotationImage_Private.h" #import "MLNAttributionInfo_Private.h" @@ -13,57 +13,57 @@ #import "MLNGeometry_Private.h" #import "MLNMultiPoint_Private.h" #import "MLNOfflineStorage_Private.h" -#import "MLNStyle_Private.h" #import "MLNShape_Private.h" +#import "MLNStyle_Private.h" -#import "MLNSettings.h" +#import "MLNAnnotationImage.h" +#import "MLNImageSource.h" #import "MLNMapCamera.h" +#import "MLNMapViewDelegate.h" #import "MLNPolygon.h" #import "MLNPolyline.h" -#import "MLNAnnotationImage.h" -#import "MLNMapViewDelegate.h" -#import "MLNImageSource.h" +#import "MLNSettings.h" -#import -#import -#import #import #import -#import +#import +#import +#import #import #import #import -#import +#import +#import #import +#import #import #import -#import #import +#import #import #import -#import #import #import #import -#import "MLNMapView+Impl.h" -#import "MLNBackendResource.h" -#import "NSBundle+MLNAdditions.h" -#import "NSDate+MLNAdditions.h" -#import "NSProcessInfo+MLNAdditions.h" -#import "NSException+MLNAdditions.h" -#import "NSString+MLNAdditions.h" -#import "NSURL+MLNAdditions.h" -#import "NSColor+MLNAdditions.h" -#import "NSImage+MLNAdditions.h" -#import "NSPredicate+MLNPrivateAdditions.h" -#import "MLNNetworkConfiguration_Private.h" -#import "MLNLoggingConfiguration_Private.h" -#import "MLNReachability.h" #import "MLNActionJournalOptions_Private.h" +#import "MLNBackendResource.h" +#import "MLNLoggingConfiguration_Private.h" +#import "MLNMapView+Impl.h" +#import "MLNNetworkConfiguration_Private.h" +#import "MLNReachability.h" #import "MLNRenderingStats_Private.h" #import "MLNSettings_Private.h" +#import "NSBundle+MLNAdditions.h" +#import "NSColor+MLNAdditions.h" +#import "NSDate+MLNAdditions.h" +#import "NSException+MLNAdditions.h" +#import "NSImage+MLNAdditions.h" +#import "NSPredicate+MLNPrivateAdditions.h" +#import "NSProcessInfo+MLNAdditions.h" +#import "NSString+MLNAdditions.h" +#import "NSURL+MLNAdditions.h" #import @@ -84,15 +84,16 @@ const CGFloat MLNKeyPanningIncrement = 150; /// Degrees that a single press of the rotation keyboard shortcut rotates the map by. const CLLocationDegrees MLNKeyRotationIncrement = 25; -/// Key for the user default that, when true, causes the map view to zoom in and out on scroll wheel events. -NSString * const MLNScrollWheelZoomsMapViewDefaultKey = @"MLNScrollWheelZoomsMapView"; +/// Key for the user default that, when true, causes the map view to zoom in and out on scroll wheel +/// events. +NSString *const MLNScrollWheelZoomsMapViewDefaultKey = @"MLNScrollWheelZoomsMapView"; /// Reuse identifier and file name of the default point annotation image. -static NSString * const MLNDefaultStyleMarkerSymbolName = @"default_marker"; +static NSString *const MLNDefaultStyleMarkerSymbolName = @"default_marker"; /// Prefix that denotes a sprite installed by MLNMapView, to avoid collisions /// with style-defined sprites. -static NSString * const MLNAnnotationSpritePrefix = @"org.maplibre.sprites."; +static NSString *const MLNAnnotationSpritePrefix = @"org.maplibre.sprites."; /// Slop area around the hit testing point, allowing for imprecise annotation selection. const CGFloat MLNAnnotationImagePaddingForHitTest = 4; @@ -100,8 +101,10 @@ const CGFloat MLNAnnotationImagePaddingForHitTest = 4; /// Distance from the callout’s anchor point to the annotation it points to. const CGFloat MLNAnnotationImagePaddingForCallout = 4; -/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by being selected) -const NSEdgeInsets MLNMapViewOffscreenAnnotationPadding = NSEdgeInsetsMake(-30.0f, -30.0f, -30.0f, -30.0f); +/// Padding to edge of view that an offscreen annotation must have when being brought onscreen (by +/// being selected) +const NSEdgeInsets MLNMapViewOffscreenAnnotationPadding = + NSEdgeInsetsMake(-30.0f, -30.0f, -30.0f, -30.0f); /// Unique identifier representing a single annotation in mbgl. typedef uint64_t MLNAnnotationTag; @@ -118,28 +121,28 @@ typedef std::map, MLNAnnotationTag> MLNAnnotationObjectTagMap; /// Returns an NSImage for the default marker image. NSImage *MLNDefaultMarkerImage() { - NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MLNDefaultStyleMarkerSymbolName - ofType:@"pdf"]; - return [[NSImage alloc] initWithContentsOfFile:path]; + NSString *path = [[NSBundle mgl_frameworkBundle] pathForResource:MLNDefaultStyleMarkerSymbolName + ofType:@"pdf"]; + return [[NSImage alloc] initWithContentsOfFile:path]; } /// Converts a media timing function into a unit bezier object usable in mbgl. mbgl::util::UnitBezier MLNUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { - if (!function) { - function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; - } - float p1[2], p2[2]; - [function getControlPointAtIndex:0 values:p1]; - [function getControlPointAtIndex:1 values:p2]; - return { p1[0], p1[1], p2[0], p2[1] }; + if (!function) { + function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; + } + float p1[2], p2[2]; + [function getControlPointAtIndex:0 values:p1]; + [function getControlPointAtIndex:1 values:p2]; + return {p1[0], p1[1], p2[0], p2[1]}; } /// Lightweight container for metadata about an annotation, including the annotation itself. class MLNAnnotationContext { public: - id annotation; - /// The annotation’s image’s reuse identifier. - NSString *imageReuseIdentifier; + id annotation; + /// The annotation’s image’s reuse identifier. + NSString *imageReuseIdentifier; }; @interface MLNMapView () @@ -152,7 +155,8 @@ public: @property (nonatomic, readwrite) MLNStyle *style; /// Mapping from reusable identifiers to annotation images. -@property (nonatomic) NSMutableDictionary *annotationImagesByIdentifier; +@property (nonatomic) + NSMutableDictionary *annotationImagesByIdentifier; /// Currently shown popover representing the selected annotation. @property (nonatomic) NSPopover *calloutForSelectedAnnotation; @@ -161,1001 +165,1034 @@ public: @end @implementation MLNMapView { - /// Cross-platform map view controller. - std::unique_ptr _mbglMap; - std::unique_ptr _mbglView; - std::unique_ptr _rendererFrontend; + /// Cross-platform map view controller. + std::unique_ptr _mbglMap; + std::unique_ptr _mbglView; + std::unique_ptr _rendererFrontend; - NSPanGestureRecognizer *_panGestureRecognizer; - NSMagnificationGestureRecognizer *_magnificationGestureRecognizer; - NSRotationGestureRecognizer *_rotationGestureRecognizer; - NSClickGestureRecognizer *_singleClickRecognizer; - double _zoomAtBeginningOfGesture; - CLLocationDirection _directionAtBeginningOfGesture; - CGFloat _pitchAtBeginningOfGesture; - BOOL _didHideCursorDuringGesture; + NSPanGestureRecognizer *_panGestureRecognizer; + NSMagnificationGestureRecognizer *_magnificationGestureRecognizer; + NSRotationGestureRecognizer *_rotationGestureRecognizer; + NSClickGestureRecognizer *_singleClickRecognizer; + double _zoomAtBeginningOfGesture; + CLLocationDirection _directionAtBeginningOfGesture; + CGFloat _pitchAtBeginningOfGesture; + BOOL _didHideCursorDuringGesture; - MLNAnnotationTagContextMap _annotationContextsByAnnotationTag; - MLNAnnotationObjectTagMap _annotationTagsByAnnotation; - MLNAnnotationTag _selectedAnnotationTag; - MLNAnnotationTag _lastSelectedAnnotationTag; - /// Size of the rectangle formed by unioning the maximum slop area around every annotation image. - NSSize _unionedAnnotationImageSize; - std::vector _annotationsNearbyLastClick; - /// True if any annotations that have tooltips have been installed. - BOOL _wantsToolTipRects; - /// True if any annotation images that have custom cursors have been installed. - BOOL _wantsCursorRects; - /// True if a willChange notification has been issued for shape annotation layers and a didChange notification is pending. - BOOL _isChangingAnnotationLayers; + MLNAnnotationTagContextMap _annotationContextsByAnnotationTag; + MLNAnnotationObjectTagMap _annotationTagsByAnnotation; + MLNAnnotationTag _selectedAnnotationTag; + MLNAnnotationTag _lastSelectedAnnotationTag; + /// Size of the rectangle formed by unioning the maximum slop area around every annotation image. + NSSize _unionedAnnotationImageSize; + std::vector _annotationsNearbyLastClick; + /// True if any annotations that have tooltips have been installed. + BOOL _wantsToolTipRects; + /// True if any annotation images that have custom cursors have been installed. + BOOL _wantsCursorRects; + /// True if a willChange notification has been issued for shape annotation layers and a didChange + /// notification is pending. + BOOL _isChangingAnnotationLayers; - // Cached checks for delegate method implementations that may be called from - // MLNMultiPointDelegate methods. + // Cached checks for delegate method implementations that may be called from + // MLNMultiPointDelegate methods. - BOOL _delegateHasAlphasForShapeAnnotations; - BOOL _delegateHasStrokeColorsForShapeAnnotations; - BOOL _delegateHasFillColorsForShapeAnnotations; - BOOL _delegateHasLineWidthsForShapeAnnotations; + BOOL _delegateHasAlphasForShapeAnnotations; + BOOL _delegateHasStrokeColorsForShapeAnnotations; + BOOL _delegateHasFillColorsForShapeAnnotations; + BOOL _delegateHasLineWidthsForShapeAnnotations; - /// True if the current process is the Interface Builder designable - /// renderer. When drawing the designable, the map is paused, so any call to - /// it may hang the process. - BOOL _isTargetingInterfaceBuilder; - CLLocationDegrees _pendingLatitude; - CLLocationDegrees _pendingLongitude; + /// True if the current process is the Interface Builder designable + /// renderer. When drawing the designable, the map is paused, so any call to + /// it may hang the process. + BOOL _isTargetingInterfaceBuilder; + CLLocationDegrees _pendingLatitude; + CLLocationDegrees _pendingLongitude; - /// True if the view is currently printing itself. - BOOL _isPrinting; + /// True if the view is currently printing itself. + BOOL _isPrinting; - /// reachability instance - MLNReachability *_reachability; + /// reachability instance + MLNReachability *_reachability; - MLNRenderingStats* _renderingStats; + MLNRenderingStats *_renderingStats; } // MARK: Lifecycle + (void)initialize { - if (self == [MLNMapView class]) { - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ - MLNScrollWheelZoomsMapViewDefaultKey: @NO, - }]; - } + if (self == [MLNMapView class]) { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + MLNScrollWheelZoomsMapViewDefaultKey : @NO, + }]; + } } - (instancetype)initWithFrame:(NSRect)frameRect { - if (self = [super initWithFrame:frameRect]) { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@", NSStringFromRect(frameRect)); - [self commonInitWithOptions:nil]; - self.styleURL = nil; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; + if (self = [super initWithFrame:frameRect]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@", NSStringFromRect(frameRect)); + [self commonInitWithOptions:nil]; + self.styleURL = nil; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } - (instancetype)initWithFrame:(NSRect)frame styleURL:(nullable NSURL *)styleURL { - if (self = [super initWithFrame:frame]) { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromRect(frame), styleURL); - [self commonInitWithOptions:nil]; - self.styleURL = styleURL; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@ styleURL: %@", NSStringFromRect(frame), styleURL); + [self commonInitWithOptions:nil]; + self.styleURL = styleURL; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } -- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options -{ - if (self = [super initWithFrame:frame]) - { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromRect(frame)); - [self commonInitWithOptions:options]; +- (instancetype)initWithFrame:(CGRect)frame options:(MLNMapOptions *)options { + if (self = [super initWithFrame:frame]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + MLNLogDebug(@"Initializing frame: %@ with options", NSStringFromRect(frame)); + [self commonInitWithOptions:options]; - if (options) { - if (options.styleURL) { - self.styleURL = options.styleURL; - } - } else { - self.styleURL = nil; - } - - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + if (options) { + if (options.styleURL) { + self.styleURL = options.styleURL; + } + } else { + self.styleURL = nil; } - return self; + + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } - (instancetype)initWithCoder:(nonnull NSCoder *)decoder { - if (self = [super initWithCoder:decoder]) { - MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); - [self commonInitWithOptions:nil]; - MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); - } - return self; + if (self = [super initWithCoder:decoder]) { + MLNLogInfo(@"Starting %@ initialization.", NSStringFromClass([self class])); + [self commonInitWithOptions:nil]; + MLNLogInfo(@"Finalizing %@ initialization.", NSStringFromClass([self class])); + } + return self; } - (void)awakeFromNib { - [super awakeFromNib]; + [super awakeFromNib]; - // If the Style URL inspectable was not set, make sure to go through - // -setStyleURL: to load the default style. - if (_mbglMap->getStyle().getURL().empty()) { - self.styleURL = nil; - } + // If the Style URL inspectable was not set, make sure to go through + // -setStyleURL: to load the default style. + if (_mbglMap->getStyle().getURL().empty()) { + self.styleURL = nil; + } } + (NSArray *)restorableStateKeyPaths { - return @[@"camera", @"debugMask"]; + return @[ @"camera", @"debugMask" ]; } -- (void)commonInitWithOptions:(MLNMapOptions*)mlnMapoptions -{ - if (mlnMapoptions == nil) - { - mlnMapoptions = [[MLNMapOptions alloc] init]; - } +- (void)commonInitWithOptions:(MLNMapOptions *)mlnMapoptions { + if (mlnMapoptions == nil) { + mlnMapoptions = [[MLNMapOptions alloc] init]; + } - [MLNNetworkConfiguration sharedManager]; + [MLNNetworkConfiguration sharedManager]; - _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; + _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; - // Set up cross-platform controllers and resources. - _mbglView = MLNMapViewImpl::Create(self); + // Set up cross-platform controllers and resources. + _mbglView = MLNMapViewImpl::Create(self); - // Delete the pre-offline ambient cache at - // ~/Library/Caches/com.mapbox.MapboxGL/cache.db. - NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory - inDomain:NSUserDomainMask - appropriateForURL:nil - create:NO - error:nil]; - cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent:@"com.mapbox.MapboxGL"]; - NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"]; - [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL]; + // Delete the pre-offline ambient cache at + // ~/Library/Caches/com.mapbox.MapboxGL/cache.db. + NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:NO + error:nil]; + cachesDirectoryURL = [cachesDirectoryURL URLByAppendingPathComponent:@"com.mapbox.MapboxGL"]; + NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"]; + [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL]; - MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; + MLNRendererConfiguration *config = [MLNRendererConfiguration currentConfiguration]; - auto localFontFamilyName = config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr; - auto renderer = std::make_unique(_mbglView->getRendererBackend(), config.scaleFactor, localFontFamilyName); - BOOL enableCrossSourceCollisions = !config.perSourceCollisions; - _rendererFrontend = std::make_unique(std::move(renderer), self, _mbglView->getRendererBackend(), true); + auto localFontFamilyName = + config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr; + auto renderer = std::make_unique(_mbglView->getRendererBackend(), + config.scaleFactor, localFontFamilyName); + BOOL enableCrossSourceCollisions = !config.perSourceCollisions; + _rendererFrontend = std::make_unique(std::move(renderer), self, + _mbglView->getRendererBackend(), true); - mbgl::MapOptions mapOptions; - mapOptions.withMapMode(mbgl::MapMode::Continuous) - .withSize(self.size) - .withPixelRatio(config.scaleFactor) - .withConstrainMode(mbgl::ConstrainMode::None) - .withViewportMode(mbgl::ViewportMode::Default) - .withCrossSourceCollisions(enableCrossSourceCollisions); + mbgl::MapOptions mapOptions; + mapOptions.withMapMode(mbgl::MapMode::Continuous) + .withSize(self.size) + .withPixelRatio(config.scaleFactor) + .withConstrainMode(mbgl::ConstrainMode::None) + .withViewportMode(mbgl::ViewportMode::Default) + .withCrossSourceCollisions(enableCrossSourceCollisions); - auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; - mbgl::ResourceOptions resourceOptions; - resourceOptions.withTileServerOptions(*tileServerOptions) - .withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) - .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String); - mbgl::ClientOptions clientOptions; + auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; + mbgl::ResourceOptions resourceOptions; + resourceOptions.withTileServerOptions(*tileServerOptions) + .withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) + .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String); + mbgl::ClientOptions clientOptions; - auto apiKey = [[MLNSettings sharedSettings] apiKey]; - if (apiKey) { - resourceOptions.withApiKey([apiKey UTF8String]); - } + auto apiKey = [[MLNSettings sharedSettings] apiKey]; + if (apiKey) { + resourceOptions.withApiKey([apiKey UTF8String]); + } - const mbgl::util::ActionJournalOptions& actionJournalOptions = [mlnMapoptions.actionJournalOptions getCoreOptions]; - _mbglMap = std::make_unique(*_rendererFrontend, *_mbglView, mapOptions, resourceOptions, clientOptions, actionJournalOptions); + const mbgl::util::ActionJournalOptions &actionJournalOptions = + [mlnMapoptions.actionJournalOptions getCoreOptions]; + _mbglMap = std::make_unique(*_rendererFrontend, *_mbglView, mapOptions, + resourceOptions, clientOptions, actionJournalOptions); - // Notify map object when network reachability status changes. - _reachability = [MLNReachability reachabilityForInternetConnection]; - _reachability.reachableBlock = ^(MLNReachability *) { - mbgl::NetworkStatus::Reachable(); - }; - [_reachability startNotifier]; + // Notify map object when network reachability status changes. + _reachability = [MLNReachability reachabilityForInternetConnection]; + _reachability.reachableBlock = ^(MLNReachability *) { + mbgl::NetworkStatus::Reachable(); + }; + [_reachability startNotifier]; - // Install ornaments and gesture recognizers. - [self installZoomControls]; - [self installCompass]; - [self installLogoView]; - [self installAttributionView]; - [self installGestureRecognizers]; + // Install ornaments and gesture recognizers. + [self installZoomControls]; + [self installCompass]; + [self installLogoView]; + [self installAttributionView]; + [self installGestureRecognizers]; - // Set up annotation management and selection state. - _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; - _annotationContextsByAnnotationTag = {}; - _annotationTagsByAnnotation = {}; - _selectedAnnotationTag = MLNAnnotationTagNotFound; - _lastSelectedAnnotationTag = MLNAnnotationTagNotFound; - _annotationsNearbyLastClick = {}; + // Set up annotation management and selection state. + _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; + _annotationContextsByAnnotationTag = {}; + _annotationTagsByAnnotation = {}; + _selectedAnnotationTag = MLNAnnotationTagNotFound; + _lastSelectedAnnotationTag = MLNAnnotationTagNotFound; + _annotationsNearbyLastClick = {}; - // Jump to Null Island initially. - self.automaticallyAdjustsContentInsets = YES; - mbgl::CameraOptions options; - options.center = mbgl::LatLng(0, 0); - options.padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - options.zoom = *_mbglMap->getBounds().minZoom; - _mbglMap->jumpTo(options); - _pendingLatitude = NAN; - _pendingLongitude = NAN; + // Jump to Null Island initially. + self.automaticallyAdjustsContentInsets = YES; + mbgl::CameraOptions options; + options.center = mbgl::LatLng(0, 0); + options.padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + options.zoom = *_mbglMap->getBounds().minZoom; + _mbglMap->jumpTo(options); + _pendingLatitude = NAN; + _pendingLongitude = NAN; } - (mbgl::Size)size { - // check for minimum texture size supported by OpenGL ES 2.0 - // - CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64)); - return { static_cast(size.width), - static_cast(size.height) }; + // check for minimum texture size supported by OpenGL ES 2.0 + // + CGSize size = CGSizeMake(MAX(self.bounds.size.width, 64), MAX(self.bounds.size.height, 64)); + return {static_cast(size.width), static_cast(size.height)}; } - (mbgl::Size)framebufferSize { - NSRect bounds = [self convertRectToBacking:self.bounds]; - return { static_cast(bounds.size.width), static_cast(bounds.size.height) }; + NSRect bounds = [self convertRectToBacking:self.bounds]; + return {static_cast(bounds.size.width), static_cast(bounds.size.height)}; } /// Adds zoom controls to the lower-right corner. - (void)installZoomControls { - _zoomControls = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect]; - _zoomControls.wantsLayer = YES; - _zoomControls.layer.opacity = MLNOrnamentOpacity; - [(NSSegmentedCell *)_zoomControls.cell setTrackingMode:NSSegmentSwitchTrackingMomentary]; - _zoomControls.continuous = YES; - _zoomControls.segmentCount = 2; - [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_LABEL", nil, nil, @"−", @"Label of Zoom Out button; U+2212 MINUS SIGN") forSegment:0]; - [(NSSegmentedCell *)_zoomControls.cell setTag:0 forSegment:0]; - [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_TOOLTIP", nil, nil, @"Zoom Out", @"Tooltip of Zoom Out button") forSegment:0]; - [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_LABEL", nil, nil, @"+", @"Label of Zoom In button") forSegment:1]; - [(NSSegmentedCell *)_zoomControls.cell setTag:1 forSegment:1]; - [(NSSegmentedCell *)_zoomControls.cell setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_TOOLTIP", nil, nil, @"Zoom In", @"Tooltip of Zoom In button") forSegment:1]; - _zoomControls.target = self; - _zoomControls.action = @selector(zoomInOrOut:); - _zoomControls.controlSize = NSControlSizeRegular; - [_zoomControls sizeToFit]; - _zoomControls.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_zoomControls]; + _zoomControls = [[NSSegmentedControl alloc] initWithFrame:NSZeroRect]; + _zoomControls.wantsLayer = YES; + _zoomControls.layer.opacity = MLNOrnamentOpacity; + [(NSSegmentedCell *)_zoomControls.cell setTrackingMode:NSSegmentSwitchTrackingMomentary]; + _zoomControls.continuous = YES; + _zoomControls.segmentCount = 2; + [_zoomControls + setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_LABEL", nil, nil, @"−", + @"Label of Zoom Out button; U+2212 MINUS SIGN") + forSegment:0]; + [(NSSegmentedCell *)_zoomControls.cell setTag:0 forSegment:0]; + [(NSSegmentedCell *)_zoomControls.cell + setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_OUT_TOOLTIP", nil, nil, @"Zoom Out", + @"Tooltip of Zoom Out button") + forSegment:0]; + [_zoomControls setLabel:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_LABEL", nil, nil, @"+", + @"Label of Zoom In button") + forSegment:1]; + [(NSSegmentedCell *)_zoomControls.cell setTag:1 forSegment:1]; + [(NSSegmentedCell *)_zoomControls.cell + setToolTip:NSLocalizedStringWithDefaultValue(@"ZOOM_IN_TOOLTIP", nil, nil, @"Zoom In", + @"Tooltip of Zoom In button") + forSegment:1]; + _zoomControls.target = self; + _zoomControls.action = @selector(zoomInOrOut:); + _zoomControls.controlSize = NSControlSizeRegular; + [_zoomControls sizeToFit]; + _zoomControls.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_zoomControls]; } /// Adds a rudimentary compass control to the lower-right corner. - (void)installCompass { - _compass = [[NSSlider alloc] initWithFrame:NSZeroRect]; - _compass.wantsLayer = YES; - _compass.layer.opacity = MLNOrnamentOpacity; - _compass.cell = [[MLNCompassCell alloc] init]; - _compass.continuous = YES; - _compass.target = self; - _compass.action = @selector(rotate:); - [_compass sizeToFit]; - _compass.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_compass]; + _compass = [[NSSlider alloc] initWithFrame:NSZeroRect]; + _compass.wantsLayer = YES; + _compass.layer.opacity = MLNOrnamentOpacity; + _compass.cell = [[MLNCompassCell alloc] init]; + _compass.continuous = YES; + _compass.target = self; + _compass.action = @selector(rotate:); + [_compass sizeToFit]; + _compass.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_compass]; } /// Adds a Mapbox logo to the lower-left corner. - (void)installLogoView { - _logoView = [[NSImageView alloc] initWithFrame:NSZeroRect]; - _logoView.wantsLayer = YES; - NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile: - [[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; - // Account for the image’s built-in padding when aligning other controls to the logo. - logoImage.alignmentRect = NSOffsetRect(logoImage.alignmentRect, 0, 3); - _logoView.image = logoImage; - _logoView.translatesAutoresizingMaskIntoConstraints = NO; - _logoView.accessibilityTitle = NSLocalizedStringWithDefaultValue(@"MAP_A11Y_TITLE", nil, nil, @"Mapbox", @"Accessibility title"); - [self addSubview:_logoView]; + _logoView = [[NSImageView alloc] initWithFrame:NSZeroRect]; + _logoView.wantsLayer = YES; + NSImage *logoImage = [[NSImage alloc] + initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" + ofType:@"pdf"]]; + // Account for the image’s built-in padding when aligning other controls to the logo. + logoImage.alignmentRect = NSOffsetRect(logoImage.alignmentRect, 0, 3); + _logoView.image = logoImage; + _logoView.translatesAutoresizingMaskIntoConstraints = NO; + _logoView.accessibilityTitle = NSLocalizedStringWithDefaultValue( + @"MAP_A11Y_TITLE", nil, nil, @"Mapbox", @"Accessibility title"); + [self addSubview:_logoView]; } /// Adds legally required map attribution to the lower-left corner. - (void)installAttributionView { - [_attributionView removeFromSuperview]; - _attributionView = [[NSView alloc] initWithFrame:NSZeroRect]; - _attributionView.wantsLayer = YES; + [_attributionView removeFromSuperview]; + _attributionView = [[NSView alloc] initWithFrame:NSZeroRect]; + _attributionView.wantsLayer = YES; - // Make the background and foreground translucent to be unobtrusive. - _attributionView.layer.opacity = 0.6; + // Make the background and foreground translucent to be unobtrusive. + _attributionView.layer.opacity = 0.6; - // Blur the background to prevent text underneath the view from running into - // the text in the view, rendering it illegible. - CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; - [attributionBlurFilter setDefaults]; + // Blur the background to prevent text underneath the view from running into + // the text in the view, rendering it illegible. + CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; + [attributionBlurFilter setDefaults]; - // Brighten the background. This is similar to applying a translucent white - // background on the view, but the effect is a bit more subtle and works - // well with the blur above. - CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; - [attributionColorFilter setDefaults]; - [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; + // Brighten the background. This is similar to applying a translucent white + // background on the view, but the effect is a bit more subtle and works + // well with the blur above. + CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; + [attributionColorFilter setDefaults]; + [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; - // Apply the background effects and a standard button corner radius. - _attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter]; - _attributionView.layer.cornerRadius = 4; + // Apply the background effects and a standard button corner radius. + _attributionView.backgroundFilters = @[ attributionColorFilter, attributionBlurFilter ]; + _attributionView.layer.cornerRadius = 4; - _attributionView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:_attributionView]; - [self updateAttributionView]; + _attributionView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_attributionView]; + [self updateAttributionView]; } /// Adds gesture recognizers for manipulating the viewport and selecting annotations. - (void)installGestureRecognizers { - _scrollEnabled = YES; - _zoomEnabled = YES; - _rotateEnabled = YES; - _pitchEnabled = YES; + _scrollEnabled = YES; + _zoomEnabled = YES; + _rotateEnabled = YES; + _pitchEnabled = YES; - _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; - _panGestureRecognizer.delaysKeyEvents = YES; - [self addGestureRecognizer:_panGestureRecognizer]; + _panGestureRecognizer = + [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; + _panGestureRecognizer.delaysKeyEvents = YES; + [self addGestureRecognizer:_panGestureRecognizer]; - _singleClickRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)]; - _singleClickRecognizer.delaysPrimaryMouseButtonEvents = NO; - _singleClickRecognizer.delegate = self; - [self addGestureRecognizer:_singleClickRecognizer]; + _singleClickRecognizer = + [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)]; + _singleClickRecognizer.delaysPrimaryMouseButtonEvents = NO; + _singleClickRecognizer.delegate = self; + [self addGestureRecognizer:_singleClickRecognizer]; - NSClickGestureRecognizer *rightClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightClickGesture:)]; - rightClickGestureRecognizer.buttonMask = 0x2; - [self addGestureRecognizer:rightClickGestureRecognizer]; + NSClickGestureRecognizer *rightClickGestureRecognizer = + [[NSClickGestureRecognizer alloc] initWithTarget:self + action:@selector(handleRightClickGesture:)]; + rightClickGestureRecognizer.buttonMask = 0x2; + [self addGestureRecognizer:rightClickGestureRecognizer]; - NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)]; - doubleClickGestureRecognizer.numberOfClicksRequired = 2; - doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO; - [self addGestureRecognizer:doubleClickGestureRecognizer]; + NSClickGestureRecognizer *doubleClickGestureRecognizer = + [[NSClickGestureRecognizer alloc] initWithTarget:self + action:@selector(handleDoubleClickGesture:)]; + doubleClickGestureRecognizer.numberOfClicksRequired = 2; + doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO; + [self addGestureRecognizer:doubleClickGestureRecognizer]; - _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)]; - [self addGestureRecognizer:_magnificationGestureRecognizer]; + _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleMagnificationGesture:)]; + [self addGestureRecognizer:_magnificationGestureRecognizer]; - _rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)]; - [self addGestureRecognizer:_rotationGestureRecognizer]; + _rotationGestureRecognizer = + [[NSRotationGestureRecognizer alloc] initWithTarget:self + action:@selector(handleRotationGesture:)]; + [self addGestureRecognizer:_rotationGestureRecognizer]; } /// Updates the attribution view to reflect the sources used. For now, this is /// hard-coded to the standard Mapbox and OpenStreetMap attribution. - (void)updateAttributionView { - NSView *attributionView = self.attributionView; - for (NSView *button in attributionView.subviews) { - [button removeConstraints:button.constraints]; - } - attributionView.subviews = @[]; - [attributionView removeConstraints:attributionView.constraints]; + NSView *attributionView = self.attributionView; + for (NSView *button in attributionView.subviews) { + [button removeConstraints:button.constraints]; + } + attributionView.subviews = @[]; + [attributionView removeConstraints:attributionView.constraints]; - // Make the whole string mini by default. - // Force links to be black, because the default blue is distracting. - CGFloat miniSize = [NSFont systemFontSizeForControlSize:NSControlSizeMini]; - NSArray *attributionInfos = [self.style attributionInfosWithFontSize:miniSize linkColor:[NSColor blackColor]]; - for (MLNAttributionInfo *info in attributionInfos) { - // Feedback links are added to the Help menu. - if (info.feedbackLink) { - continue; - } - - // For each attribution, add a borderless button that responds to clicks - // and feels like a hyperlink. - NSButton *button = [[MLNAttributionButton alloc] initWithAttributionInfo:info]; - button.controlSize = NSControlSizeMini; - button.translatesAutoresizingMaskIntoConstraints = NO; - - // Set the new button flush with the buttom of the container and to the - // right of the previous button, with standard spacing. If there is no - // previous button, align to the container instead. - NSView *previousView = attributionView.subviews.lastObject; - [attributionView addSubview:button]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:attributionView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:0]]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:previousView ? previousView : attributionView - attribute:previousView ? NSLayoutAttributeTrailing : NSLayoutAttributeLeading - multiplier:1 - constant:8]]; - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:button - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:attributionView - attribute:NSLayoutAttributeTop - multiplier:1 - constant:0]]; + // Make the whole string mini by default. + // Force links to be black, because the default blue is distracting. + CGFloat miniSize = [NSFont systemFontSizeForControlSize:NSControlSizeMini]; + NSArray *attributionInfos = [self.style attributionInfosWithFontSize:miniSize + linkColor:[NSColor blackColor]]; + for (MLNAttributionInfo *info in attributionInfos) { + // Feedback links are added to the Help menu. + if (info.feedbackLink) { + continue; } - if (attributionInfos.count) { - [attributionView addConstraint: - [NSLayoutConstraint constraintWithItem:attributionView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:attributionView.subviews.lastObject - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:8]]; - } + // For each attribution, add a borderless button that responds to clicks + // and feels like a hyperlink. + NSButton *button = [[MLNAttributionButton alloc] initWithAttributionInfo:info]; + button.controlSize = NSControlSizeMini; + button.translatesAutoresizingMaskIntoConstraints = NO; + + // Set the new button flush with the buttom of the container and to the + // right of the previous button, with standard spacing. If there is no + // previous button, align to the container instead. + NSView *previousView = attributionView.subviews.lastObject; + [attributionView addSubview:button]; + [attributionView addConstraint:[NSLayoutConstraint constraintWithItem:button + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:attributionView + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:0]]; + [attributionView + addConstraint:[NSLayoutConstraint + constraintWithItem:button + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:previousView ? previousView : attributionView + attribute:previousView ? NSLayoutAttributeTrailing + : NSLayoutAttributeLeading + multiplier:1 + constant:8]]; + [attributionView addConstraint:[NSLayoutConstraint constraintWithItem:button + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:attributionView + attribute:NSLayoutAttributeTop + multiplier:1 + constant:0]]; + } + + if (attributionInfos.count) { + [attributionView + addConstraint:[NSLayoutConstraint constraintWithItem:attributionView + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:attributionView.subviews.lastObject + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:8]]; + } } - (void)dealloc { + [_reachability stopNotifier]; - [_reachability stopNotifier]; + [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; + [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; + // Close any annotation callout immediately. + [self.calloutForSelectedAnnotation close]; + self.calloutForSelectedAnnotation = nil; - [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; - [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; + // Removing the annotations unregisters any outstanding KVO observers. + [self removeAnnotations:self.annotations]; - // Close any annotation callout immediately. - [self.calloutForSelectedAnnotation close]; - self.calloutForSelectedAnnotation = nil; - - // Removing the annotations unregisters any outstanding KVO observers. - [self removeAnnotations:self.annotations]; - - _mbglMap.reset(); - _mbglView.reset(); + _mbglMap.reset(); + _mbglView.reset(); } -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(__unused NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"contentLayoutRect"] || - [keyPath isEqualToString:@"titlebarAppearsTransparent"]) { - [self adjustContentInsets]; - } else if ([keyPath isEqualToString:@"coordinate"] && - [object conformsToProtocol:@protocol(MLNAnnotation)] && - ![object isKindOfClass:[MLNMultiPoint class]]) { - id annotation = object; - MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinate key path of a non-multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) { - const mbgl::Point point = MLNPointFromLocationCoordinate2D(annotation.coordinate); - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - _mbglMap->updateAnnotation(annotationTag, mbgl::SymbolAnnotation { point, annotationImage.styleIconIdentifier.UTF8String ?: "" }); - [self updateAnnotationCallouts]; - } - } else if ([keyPath isEqualToString:@"coordinates"] && - [object isKindOfClass:[MLNMultiPoint class]]) { - MLNMultiPoint *annotation = object; - MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; - // We can get here because a subclass registered itself as an observer - // of the coordinates key path of a multipoint annotation but failed - // to handle the change. This check deters us from treating the - // subclass’s context as an annotation tag. If the context happens to - // match a valid annotation tag, the annotation will be unnecessarily - // but safely updated. - if (annotation == [self annotationWithTag:annotationTag]) { - _mbglMap->updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); - [self updateAnnotationCallouts]; - } +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(__unused NSDictionary *)change + context:(void *)context { + if ([keyPath isEqualToString:@"contentLayoutRect"] || + [keyPath isEqualToString:@"titlebarAppearsTransparent"]) { + [self adjustContentInsets]; + } else if ([keyPath isEqualToString:@"coordinate"] && + [object conformsToProtocol:@protocol(MLNAnnotation)] && + ![object isKindOfClass:[MLNMultiPoint class]]) { + id annotation = object; + MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; + // We can get here because a subclass registered itself as an observer + // of the coordinate key path of a non-multipoint annotation but failed + // to handle the change. This check deters us from treating the + // subclass’s context as an annotation tag. If the context happens to + // match a valid annotation tag, the annotation will be unnecessarily + // but safely updated. + if (annotation == [self annotationWithTag:annotationTag]) { + const mbgl::Point point = MLNPointFromLocationCoordinate2D(annotation.coordinate); + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + _mbglMap->updateAnnotation( + annotationTag, + mbgl::SymbolAnnotation{point, annotationImage.styleIconIdentifier.UTF8String ?: ""}); + [self updateAnnotationCallouts]; } + } else if ([keyPath isEqualToString:@"coordinates"] && + [object isKindOfClass:[MLNMultiPoint class]]) { + MLNMultiPoint *annotation = object; + MLNAnnotationTag annotationTag = (MLNAnnotationTag)(NSUInteger)context; + // We can get here because a subclass registered itself as an observer + // of the coordinates key path of a multipoint annotation but failed + // to handle the change. This check deters us from treating the + // subclass’s context as an annotation tag. If the context happens to + // match a valid annotation tag, the annotation will be unnecessarily + // but safely updated. + if (annotation == [self annotationWithTag:annotationTag]) { + _mbglMap->updateAnnotation(annotationTag, [annotation annotationObjectWithDelegate:self]); + [self updateAnnotationCallouts]; + } + } } + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { - return [key isEqualToString:@"annotations"] ? YES : [super automaticallyNotifiesObserversForKey:key]; + return [key isEqualToString:@"annotations"] ? YES + : [super automaticallyNotifiesObserversForKey:key]; } - (void)setDelegate:(id)delegate { - _delegate = delegate; + _delegate = delegate; - // Cache checks for delegate method implementations that may be called in a - // hot loop, namely the annotation style methods. - _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; - _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; - _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; - _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; + // Cache checks for delegate method implementations that may be called in a + // hot loop, namely the annotation style methods. + _delegateHasAlphasForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; + _delegateHasStrokeColorsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; + _delegateHasFillColorsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; + _delegateHasLineWidthsForShapeAnnotations = + [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" - if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) { - NSLog(@"-mapView:regionWillChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapView:cameraWillChangeAnimated:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } - if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { - NSLog(@"-mapViewRegionIsChanging: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapViewCameraIsChanging:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } - if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) { - NSLog(@"-mapView:regionDidChangeAnimated: is not supported by the macOS SDK, but %@ implements it anyways. " - @"Please implement -[%@ mapView:cameraDidChangeAnimated:] instead.", - NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); - } + if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) { + NSLog(@"-mapView:regionWillChangeAnimated: is not supported by the macOS SDK, but %@ " + @"implements it anyways. " + @"Please implement -[%@ mapView:cameraWillChangeAnimated:] instead.", + NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); + } + if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { + NSLog(@"-mapViewRegionIsChanging: is not supported by the macOS SDK, but %@ implements it " + @"anyways. " + @"Please implement -[%@ mapViewCameraIsChanging:] instead.", + NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); + } + if ([self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) { + NSLog(@"-mapView:regionDidChangeAnimated: is not supported by the macOS SDK, but %@ implements " + @"it anyways. " + @"Please implement -[%@ mapView:cameraDidChangeAnimated:] instead.", + NSStringFromClass([delegate class]), NSStringFromClass([delegate class])); + } #pragma clang diagnostic pop } // MARK: Style + (NSSet *)keyPathsForValuesAffectingStyle { - return [NSSet setWithObject:@"styleURL"]; + return [NSSet setWithObject:@"styleURL"]; } - (nonnull NSURL *)styleURL { - NSString *styleURLString = @(_mbglMap->getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty; - return styleURLString ? [NSURL URLWithString:styleURLString] : [MLNStyle defaultStyleURL]; + NSString *styleURLString = @(_mbglMap->getStyle().getURL().c_str()).mgl_stringOrNilIfEmpty; + return styleURLString ? [NSURL URLWithString:styleURLString] : [MLNStyle defaultStyleURL]; } - (void)setStyleURL:(nullable NSURL *)styleURL { - if (_isTargetingInterfaceBuilder) { - return; - } + if (_isTargetingInterfaceBuilder) { + return; + } - if (!styleURL) { - styleURL = [MLNStyle defaultStyleURL]; - } - MLNLogDebug(@"Setting styleURL: %@", styleURL); + if (!styleURL) { + styleURL = [MLNStyle defaultStyleURL]; + } + MLNLogDebug(@"Setting styleURL: %@", styleURL); - // An access token is required to load any default style, including Streets. - if ([[MLNSettings sharedSettings] tileServerOptionsInternal]->requiresApiKey() && ![MLNSettings apiKey]) { - NSLog(@"Cannot set the style URL to %@ because no API key has been specified.", styleURL); - return; - } + // An access token is required to load any default style, including Streets. + if ([[MLNSettings sharedSettings] tileServerOptionsInternal]->requiresApiKey() && + ![MLNSettings apiKey]) { + NSLog(@"Cannot set the style URL to %@ because no API key has been specified.", styleURL); + return; + } - styleURL = styleURL.mgl_URLByStandardizingScheme; - self.style = nil; - _mbglMap->getStyle().loadURL(styleURL.absoluteString.UTF8String); + styleURL = styleURL.mgl_URLByStandardizingScheme; + self.style = nil; + _mbglMap->getStyle().loadURL(styleURL.absoluteString.UTF8String); } - (IBAction)reloadStyle:(__unused id)sender { - MLNLogInfo(@"Reloading style."); - NSURL *styleURL = self.styleURL; - _mbglMap->getStyle().loadURL(""); - self.styleURL = styleURL; + MLNLogInfo(@"Reloading style."); + NSURL *styleURL = self.styleURL; + _mbglMap->getStyle().loadURL(""); + self.styleURL = styleURL; } -- (void)setPrefetchesTiles:(BOOL)prefetchesTiles -{ - _mbglMap->setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0); +- (void)setPrefetchesTiles:(BOOL)prefetchesTiles { + _mbglMap->setPrefetchZoomDelta(prefetchesTiles ? mbgl::util::DEFAULT_PREFETCH_ZOOM_DELTA : 0); } -- (BOOL)prefetchesTiles -{ - return _mbglMap->getPrefetchZoomDelta() > 0 ? YES : NO; +- (BOOL)prefetchesTiles { + return _mbglMap->getPrefetchZoomDelta() > 0 ? YES : NO; } -- (void)setTileLodMinRadius:(double)tileLodMinRadius -{ - _mbglMap->setTileLodMinRadius(tileLodMinRadius); +- (void)setTileLodMinRadius:(double)tileLodMinRadius { + _mbglMap->setTileLodMinRadius(tileLodMinRadius); } -- (double)tileLodMinRadius -{ - return _mbglMap->getTileLodMinRadius(); +- (double)tileLodMinRadius { + return _mbglMap->getTileLodMinRadius(); } -- (void)setTileLodScale:(double)tileLodScale -{ - _mbglMap->setTileLodScale(tileLodScale); +- (void)setTileLodScale:(double)tileLodScale { + _mbglMap->setTileLodScale(tileLodScale); } -- (double)tileLodScale -{ - return _mbglMap->getTileLodScale(); +- (double)tileLodScale { + return _mbglMap->getTileLodScale(); } --(void)setTileLodPitchThreshold:(double)tileLodPitchThreshold -{ - _mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold); +- (void)setTileLodPitchThreshold:(double)tileLodPitchThreshold { + _mbglMap->setTileLodPitchThreshold(tileLodPitchThreshold); } --(double)tileLodPitchThreshold -{ - return _mbglMap->getTileLodPitchThreshold(); +- (double)tileLodPitchThreshold { + return _mbglMap->getTileLodPitchThreshold(); } --(void)setTileLodZoomShift:(double)tileLodZoomShift -{ - _mbglMap->setTileLodZoomShift(tileLodZoomShift); +- (void)setTileLodZoomShift:(double)tileLodZoomShift { + _mbglMap->setTileLodZoomShift(tileLodZoomShift); } --(double)tileLodZoomShift -{ - return _mbglMap->getTileLodZoomShift(); +- (double)tileLodZoomShift { + return _mbglMap->getTileLodZoomShift(); } - (mbgl::Renderer *)renderer { - return _rendererFrontend->getRenderer(); + return _rendererFrontend->getRenderer(); } // MARK: View hierarchy and drawing - (void)viewWillMoveToWindow:(NSWindow *)newWindow { - [self deselectAnnotation:self.selectedAnnotation]; - if (!self.dormant && !newWindow) { - self.dormant = YES; - } + [self deselectAnnotation:self.selectedAnnotation]; + if (!self.dormant && !newWindow) { + self.dormant = YES; + } - [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; - [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; + [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; + [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; } - (void)viewDidMoveToWindow { - NSWindow *window = self.window; - if (self.dormant && window) { - self.dormant = NO; - } + NSWindow *window = self.window; + if (self.dormant && window) { + self.dormant = NO; + } - if (window && _mbglMap->getMapOptions().constrainMode() == mbgl::ConstrainMode::None) { - _mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly); - } + if (window && _mbglMap->getMapOptions().constrainMode() == mbgl::ConstrainMode::None) { + _mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly); + } - [window addObserver:self - forKeyPath:@"contentLayoutRect" - options:NSKeyValueObservingOptionInitial - context:NULL]; - [window addObserver:self - forKeyPath:@"titlebarAppearsTransparent" - options:NSKeyValueObservingOptionInitial - context:NULL]; + [window addObserver:self + forKeyPath:@"contentLayoutRect" + options:NSKeyValueObservingOptionInitial + context:NULL]; + [window addObserver:self + forKeyPath:@"titlebarAppearsTransparent" + options:NSKeyValueObservingOptionInitial + context:NULL]; } - (BOOL)wantsLayer { - return YES; + return YES; } - (BOOL)wantsBestResolutionOpenGLSurface { - // Use an OpenGL layer, except when drawing the designable, which is just - // ordinary Cocoa. - return !_isTargetingInterfaceBuilder; + // Use an OpenGL layer, except when drawing the designable, which is just + // ordinary Cocoa. + return !_isTargetingInterfaceBuilder; } - (CGLContextObj)context { - return _mbglView->getCGLContextObj(); + return _mbglView->getCGLContextObj(); } - (void)setFrame:(NSRect)frame { - super.frame = frame; - if (!_isTargetingInterfaceBuilder) { - _mbglMap->setSize(self.size); - } + super.frame = frame; + if (!_isTargetingInterfaceBuilder) { + _mbglMap->setSize(self.size); + } } - (void)updateConstraints { - // Place the zoom controls at the lower-right corner of the view. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:MLNOrnamentPadding]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:MLNOrnamentPadding]]; + // Place the zoom controls at the lower-right corner of the view. + [self addConstraint:[NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:_zoomControls + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:MLNOrnamentPadding]]; + [self addConstraint:[NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:_zoomControls + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:MLNOrnamentPadding]]; - // Center the compass above the zoom controls, assuming that the compass is - // narrower than the zoom controls. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_compass - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:_zoomControls - attribute:NSLayoutAttributeCenterX - multiplier:1 - constant:0]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_zoomControls - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:_compass - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:8]]; + // Center the compass above the zoom controls, assuming that the compass is + // narrower than the zoom controls. + [self addConstraint:[NSLayoutConstraint constraintWithItem:_compass + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:_zoomControls + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0]]; + [self addConstraint:[NSLayoutConstraint constraintWithItem:_zoomControls + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:_compass + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:8]]; - // Place the logo view in the lower-left corner of the view, accounting for - // the logo’s alignment rect. - [self addConstraint: - [NSLayoutConstraint constraintWithItem:self - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:_logoView - attribute:NSLayoutAttributeBottom - multiplier:1 - constant:MLNOrnamentPadding - _logoView.image.alignmentRect.origin.y]]; - [self addConstraint: - [NSLayoutConstraint constraintWithItem:_logoView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:self - attribute:NSLayoutAttributeLeading - multiplier:1 - constant:MLNOrnamentPadding - _logoView.image.alignmentRect.origin.x]]; + // Place the logo view in the lower-left corner of the view, accounting for + // the logo’s alignment rect. + [self + addConstraint:[NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:_logoView + attribute:NSLayoutAttributeBottom + multiplier:1 + constant:MLNOrnamentPadding - + _logoView.image.alignmentRect.origin.y]]; + [self + addConstraint:[NSLayoutConstraint constraintWithItem:_logoView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeLeading + multiplier:1 + constant:MLNOrnamentPadding - + _logoView.image.alignmentRect.origin.x]]; - // Place the attribution view to the right of the logo view and size it to - // fit the buttons inside. - [self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView - attribute:NSLayoutAttributeBaseline - relatedBy:NSLayoutRelationEqual - toItem:_attributionView - attribute:NSLayoutAttributeBaseline - multiplier:1 - constant:_logoView.image.alignmentRect.origin.y]]; - [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:_logoView - attribute:NSLayoutAttributeTrailing - multiplier:1 - constant:8]]; + // Place the attribution view to the right of the logo view and size it to + // fit the buttons inside. + [self + addConstraint:[NSLayoutConstraint constraintWithItem:_logoView + attribute:NSLayoutAttributeBaseline + relatedBy:NSLayoutRelationEqual + toItem:_attributionView + attribute:NSLayoutAttributeBaseline + multiplier:1 + constant:_logoView.image.alignmentRect.origin.y]]; + [self addConstraint:[NSLayoutConstraint constraintWithItem:_attributionView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:_logoView + attribute:NSLayoutAttributeTrailing + multiplier:1 + constant:8]]; - [super updateConstraints]; + [super updateConstraints]; } - (void)renderSync { - if (!self.dormant && _rendererFrontend) { - _rendererFrontend->render(); + if (!self.dormant && _rendererFrontend) { + _rendererFrontend->render(); - if (_isPrinting) { - _isPrinting = NO; - NSImage *image = [[NSImage alloc] initWithMLNPremultipliedImage:_mbglView->readStillImage()]; - [self performSelector:@selector(printWithImage:) withObject:image afterDelay:0]; - } - -// [self updateUserLocationAnnotationView]; + if (_isPrinting) { + _isPrinting = NO; + NSImage *image = [[NSImage alloc] initWithMLNPremultipliedImage:_mbglView->readStillImage()]; + [self performSelector:@selector(printWithImage:) withObject:image afterDelay:0]; } + + // [self updateUserLocationAnnotationView]; + } } - (BOOL)isTargetingInterfaceBuilder { - return _isTargetingInterfaceBuilder; + return _isTargetingInterfaceBuilder; } - (void)setNeedsRerender { - MLNAssertIsMainThread(); + MLNAssertIsMainThread(); - [self.layer setNeedsDisplay]; + [self.layer setNeedsDisplay]; } - (void)cameraWillChangeAnimated:(BOOL)animated { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:cameraWillChangeAnimated:)]) { - [self.delegate mapView:self cameraWillChangeAnimated:animated]; - } + if ([self.delegate respondsToSelector:@selector(mapView:cameraWillChangeAnimated:)]) { + [self.delegate mapView:self cameraWillChangeAnimated:animated]; + } } - (void)cameraIsChanging { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - // Update a minimum of UI that needs to stay attached to the map - // while animating. - [self updateCompass]; - [self updateAnnotationCallouts]; + // Update a minimum of UI that needs to stay attached to the map + // while animating. + [self updateCompass]; + [self updateAnnotationCallouts]; - if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) { - [self.delegate mapViewCameraIsChanging:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) { + [self.delegate mapViewCameraIsChanging:self]; + } } - (void)cameraDidChangeAnimated:(BOOL)animated { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - // Update all UI at the end of an animation or atomic change to the - // viewport. More expensive updates can happen here, but care should - // still be taken to minimize the work done here because scroll - // gesture recognition and momentum scrolling is performed as a - // series of atomic changes, not an animation. - [self updateZoomControls]; - [self updateCompass]; - [self updateAnnotationCallouts]; - [self updateAnnotationTrackingAreas]; + // Update all UI at the end of an animation or atomic change to the + // viewport. More expensive updates can happen here, but care should + // still be taken to minimize the work done here because scroll + // gesture recognition and momentum scrolling is performed as a + // series of atomic changes, not an animation. + [self updateZoomControls]; + [self updateCompass]; + [self updateAnnotationCallouts]; + [self updateAnnotationTrackingAreas]; - if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) { - [self.delegate mapView:self cameraDidChangeAnimated:animated]; - } + if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) { + [self.delegate mapView:self cameraDidChangeAnimated:animated]; + } } - (void)mapViewWillStartLoadingMap { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) { - [self.delegate mapViewWillStartLoadingMap:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) { + [self.delegate mapViewWillStartLoadingMap:self]; + } } - (void)mapViewDidFinishLoadingMap { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - [self.style willChangeValueForKey:@"sources"]; - [self.style didChangeValueForKey:@"sources"]; - [self.style willChangeValueForKey:@"layers"]; - [self.style didChangeValueForKey:@"layers"]; - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) { - [self.delegate mapViewDidFinishLoadingMap:self]; - } + [self.style willChangeValueForKey:@"sources"]; + [self.style didChangeValueForKey:@"sources"]; + [self.style willChangeValueForKey:@"layers"]; + [self.style didChangeValueForKey:@"layers"]; + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) { + [self.delegate mapViewDidFinishLoadingMap:self]; + } } - (void)mapViewDidFailLoadingMapWithError:(NSError *)error { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) { - [self.delegate mapViewDidFailLoadingMap:self withError:error]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) { + [self.delegate mapViewDidFailLoadingMap:self withError:error]; + } } - (void)mapViewWillStartRenderingFrame { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) { - [self.delegate mapViewWillStartRenderingFrame:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) { + [self.delegate mapViewWillStartRenderingFrame:self]; + } } - (void)mapViewDidFinishRenderingFrameFullyRendered:(BOOL)fullyRendered renderingStats:(const mbgl::gfx::RenderingStats &)stats { - if (!_mbglMap) { - return; + if (!_mbglMap) { + return; + } + + if (_isChangingAnnotationLayers) { + _isChangingAnnotationLayers = NO; + [self.style didChangeValueForKey:@"layers"]; + } + + if ([self.delegate respondsToSelector:@selector + (mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)]) { + if (!_renderingStats) { + _renderingStats = [[MLNRenderingStats alloc] init]; } - if (_isChangingAnnotationLayers) { - _isChangingAnnotationLayers = NO; - [self.style didChangeValueForKey:@"layers"]; - } - - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:renderingStats:)]) - { - if (!_renderingStats) { - _renderingStats = [[MLNRenderingStats alloc] init]; - } - - [_renderingStats setCoreData:stats]; - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered renderingStats:_renderingStats]; - } - else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:frameEncodingTime:frameRenderingTime:)]) - { - [self.delegate mapViewDidFinishRenderingFrame:self - fullyRendered:fullyRendered - frameEncodingTime:stats.encodingTime - frameRenderingTime:stats.renderingTime]; - } - else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) - { - [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; - } + [_renderingStats setCoreData:stats]; + [self.delegate mapViewDidFinishRenderingFrame:self + fullyRendered:fullyRendered + renderingStats:_renderingStats]; + } else if ([self.delegate + respondsToSelector:@selector + (mapViewDidFinishRenderingFrame: + fullyRendered:frameEncodingTime:frameRenderingTime:)]) { + [self.delegate mapViewDidFinishRenderingFrame:self + fullyRendered:fullyRendered + frameEncodingTime:stats.encodingTime + frameRenderingTime:stats.renderingTime]; + } else if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame: + fullyRendered:)]) { + [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; + } } - (void)mapViewWillStartRenderingMap { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) { - [self.delegate mapViewWillStartRenderingMap:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) { + [self.delegate mapViewWillStartRenderingMap:self]; + } } - (void)mapViewDidFinishRenderingMapFullyRendered:(BOOL)fullyRendered { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) { - [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) { + [self.delegate mapViewDidFinishRenderingMap:self fullyRendered:fullyRendered]; + } } - (void)mapViewDidBecomeIdle { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) { - [self.delegate mapViewDidBecomeIdle:self]; - } + if ([self.delegate respondsToSelector:@selector(mapViewDidBecomeIdle:)]) { + [self.delegate mapViewDidBecomeIdle:self]; + } } - (void)mapViewDidFinishLoadingStyle { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - self.style = [[MLNStyle alloc] initWithRawStyle:&_mbglMap->getStyle() stylable:self]; - if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) - { - [self.delegate mapView:self didFinishLoadingStyle:self.style]; - } + self.style = [[MLNStyle alloc] initWithRawStyle:&_mbglMap->getStyle() stylable:self]; + if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) { + [self.delegate mapView:self didFinishLoadingStyle:self.style]; + } } - (void)sourceDidChange:(MLNSource *)source { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:sourceDidChange:)]) { - [self.delegate mapView:self sourceDidChange:source]; - } + if ([self.delegate respondsToSelector:@selector(mapView:sourceDidChange:)]) { + [self.delegate mapView:self sourceDidChange:source]; + } - // Attribution only applies to tiled sources - if ([source isKindOfClass:[MLNTileSource class]]) { - [self installAttributionView]; - } - self.needsUpdateConstraints = YES; - self.needsDisplay = YES; + // Attribution only applies to tiled sources + if ([source isKindOfClass:[MLNTileSource class]]) { + [self installAttributionView]; + } + self.needsUpdateConstraints = YES; + self.needsDisplay = YES; } - (BOOL)shouldRemoveStyleImage:(NSString *)imageName { - if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) { - return [self.delegate mapView:self shouldRemoveStyleImage:imageName]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shouldRemoveStyleImage:)]) { + return [self.delegate mapView:self shouldRemoveStyleImage:imageName]; + } - return YES; + return YES; } -- (void)shaderWillCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderWillCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderWillCompile:backend:defines:)]) { - [self.delegate mapView:self shaderWillCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderWillCompile:backend:defines:)]) { + [self.delegate mapView:self shaderWillCompile:id backend:backend defines:defines]; + } } -- (void)shaderDidCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderDidCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderDidCompile:backend:defines:)]) { - [self.delegate mapView:self shaderDidCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderDidCompile:backend:defines:)]) { + [self.delegate mapView:self shaderDidCompile:id backend:backend defines:defines]; + } } -- (void)shaderDidFailCompile:(NSInteger)id backend:(NSInteger)backend defines:(nonnull NSString *)defines { - if (!_mbglMap) { - return; - } +- (void)shaderDidFailCompile:(NSInteger)id + backend:(NSInteger)backend + defines:(nonnull NSString *)defines { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:shaderDidFailCompile:backend:defines:)]) { - [self.delegate mapView:self shaderDidFailCompile:id backend:backend defines:defines]; - } + if ([self.delegate respondsToSelector:@selector(mapView:shaderDidFailCompile:backend:defines:)]) { + [self.delegate mapView:self shaderDidFailCompile:id backend:backend defines:defines]; + } } -- (void)glyphsWillLoad:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsWillLoad:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsWillLoad:range:)]) { - [self.delegate mapView:self glyphsWillLoad:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsWillLoad:range:)]) { + [self.delegate mapView:self glyphsWillLoad:fontStack range:range]; + } } -- (void)glyphsDidLoad:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsDidLoad:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidLoad:range:)]) { - [self.delegate mapView:self glyphsDidLoad:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidLoad:range:)]) { + [self.delegate mapView:self glyphsDidLoad:fontStack range:range]; + } } -- (void)glyphsDidError:(nonnull NSArray*)fontStack range:(NSRange)range { - if (!_mbglMap) { - return; - } +- (void)glyphsDidError:(nonnull NSArray *)fontStack range:(NSRange)range { + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidError:range:)]) { - [self.delegate mapView:self glyphsDidError:fontStack range:range]; - } + if ([self.delegate respondsToSelector:@selector(mapView:glyphsDidError:range:)]) { + [self.delegate mapView:self glyphsDidError:fontStack range:range]; + } } - (void)tileDidTriggerAction:(MLNTileOperation)operation @@ -1165,1214 +1202,1289 @@ public: wrap:(NSInteger)wrap overscaledZ:(NSInteger)overscaledZ sourceID:(nonnull NSString *)sourceID { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:tileDidTriggerAction:x:y:z:wrap:overscaledZ:sourceID:)]) { - [self.delegate mapView:self tileDidTriggerAction:operation x:x y:y z:z wrap:wrap overscaledZ:overscaledZ sourceID:sourceID]; - } + if ([self.delegate respondsToSelector:@selector + (mapView:tileDidTriggerAction:x:y:z:wrap:overscaledZ:sourceID:)]) { + [self.delegate mapView:self + tileDidTriggerAction:operation + x:x + y:y + z:z + wrap:wrap + overscaledZ:overscaledZ + sourceID:sourceID]; + } } - (void)spriteWillLoad:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteWillLoad:url:)]) { - [self.delegate mapView:self spriteWillLoad:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteWillLoad:url:)]) { + [self.delegate mapView:self spriteWillLoad:id url:url]; + } } - (void)spriteDidLoad:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteDidLoad:url:)]) { - [self.delegate mapView:self spriteDidLoad:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteDidLoad:url:)]) { + [self.delegate mapView:self spriteDidLoad:id url:url]; + } } - (void)spriteDidError:(nullable NSString *)id url:(nullable NSString *)url { - if (!_mbglMap) { - return; - } + if (!_mbglMap) { + return; + } - if ([self.delegate respondsToSelector:@selector(mapView:spriteDidError:url:)]) { - [self.delegate mapView:self spriteDidError:id url:url]; - } + if ([self.delegate respondsToSelector:@selector(mapView:spriteDidError:url:)]) { + [self.delegate mapView:self spriteDidError:id url:url]; + } } - // MARK: Printing - (void)print:(__unused id)sender { - _isPrinting = YES; - [self setNeedsRerender]; + _isPrinting = YES; + [self setNeedsRerender]; } - (void)printWithImage:(NSImage *)image { - NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds]; - imageView.image = image; + NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds]; + imageView.image = image; - NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView]; - [op runOperation]; + NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView]; + [op runOperation]; } // MARK: Viewport + (NSSet *)keyPathsForValuesAffectingCenterCoordinate { - return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; + return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil]; } - (CLLocationCoordinate2D)centerCoordinate { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return MLNLocationCoordinate2DFromLatLng(*_mbglMap->getCameraOptions(padding).center); + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + return MLNLocationCoordinate2DFromLatLng(*_mbglMap->getCameraOptions(padding).center); } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate { - MLNLogDebug(@"Setting centerCoordinate: %@", MLNStringFromCLLocationCoordinate2D(centerCoordinate)); - [self setCenterCoordinate:centerCoordinate animated:NO]; + MLNLogDebug(@"Setting centerCoordinate: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate)); + [self setCenterCoordinate:centerCoordinate animated:NO]; } - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated { - [self setCenterCoordinate:centerCoordinate animated:animated completionHandler:nil]; + [self setCenterCoordinate:centerCoordinate animated:animated completionHandler:nil]; } -- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - MLNLogDebug(@"Setting centerCoordinate: %@ animated: %@", MLNStringFromCLLocationCoordinate2D(centerCoordinate), MLNStringFromBOOL(animated)); - mbgl::AnimationOptions animationOptions = MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0); - animationOptions.transitionFinishFn = ^() { - [self didChangeValueForKey:@"centerCoordinate"]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - } - }; +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting centerCoordinate: %@ animated: %@", + MLNStringFromCLLocationCoordinate2D(centerCoordinate), MLNStringFromBOOL(animated)); + mbgl::AnimationOptions animationOptions = + MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0); + animationOptions.transitionFinishFn = ^() { + [self didChangeValueForKey:@"centerCoordinate"]; + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); + } + }; - [self willChangeValueForKey:@"centerCoordinate"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withCenter(MLNLatLngFromLocationCoordinate2D(centerCoordinate)) - .withPadding(MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - animationOptions); + [self willChangeValueForKey:@"centerCoordinate"]; + _mbglMap->easeTo(mbgl::CameraOptions() + .withCenter(MLNLatLngFromLocationCoordinate2D(centerCoordinate)) + .withPadding(MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), + animationOptions); } - (void)offsetCenterCoordinateBy:(NSPoint)delta animated:(BOOL)animated { - [self willChangeValueForKey:@"centerCoordinate"]; - _mbglMap->cancelTransitions(); - MLNMapCamera *oldCamera = self.camera; - _mbglMap->moveBy({ delta.x, delta.y }, - MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"centerCoordinate"]; + [self willChangeValueForKey:@"centerCoordinate"]; + _mbglMap->cancelTransitions(); + MLNMapCamera *oldCamera = self.camera; + _mbglMap->moveBy({delta.x, delta.y}, + MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] && + ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { + self.camera = oldCamera; + } + [self didChangeValueForKey:@"centerCoordinate"]; } - (CLLocationDegrees)pendingLatitude { - return _pendingLatitude; + return _pendingLatitude; } - (void)setPendingLatitude:(CLLocationDegrees)pendingLatitude { - _pendingLatitude = pendingLatitude; + _pendingLatitude = pendingLatitude; } - (CLLocationDegrees)pendingLongitude { - return _pendingLongitude; + return _pendingLongitude; } - (void)setPendingLongitude:(CLLocationDegrees)pendingLongitude { - _pendingLongitude = pendingLongitude; + _pendingLongitude = pendingLongitude; } + (NSSet *)keyPathsForValuesAffectingZoomLevel { - return [NSSet setWithObject:@"camera"]; + return [NSSet setWithObject:@"camera"]; } - (double)zoomLevel { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return *_mbglMap->getCameraOptions(padding).zoom; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + return *_mbglMap->getCameraOptions(padding).zoom; } - (void)setZoomLevel:(double)zoomLevel { - MLNLogDebug(@"Setting zoomLevel: %f", zoomLevel); - [self setZoomLevel:zoomLevel animated:NO]; + MLNLogDebug(@"Setting zoomLevel: %f", zoomLevel); + [self setZoomLevel:zoomLevel animated:NO]; } - (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated { - MLNLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MLNStringFromBOOL(animated)); - [self willChangeValueForKey:@"zoomLevel"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withZoom(zoomLevel) - .withPadding(MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); - [self didChangeValueForKey:@"zoomLevel"]; + MLNLogDebug(@"Setting zoomLevel: %f animated: %@", zoomLevel, MLNStringFromBOOL(animated)); + [self willChangeValueForKey:@"zoomLevel"]; + _mbglMap->easeTo(mbgl::CameraOptions().withZoom(zoomLevel).withPadding( + MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), + MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); + [self didChangeValueForKey:@"zoomLevel"]; } - (void)setZoomLevel:(double)zoomLevel atPoint:(NSPoint)point animated:(BOOL)animated { - [self willChangeValueForKey:@"centerCoordinate"]; - [self willChangeValueForKey:@"zoomLevel"]; - MLNMapCamera *oldCamera = self.camera; - mbgl::ScreenCoordinate center(point.x, self.bounds.size.height - point.y); - _mbglMap->easeTo(mbgl::CameraOptions() - .withZoom(zoomLevel) - .withAnchor(center), - MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"zoomLevel"]; - [self didChangeValueForKey:@"centerCoordinate"]; + [self willChangeValueForKey:@"centerCoordinate"]; + [self willChangeValueForKey:@"zoomLevel"]; + MLNMapCamera *oldCamera = self.camera; + mbgl::ScreenCoordinate center(point.x, self.bounds.size.height - point.y); + _mbglMap->easeTo(mbgl::CameraOptions().withZoom(zoomLevel).withAnchor(center), + MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] && + ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { + self.camera = oldCamera; + } + [self didChangeValueForKey:@"zoomLevel"]; + [self didChangeValueForKey:@"centerCoordinate"]; } -- (void)setMinimumZoomLevel:(double)minimumZoomLevel -{ - MLNLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel); - _mbglMap->setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); +- (void)setMinimumZoomLevel:(double)minimumZoomLevel { + MLNLogDebug(@"Setting minimumZoomLevel: %f", minimumZoomLevel); + _mbglMap->setBounds(mbgl::BoundOptions().withMinZoom(minimumZoomLevel)); } -- (void)setMaximumZoomLevel:(double)maximumZoomLevel -{ - MLNLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel); - _mbglMap->setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel)); +- (void)setMaximumZoomLevel:(double)maximumZoomLevel { + MLNLogDebug(@"Setting maximumZoomLevel: %f", maximumZoomLevel); + _mbglMap->setBounds(mbgl::BoundOptions().withMaxZoom(maximumZoomLevel)); } - (double)maximumZoomLevel { - return *_mbglMap->getBounds().maxZoom; + return *_mbglMap->getBounds().maxZoom; } - (double)minimumZoomLevel { - return *_mbglMap->getBounds().minZoom; + return *_mbglMap->getBounds().minZoom; } /// Respond to a click on the zoom control. - (IBAction)zoomInOrOut:(NSSegmentedControl *)sender { - switch (sender.selectedSegment) { - case 0: - // Zoom out. - [self moveToEndOfParagraph:sender]; - break; - case 1: - // Zoom in. - [self moveToBeginningOfParagraph:sender]; - break; - default: - break; - } + switch (sender.selectedSegment) { + case 0: + // Zoom out. + [self moveToEndOfParagraph:sender]; + break; + case 1: + // Zoom in. + [self moveToBeginningOfParagraph:sender]; + break; + default: + break; + } } + (NSSet *)keyPathsForValuesAffectingDirection { - return [NSSet setWithObject:@"camera"]; + return [NSSet setWithObject:@"camera"]; } - (CLLocationDirection)direction { - return mbgl::util::wrap(*_mbglMap->getCameraOptions().bearing, 0., 360.); + return mbgl::util::wrap(*_mbglMap->getCameraOptions().bearing, 0., 360.); } - (void)setDirection:(CLLocationDirection)direction { - MLNLogDebug(@"Setting direction: %f", direction); - [self setDirection:direction animated:NO]; + MLNLogDebug(@"Setting direction: %f", direction); + [self setDirection:direction animated:NO]; } - (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated { - MLNLogDebug(@"Setting direction: %f animated: %@", direction, MLNStringFromBOOL(animated)); - [self willChangeValueForKey:@"direction"]; - _mbglMap->easeTo(mbgl::CameraOptions() - .withBearing(direction) - .withPadding(MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), - MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); - [self didChangeValueForKey:@"direction"]; + MLNLogDebug(@"Setting direction: %f animated: %@", direction, MLNStringFromBOOL(animated)); + [self willChangeValueForKey:@"direction"]; + _mbglMap->easeTo(mbgl::CameraOptions().withBearing(direction).withPadding( + MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets)), + MLNDurationFromTimeInterval(animated ? MLNAnimationDuration : 0)); + [self didChangeValueForKey:@"direction"]; } - (void)offsetDirectionBy:(CLLocationDegrees)delta animated:(BOOL)animated { - [self setDirection:*_mbglMap->getCameraOptions().bearing + delta animated:animated]; + [self setDirection:*_mbglMap->getCameraOptions().bearing + delta animated:animated]; } - (CGFloat)minimumPitch { - return *_mbglMap->getBounds().minPitch; + return *_mbglMap->getBounds().minPitch; } - (void)setMinimumPitch:(CGFloat)minimumPitch { - MLNLogDebug(@"Setting minimumPitch: %f", minimumPitch); - _mbglMap->setBounds(mbgl::BoundOptions().withMinPitch(minimumPitch)); + MLNLogDebug(@"Setting minimumPitch: %f", minimumPitch); + _mbglMap->setBounds(mbgl::BoundOptions().withMinPitch(minimumPitch)); } - (CGFloat)maximumPitch { - return *_mbglMap->getBounds().maxPitch; + return *_mbglMap->getBounds().maxPitch; } - (void)setMaximumPitch:(CGFloat)maximumPitch { - MLNLogDebug(@"Setting maximumPitch: %f", maximumPitch); - _mbglMap->setBounds(mbgl::BoundOptions().withMaxPitch(maximumPitch)); + MLNLogDebug(@"Setting maximumPitch: %f", maximumPitch); + _mbglMap->setBounds(mbgl::BoundOptions().withMaxPitch(maximumPitch)); } + (NSSet *)keyPathsForValuesAffectingCamera { - return [NSSet setWithObjects:@"latitude", @"longitude", @"centerCoordinate", @"zoomLevel", @"direction", nil]; + return [NSSet setWithObjects:@"latitude", @"longitude", @"centerCoordinate", @"zoomLevel", + @"direction", nil]; } - (MLNMapCamera *)camera { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - return [self cameraForCameraOptions:_mbglMap->getCameraOptions(padding)]; + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + return [self cameraForCameraOptions:_mbglMap->getCameraOptions(padding)]; } - (void)setCamera:(MLNMapCamera *)camera { - MLNLogDebug(@"Setting camera: %@", camera); - [self setCamera:camera animated:NO]; + MLNLogDebug(@"Setting camera: %@", camera); + [self setCamera:camera animated:NO]; } - (void)setCamera:(MLNMapCamera *)camera animated:(BOOL)animated { - MLNLogDebug(@"Setting camera: %@ animated: %@", camera, MLNStringFromBOOL(animated)); - [self setCamera:camera withDuration:animated ? MLNAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL]; + MLNLogDebug(@"Setting camera: %@ animated: %@", camera, MLNStringFromBOOL(animated)); + [self setCamera:camera + withDuration:animated ? MLNAnimationDuration : 0 + animationTimingFunction:nil + completionHandler:NULL]; } -- (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { - MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, function); - [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:NSEdgeInsetsZero completionHandler:completion]; +- (void)setCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting camera: %@ duration: %f animationTimingFunction: %@", camera, duration, + function); + [self setCamera:camera + withDuration:duration + animationTimingFunction:function + edgePadding:NSEdgeInsetsZero + completionHandler:completion]; } -- (void)setCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(NSEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { - edgePadding = MLNEdgeInsetsInsetEdgeInset(edgePadding, self.contentInsets); - mbgl::AnimationOptions animationOptions; - if (duration > 0) { - animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); - animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); - } +- (void)setCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + animationTimingFunction:(nullable CAMediaTimingFunction *)function + edgePadding:(NSEdgeInsets)edgePadding + completionHandler:(nullable void (^)(void))completion { + edgePadding = MLNEdgeInsetsInsetEdgeInset(edgePadding, self.contentInsets); + mbgl::AnimationOptions animationOptions; + if (duration > 0) { + animationOptions.duration.emplace(MLNDurationFromTimeInterval(duration)); + animationOptions.easing.emplace(MLNUnitBezierForMediaTimingFunction(function)); + } + if (completion) { + animationOptions.transitionFinishFn = [completion]() { + // Must run asynchronously after the transition is completely over. + // Otherwise, a call to -setCamera: within the completion handler + // would reenter the completion handler’s caller. + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); + }; + } + + if ([self.camera isEqualToMapCamera:camera]) { if (completion) { - animationOptions.transitionFinishFn = [completion]() { - // Must run asynchronously after the transition is completely over. - // Otherwise, a call to -setCamera: within the completion handler - // would reenter the completion handler’s caller. - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - }; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + completion(); + }); } + return; + } - if ([self.camera isEqualToMapCamera:camera]) { - if (completion) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completion(); - }); - } - return; - } - - [self willChangeValueForKey:@"camera"]; - _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; - _mbglMap->easeTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; + [self willChangeValueForKey:@"camera"]; + _mbglMap->cancelTransitions(); + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera + edgePadding:edgePadding]; + _mbglMap->easeTo(cameraOptions, animationOptions); + [self didChangeValueForKey:@"camera"]; } - (void)flyToCamera:(MLNMapCamera *)camera completionHandler:(nullable void (^)(void))completion { - MLNLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion); - [self flyToCamera:camera withDuration:-1 completionHandler:completion]; + MLNLogDebug(@"Setting flyToCamera: %@ completionHandler: %@", camera, completion); + [self flyToCamera:camera withDuration:-1 completionHandler:completion]; } -- (void)flyToCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration completionHandler:(nullable void (^)(void))completion { - MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, completion); - [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion]; +- (void)flyToCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f completionHandler: %@", camera, duration, + completion); + [self flyToCamera:camera withDuration:duration peakAltitude:-1 completionHandler:completion]; } -- (void)flyToCamera:(MLNMapCamera *)camera withDuration:(NSTimeInterval)duration peakAltitude:(CLLocationDistance)peakAltitude completionHandler:(nullable void (^)(void))completion { - MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", camera, duration, peakAltitude, completion); - mbgl::AnimationOptions animationOptions; - if (duration >= 0) { - animationOptions.duration = MLNDurationFromTimeInterval(duration); - } - if (peakAltitude >= 0) { - CLLocationDegrees peakLatitude = (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2; - CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2; - animationOptions.minZoom = MLNZoomLevelForAltitude(peakAltitude, peakPitch, - peakLatitude, self.frame.size); - } +- (void)flyToCamera:(MLNMapCamera *)camera + withDuration:(NSTimeInterval)duration + peakAltitude:(CLLocationDistance)peakAltitude + completionHandler:(nullable void (^)(void))completion { + MLNLogDebug(@"Setting flyToCamera: %@ withDuration: %f peakAltitude: %f completionHandler: %@", + camera, duration, peakAltitude, completion); + mbgl::AnimationOptions animationOptions; + if (duration >= 0) { + animationOptions.duration = MLNDurationFromTimeInterval(duration); + } + if (peakAltitude >= 0) { + CLLocationDegrees peakLatitude = + (self.centerCoordinate.latitude + camera.centerCoordinate.latitude) / 2; + CLLocationDegrees peakPitch = (self.camera.pitch + camera.pitch) / 2; + animationOptions.minZoom = + MLNZoomLevelForAltitude(peakAltitude, peakPitch, peakLatitude, self.frame.size); + } + if (completion) { + animationOptions.transitionFinishFn = [completion]() { + // Must run asynchronously after the transition is completely over. + // Otherwise, a call to -setCamera: within the completion handler + // would reenter the completion handler’s caller. + dispatch_async(dispatch_get_main_queue(), ^{ + completion(); + }); + }; + } + + if ([self.camera isEqualToMapCamera:camera]) { if (completion) { - animationOptions.transitionFinishFn = [completion]() { - // Must run asynchronously after the transition is completely over. - // Otherwise, a call to -setCamera: within the completion handler - // would reenter the completion handler’s caller. - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - }; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + completion(); + }); } + return; + } - if ([self.camera isEqualToMapCamera:camera]) { - if (completion) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - completion(); - }); - } - return; - } - - [self willChangeValueForKey:@"camera"]; - _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInsets]; - _mbglMap->flyTo(cameraOptions, animationOptions); - [self didChangeValueForKey:@"camera"]; + [self willChangeValueForKey:@"camera"]; + _mbglMap->cancelTransitions(); + mbgl::CameraOptions cameraOptions = + [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInsets]; + _mbglMap->flyTo(cameraOptions, animationOptions); + [self didChangeValueForKey:@"camera"]; } /// Returns a CameraOptions object that specifies parameters for animating to /// the given camera. -- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(NSEdgeInsets) edgePadding { - mbgl::CameraOptions options; - options.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); - options.padding = MLNEdgeInsetsFromNSEdgeInsets(edgePadding); - options.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, - camera.centerCoordinate.latitude, - self.frame.size); - if (camera.heading >= 0) { - options.bearing = camera.heading; - } - if (camera.pitch >= 0) { - options.pitch = camera.pitch; - } - return options; +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera + edgePadding:(NSEdgeInsets)edgePadding { + mbgl::CameraOptions options; + options.center = MLNLatLngFromLocationCoordinate2D(camera.centerCoordinate); + options.padding = MLNEdgeInsetsFromNSEdgeInsets(edgePadding); + options.zoom = MLNZoomLevelForAltitude(camera.altitude, camera.pitch, + camera.centerCoordinate.latitude, self.frame.size); + if (camera.heading >= 0) { + options.bearing = camera.heading; + } + if (camera.pitch >= 0) { + options.pitch = camera.pitch; + } + return options; } + (NSSet *)keyPathsForValuesAffectingVisibleCoordinateBounds { - return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil]; + return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil]; } - (MLNCoordinateBounds)visibleCoordinateBounds { - return [self convertRect:self.bounds toCoordinateBoundsFromView:self]; + return [self convertRect:self.bounds toCoordinateBoundsFromView:self]; } - (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds { - MLNLogDebug(@"Setting visibleCoordinateBounds: %@", MLNStringFromCoordinateBounds(bounds)); - [self setVisibleCoordinateBounds:bounds animated:NO]; + MLNLogDebug(@"Setting visibleCoordinateBounds: %@", MLNStringFromCoordinateBounds(bounds)); + [self setVisibleCoordinateBounds:bounds animated:NO]; } - (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds animated:(BOOL)animated { - MLNLogDebug(@"Setting visibleCoordinateBounds: %@ animated: %@", MLNStringFromCoordinateBounds(bounds), MLNStringFromBOOL(animated)); - [self setVisibleCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero animated:animated]; + MLNLogDebug(@"Setting visibleCoordinateBounds: %@ animated: %@", + MLNStringFromCoordinateBounds(bounds), MLNStringFromBOOL(animated)); + [self setVisibleCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero animated:animated]; } -- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated { - [self setVisibleCoordinateBounds:bounds edgePadding:insets animated:animated completionHandler:nil]; +- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(NSEdgeInsets)insets + animated:(BOOL)animated { + [self setVisibleCoordinateBounds:bounds + edgePadding:insets + animated:animated + completionHandler:nil]; } -- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - _mbglMap->cancelTransitions(); +- (void)setVisibleCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(NSEdgeInsets)insets + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + _mbglMap->cancelTransitions(); - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); - mbgl::AnimationOptions animationOptions; - if (animated) { - animationOptions.duration = MLNDurationFromTimeInterval(MLNAnimationDuration); - } + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + mbgl::CameraOptions cameraOptions = + _mbglMap->cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); + mbgl::AnimationOptions animationOptions; + if (animated) { + animationOptions.duration = MLNDurationFromTimeInterval(MLNAnimationDuration); + } - MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; - if ([self.camera isEqualToMapCamera:camera]) { + MLNMapCamera *camera = [self cameraForCameraOptions:cameraOptions]; + if ([self.camera isEqualToMapCamera:camera]) { + completion(); + return; + } + + [self willChangeValueForKey:@"visibleCoordinateBounds"]; + animationOptions.transitionFinishFn = ^() { + [self didChangeValueForKey:@"visibleCoordinateBounds"]; + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ completion(); - return; + }); } - - [self willChangeValueForKey:@"visibleCoordinateBounds"]; - animationOptions.transitionFinishFn = ^() { - [self didChangeValueForKey:@"visibleCoordinateBounds"]; - if (completion) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(); - }); - } - }; - _mbglMap->easeTo(cameraOptions, animationOptions); + }; + _mbglMap->easeTo(cameraOptions, animationOptions); } - (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds { - return [self cameraThatFitsCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero]; + return [self cameraThatFitsCoordinateBounds:bounds edgePadding:NSEdgeInsetsZero]; } -- (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); - return [self cameraForCameraOptions:cameraOptions]; +- (MLNMapCamera *)cameraThatFitsCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(NSEdgeInsets)insets { + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); + mbgl::CameraOptions cameraOptions = + _mbglMap->cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding); + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)camera:(MLNMapCamera *)camera fittingCoordinateBounds:(MLNCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets -{ - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); +- (MLNMapCamera *)camera:(MLNMapCamera *)camera + fittingCoordinateBounds:(MLNCoordinateBounds)bounds + edgePadding:(NSEdgeInsets)insets { + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - MLNMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + MLNMapCamera *currentCamera = self.camera; + CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; + CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MLNLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); - return [self cameraForCameraOptions:cameraOptions]; + mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds( + MLNLatLngBoundsFromCoordinateBounds(bounds), padding, direction, pitch); + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)camera:(MLNMapCamera *)camera fittingShape:(MLNShape *)shape edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); +- (MLNMapCamera *)camera:(MLNMapCamera *)camera + fittingShape:(MLNShape *)shape + edgePadding:(NSEdgeInsets)insets { + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - MLNMapCamera *currentCamera = self.camera; - CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; - CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; + MLNMapCamera *currentCamera = self.camera; + CGFloat pitch = camera.pitch < 0 ? currentCamera.pitch : camera.pitch; + CLLocationDirection direction = camera.heading < 0 ? currentCamera.heading : camera.heading; - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction, pitch); + mbgl::CameraOptions cameraOptions = + _mbglMap->cameraForGeometry([shape geometryObject], padding, direction, pitch); - return [self cameraForCameraOptions: cameraOptions]; + return [self cameraForCameraOptions:cameraOptions]; } -- (MLNMapCamera *)cameraThatFitsShape:(MLNShape *)shape direction:(CLLocationDirection)direction edgePadding:(NSEdgeInsets)insets { - mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); - padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); +- (MLNMapCamera *)cameraThatFitsShape:(MLNShape *)shape + direction:(CLLocationDirection)direction + edgePadding:(NSEdgeInsets)insets { + mbgl::EdgeInsets padding = MLNEdgeInsetsFromNSEdgeInsets(insets); + padding += MLNEdgeInsetsFromNSEdgeInsets(self.contentInsets); - mbgl::CameraOptions cameraOptions = _mbglMap->cameraForGeometry([shape geometryObject], padding, direction); + mbgl::CameraOptions cameraOptions = + _mbglMap->cameraForGeometry([shape geometryObject], padding, direction); - return [self cameraForCameraOptions:cameraOptions]; + return [self cameraForCameraOptions:cameraOptions]; } - (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions { - mbgl::CameraOptions mapCamera = _mbglMap->getCameraOptions(); - CLLocationCoordinate2D centerCoordinate = MLNLocationCoordinate2DFromLatLng(cameraOptions.center ? *cameraOptions.center : *mapCamera.center); - double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; - CLLocationDirection direction = cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction; - CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch; - CLLocationDistance altitude = MLNAltitudeForZoomLevel(zoomLevel, pitch, - centerCoordinate.latitude, - self.frame.size); - return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate - altitude:altitude - pitch:pitch - heading:direction]; + mbgl::CameraOptions mapCamera = _mbglMap->getCameraOptions(); + CLLocationCoordinate2D centerCoordinate = MLNLocationCoordinate2DFromLatLng( + cameraOptions.center ? *cameraOptions.center : *mapCamera.center); + double zoomLevel = cameraOptions.zoom ? *cameraOptions.zoom : self.zoomLevel; + CLLocationDirection direction = + cameraOptions.bearing ? mbgl::util::wrap(*cameraOptions.bearing, 0., 360.) : self.direction; + CGFloat pitch = cameraOptions.pitch ? *cameraOptions.pitch : *mapCamera.pitch; + CLLocationDistance altitude = + MLNAltitudeForZoomLevel(zoomLevel, pitch, centerCoordinate.latitude, self.frame.size); + return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate + altitude:altitude + pitch:pitch + heading:direction]; } - (void)setAutomaticallyAdjustsContentInsets:(BOOL)automaticallyAdjustsContentInsets { - _automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets; - [self adjustContentInsets]; + _automaticallyAdjustsContentInsets = automaticallyAdjustsContentInsets; + [self adjustContentInsets]; } /// Updates `contentInsets` to reflect the current window geometry. - (void)adjustContentInsets { - if (!_automaticallyAdjustsContentInsets) { - return; - } + if (!_automaticallyAdjustsContentInsets) { + return; + } - NSEdgeInsets contentInsets = self.contentInsets; - if ((self.window.styleMask & NSWindowStyleMaskFullSizeContentView) - && !self.window.titlebarAppearsTransparent) { - NSRect contentLayoutRect = [self convertRect:self.window.contentLayoutRect fromView:nil]; - if (NSMaxX(contentLayoutRect) > 0 && NSMaxY(contentLayoutRect) > 0) { - contentInsets = NSEdgeInsetsMake(NSHeight(self.bounds) - NSMaxY(contentLayoutRect), - MAX(NSMinX(contentLayoutRect), 0), - MAX(NSMinY(contentLayoutRect), 0), - NSWidth(self.bounds) - NSMaxX(contentLayoutRect)); - } - } else { - contentInsets = NSEdgeInsetsZero; + NSEdgeInsets contentInsets = self.contentInsets; + if ((self.window.styleMask & NSWindowStyleMaskFullSizeContentView) && + !self.window.titlebarAppearsTransparent) { + NSRect contentLayoutRect = [self convertRect:self.window.contentLayoutRect fromView:nil]; + if (NSMaxX(contentLayoutRect) > 0 && NSMaxY(contentLayoutRect) > 0) { + contentInsets = NSEdgeInsetsMake( + NSHeight(self.bounds) - NSMaxY(contentLayoutRect), MAX(NSMinX(contentLayoutRect), 0), + MAX(NSMinY(contentLayoutRect), 0), NSWidth(self.bounds) - NSMaxX(contentLayoutRect)); } + } else { + contentInsets = NSEdgeInsetsZero; + } - self.contentInsets = contentInsets; + self.contentInsets = contentInsets; } - (void)setContentInsets:(NSEdgeInsets)contentInsets { - [self setContentInsets:contentInsets animated:NO completionHandler:nil]; + [self setContentInsets:contentInsets animated:NO completionHandler:nil]; } - (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated { - [self setContentInsets:contentInsets animated:animated completionHandler:nil]; + [self setContentInsets:contentInsets animated:animated completionHandler:nil]; } -- (void)setContentInsets:(NSEdgeInsets)contentInsets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) { - if (completion) { - completion(); - } - return; +- (void)setContentInsets:(NSEdgeInsets)contentInsets + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) { + if (completion) { + completion(); } - MLNLogDebug(@"Setting contentInset: %@ animated:", MLNStringFromNSEdgeInsets(contentInsets), MLNStringFromBOOL(animated)); - // After adjusting the content insets, move the center coordinate from the - // old frame of reference to the new one represented by the newly set - // content insets. - CLLocationCoordinate2D oldCenter = self.centerCoordinate; - _contentInsets = contentInsets; - [self setCenterCoordinate:oldCenter animated:animated completionHandler:completion]; + return; + } + MLNLogDebug(@"Setting contentInset: %@ animated:", MLNStringFromNSEdgeInsets(contentInsets), + MLNStringFromBOOL(animated)); + // After adjusting the content insets, move the center coordinate from the + // old frame of reference to the new one represented by the newly set + // content insets. + CLLocationCoordinate2D oldCenter = self.centerCoordinate; + _contentInsets = contentInsets; + [self setCenterCoordinate:oldCenter animated:animated completionHandler:completion]; } // MARK: Mouse events and gestures - (BOOL)acceptsFirstResponder { - return YES; + return YES; } /// Drag to pan, plus drag to zoom, rotate, and tilt when a modifier key is held /// down. - (void)handlePanGesture:(NSPanGestureRecognizer *)gestureRecognizer { - NSPoint delta = [gestureRecognizer translationInView:self]; - NSPoint endPoint = [gestureRecognizer locationInView:self]; - NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y); + NSPoint delta = [gestureRecognizer translationInView:self]; + NSPoint endPoint = [gestureRecognizer locationInView:self]; + NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y); + + NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { + [self.window invalidateCursorRectsForView:self]; + _mbglMap->setGestureInProgress(true); + + if (![self isPanningWithGesture]) { + // Hide the cursor except when panning. + CGDisplayHideCursor(kCGDirectMainDisplay); + _didHideCursorDuringGesture = YES; + } + } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded || + gestureRecognizer.state == NSGestureRecognizerStateCancelled) { + _mbglMap->setGestureInProgress(false); + [self.window invalidateCursorRectsForView:self]; + + if (_didHideCursorDuringGesture) { + _didHideCursorDuringGesture = NO; + // Move the cursor back to the start point and show it again, creating + // the illusion that it has stayed in place during the entire gesture. + CGPoint cursorPoint = [self convertPoint:startPoint toView:nil]; + cursorPoint = [self.window convertRectToScreen:{cursorPoint, NSZeroSize}].origin; + cursorPoint.y = self.window.screen.frame.size.height - cursorPoint.y; + CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cursorPoint); + CGDisplayShowCursor(kCGDirectMainDisplay); + } + } + if (flags & NSEventModifierFlagShift) { + // Shift-drag to zoom. + if (!self.zoomEnabled) { + return; + } + + _mbglMap->cancelTransitions(); - NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - [self.window invalidateCursorRectsForView:self]; - _mbglMap->setGestureInProgress(true); - - if (![self isPanningWithGesture]) { - // Hide the cursor except when panning. - CGDisplayHideCursor(kCGDirectMainDisplay); - _didHideCursorDuringGesture = YES; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); - [self.window invalidateCursorRectsForView:self]; - - if (_didHideCursorDuringGesture) { - _didHideCursorDuringGesture = NO; - // Move the cursor back to the start point and show it again, creating - // the illusion that it has stayed in place during the entire gesture. - CGPoint cursorPoint = [self convertPoint:startPoint toView:nil]; - cursorPoint = [self.window convertRectToScreen:{ cursorPoint, NSZeroSize }].origin; - cursorPoint.y = self.window.screen.frame.size.height - cursorPoint.y; - CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cursorPoint); - CGDisplayShowCursor(kCGDirectMainDisplay); - } + _zoomAtBeginningOfGesture = [self zoomLevel]; + } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { + CGFloat newZoomLevel = _zoomAtBeginningOfGesture - delta.y / 75; + [self setZoomLevel:newZoomLevel atPoint:startPoint animated:NO]; } - if (flags & NSEventModifierFlagShift) { - // Shift-drag to zoom. - if (!self.zoomEnabled) { - return; - } + } else if (flags & NSEventModifierFlagOption) { + // Option-drag to rotate and/or tilt. + _mbglMap->cancelTransitions(); - _mbglMap->cancelTransitions(); + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { + _directionAtBeginningOfGesture = self.direction; + _pitchAtBeginningOfGesture = *_mbglMap->getCameraOptions().pitch; + } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { + MLNMapCamera *oldCamera = self.camera; + BOOL didChangeCamera = NO; + mbgl::ScreenCoordinate center(startPoint.x, self.bounds.size.height - startPoint.y); + if (self.rotateEnabled) { + CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10; + [self willChangeValueForKey:@"direction"]; + _mbglMap->jumpTo(mbgl::CameraOptions().withBearing(newDirection).withAnchor(center)); + didChangeCamera = YES; + [self didChangeValueForKey:@"direction"]; + } + if (self.pitchEnabled) { + _mbglMap->jumpTo(mbgl::CameraOptions() + .withPitch(_pitchAtBeginningOfGesture + delta.y / 5) + .withAnchor(center)); + didChangeCamera = YES; + } - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _zoomAtBeginningOfGesture = [self zoomLevel]; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - CGFloat newZoomLevel = _zoomAtBeginningOfGesture - delta.y / 75; - [self setZoomLevel:newZoomLevel atPoint:startPoint animated:NO]; - } - } else if (flags & NSEventModifierFlagOption) { - // Option-drag to rotate and/or tilt. - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _directionAtBeginningOfGesture = self.direction; - _pitchAtBeginningOfGesture = *_mbglMap->getCameraOptions().pitch; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - MLNMapCamera *oldCamera = self.camera; - BOOL didChangeCamera = NO; - mbgl::ScreenCoordinate center(startPoint.x, self.bounds.size.height - startPoint.y); - if (self.rotateEnabled) { - CLLocationDirection newDirection = _directionAtBeginningOfGesture - delta.x / 10; - [self willChangeValueForKey:@"direction"]; - _mbglMap->jumpTo(mbgl::CameraOptions().withBearing(newDirection).withAnchor(center)); - didChangeCamera = YES; - [self didChangeValueForKey:@"direction"]; - } - if (self.pitchEnabled) { - _mbglMap->jumpTo(mbgl::CameraOptions().withPitch(_pitchAtBeginningOfGesture + delta.y / 5).withAnchor(center)); - didChangeCamera = YES; - } - - if (didChangeCamera - && [self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - } - } else if (self.scrollEnabled) { - // Otherwise, drag to pan. - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - delta.y *= -1; - [self offsetCenterCoordinateBy:delta animated:NO]; - [gestureRecognizer setTranslation:NSZeroPoint inView:nil]; - } + if (didChangeCamera && + [self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] && + ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { + self.camera = oldCamera; + } } + } else if (self.scrollEnabled) { + // Otherwise, drag to pan. + _mbglMap->cancelTransitions(); + + if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { + delta.y *= -1; + [self offsetCenterCoordinateBy:delta animated:NO]; + [gestureRecognizer setTranslation:NSZeroPoint inView:nil]; + } + } } /// Returns whether the user is panning using a gesture. - (BOOL)isPanningWithGesture { NSGestureRecognizerState state = _panGestureRecognizer.state; NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; - return ((state == NSGestureRecognizerStateBegan || state == NSGestureRecognizerStateChanged) - && !(flags & NSEventModifierFlagShift || flags & NSEventModifierFlagOption)); + return ((state == NSGestureRecognizerStateBegan || state == NSGestureRecognizerStateChanged) && + !(flags & NSEventModifierFlagShift || flags & NSEventModifierFlagOption)); } /// Pinch to zoom. - (void)handleMagnificationGesture:(NSMagnificationGestureRecognizer *)gestureRecognizer { - if (!self.zoomEnabled) { - return; - } + if (!self.zoomEnabled) { + return; + } - _mbglMap->cancelTransitions(); + _mbglMap->cancelTransitions(); - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _mbglMap->setGestureInProgress(true); - _zoomAtBeginningOfGesture = [self zoomLevel]; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - NSPoint zoomInPoint = [gestureRecognizer locationInView:self]; - mbgl::ScreenCoordinate center(zoomInPoint.x, self.bounds.size.height - zoomInPoint.y); - if (gestureRecognizer.magnification > -1) { - [self willChangeValueForKey:@"zoomLevel"]; - [self willChangeValueForKey:@"centerCoordinate"]; - MLNMapCamera *oldCamera = self.camera; - _mbglMap->jumpTo(mbgl::CameraOptions() - .withZoom(_zoomAtBeginningOfGesture + log2(1 + gestureRecognizer.magnification)) - .withAnchor(center)); - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - [self didChangeValueForKey:@"centerCoordinate"]; - [self didChangeValueForKey:@"zoomLevel"]; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { + _mbglMap->setGestureInProgress(true); + _zoomAtBeginningOfGesture = [self zoomLevel]; + } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { + NSPoint zoomInPoint = [gestureRecognizer locationInView:self]; + mbgl::ScreenCoordinate center(zoomInPoint.x, self.bounds.size.height - zoomInPoint.y); + if (gestureRecognizer.magnification > -1) { + [self willChangeValueForKey:@"zoomLevel"]; + [self willChangeValueForKey:@"centerCoordinate"]; + MLNMapCamera *oldCamera = self.camera; + _mbglMap->jumpTo( + mbgl::CameraOptions() + .withZoom(_zoomAtBeginningOfGesture + log2(1 + gestureRecognizer.magnification)) + .withAnchor(center)); + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] && + ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { + self.camera = oldCamera; + } + [self didChangeValueForKey:@"centerCoordinate"]; + [self didChangeValueForKey:@"zoomLevel"]; } + } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded || + gestureRecognizer.state == NSGestureRecognizerStateCancelled) { + _mbglMap->setGestureInProgress(false); + } } /// Click or tap to select an annotation. - (void)handleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - if (gestureRecognizer.state != NSGestureRecognizerStateEnded - || [self subviewContainingGesture:gestureRecognizer]) { - return; - } + if (gestureRecognizer.state != NSGestureRecognizerStateEnded || + [self subviewContainingGesture:gestureRecognizer]) { + return; + } - NSPoint gesturePoint = [gestureRecognizer locationInView:self]; - MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES]; - if (hitAnnotationTag != MLNAnnotationTagNotFound) { - if (hitAnnotationTag != _selectedAnnotationTag) { - id annotation = [self annotationWithTag:hitAnnotationTag]; - NSAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", hitAnnotationTag); - [self selectAnnotation:annotation atPoint:gesturePoint]; - } - } else { - [self deselectAnnotation:self.selectedAnnotation]; + NSPoint gesturePoint = [gestureRecognizer locationInView:self]; + MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint + persistingResults:YES]; + if (hitAnnotationTag != MLNAnnotationTagNotFound) { + if (hitAnnotationTag != _selectedAnnotationTag) { + id annotation = [self annotationWithTag:hitAnnotationTag]; + NSAssert(annotation, @"Cannot select nonexistent annotation with tag %llu", hitAnnotationTag); + [self selectAnnotation:annotation atPoint:gesturePoint]; } + } else { + [self deselectAnnotation:self.selectedAnnotation]; + } } /// Right-click to show the context menu. - (void)handleRightClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - NSMenu *menu = [self menuForEvent:[NSApp currentEvent]]; - if (menu) { - [NSMenu popUpContextMenu:menu withEvent:[NSApp currentEvent] forView:self]; - } + NSMenu *menu = [self menuForEvent:[NSApp currentEvent]]; + if (menu) { + [NSMenu popUpContextMenu:menu withEvent:[NSApp currentEvent] forView:self]; + } } /// Double-click or double-tap to zoom in. - (void)handleDoubleClickGesture:(NSClickGestureRecognizer *)gestureRecognizer { - if (!self.zoomEnabled || gestureRecognizer.state != NSGestureRecognizerStateEnded - || [self subviewContainingGesture:gestureRecognizer]) { - return; - } + if (!self.zoomEnabled || gestureRecognizer.state != NSGestureRecognizerStateEnded || + [self subviewContainingGesture:gestureRecognizer]) { + return; + } - _mbglMap->cancelTransitions(); + _mbglMap->cancelTransitions(); - NSPoint gesturePoint = [gestureRecognizer locationInView:self]; - [self setZoomLevel:round(self.zoomLevel) + 1 atPoint:gesturePoint animated:YES]; + NSPoint gesturePoint = [gestureRecognizer locationInView:self]; + [self setZoomLevel:round(self.zoomLevel) + 1 atPoint:gesturePoint animated:YES]; } - (void)smartMagnifyWithEvent:(NSEvent *)event { - if (!self.zoomEnabled) { - return; - } + if (!self.zoomEnabled) { + return; + } - _mbglMap->cancelTransitions(); + _mbglMap->cancelTransitions(); - // Tap with two fingers (“right-click”) to zoom out on mice but not trackpads. - NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; - [self setZoomLevel:round(self.zoomLevel) - 1 atPoint:gesturePoint animated:YES]; + // Tap with two fingers (“right-click”) to zoom out on mice but not trackpads. + NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; + [self setZoomLevel:round(self.zoomLevel) - 1 atPoint:gesturePoint animated:YES]; } /// Rotate fingers to rotate. - (void)handleRotationGesture:(NSRotationGestureRecognizer *)gestureRecognizer { - if (!self.rotateEnabled) { - return; - } - - _mbglMap->cancelTransitions(); - - if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { - _mbglMap->setGestureInProgress(true); - _directionAtBeginningOfGesture = self.direction; - } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { - MLNMapCamera *oldCamera = self.camera; - - NSPoint rotationPoint = [gestureRecognizer locationInView:self]; - mbgl::ScreenCoordinate anchor(rotationPoint.x, self.bounds.size.height - rotationPoint.y); - _mbglMap->jumpTo(mbgl::CameraOptions() - .withBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees) - .withAnchor(anchor)); - - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { - self.camera = oldCamera; - } - } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded - || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { - _mbglMap->setGestureInProgress(false); + if (!self.rotateEnabled) { + return; + } + + _mbglMap->cancelTransitions(); + + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { + _mbglMap->setGestureInProgress(true); + _directionAtBeginningOfGesture = self.direction; + } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { + MLNMapCamera *oldCamera = self.camera; + + NSPoint rotationPoint = [gestureRecognizer locationInView:self]; + mbgl::ScreenCoordinate anchor(rotationPoint.x, self.bounds.size.height - rotationPoint.y); + _mbglMap->jumpTo( + mbgl::CameraOptions() + .withBearing(_directionAtBeginningOfGesture + gestureRecognizer.rotationInDegrees) + .withAnchor(anchor)); + + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] && + ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:self.camera]) { + self.camera = oldCamera; } + } else if (gestureRecognizer.state == NSGestureRecognizerStateEnded || + gestureRecognizer.state == NSGestureRecognizerStateCancelled) { + _mbglMap->setGestureInProgress(false); + } } - (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(__unused NSEventGestureAxis)axis { - // Track both horizontal and vertical swipes in -scrollWheel:. - return YES; + // Track both horizontal and vertical swipes in -scrollWheel:. + return YES; } - (void)scrollWheel:(NSEvent *)event { - // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_7Dragging - BOOL isScrollWheel = event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone && !event.hasPreciseScrollingDeltas; - if (isScrollWheel || [[NSUserDefaults standardUserDefaults] boolForKey:MLNScrollWheelZoomsMapViewDefaultKey]) { - // A traditional, vertical scroll wheel zooms instead of panning. - if (self.zoomEnabled) { - const double delta = - event.scrollingDeltaY / ([event hasPreciseScrollingDeltas] ? 100 : 10); - if (delta != 0) { - double scale = 2.0 / (1.0 + std::exp(-std::abs(delta))); + // https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#10_7Dragging + BOOL isScrollWheel = event.phase == NSEventPhaseNone && event.momentumPhase == NSEventPhaseNone && + !event.hasPreciseScrollingDeltas; + if (isScrollWheel || + [[NSUserDefaults standardUserDefaults] boolForKey:MLNScrollWheelZoomsMapViewDefaultKey]) { + // A traditional, vertical scroll wheel zooms instead of panning. + if (self.zoomEnabled) { + const double delta = event.scrollingDeltaY / ([event hasPreciseScrollingDeltas] ? 100 : 10); + if (delta != 0) { + double scale = 2.0 / (1.0 + std::exp(-std::abs(delta))); - // Zooming out. - if (delta < 0) { - scale = 1.0 / scale; - } - - NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; - [self setZoomLevel:self.zoomLevel + log2(scale) atPoint:gesturePoint animated:NO]; - } - } - } else if (self.scrollEnabled - && _magnificationGestureRecognizer.state == NSGestureRecognizerStatePossible - && _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) { - // Scroll to pan. - _mbglMap->cancelTransitions(); - - CGFloat x = event.scrollingDeltaX; - CGFloat y = event.scrollingDeltaY; - if (x || y) { - [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; + // Zooming out. + if (delta < 0) { + scale = 1.0 / scale; } - // Drift pan. - if (event.momentumPhase != NSEventPhaseNone) { - [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; - } + NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; + [self setZoomLevel:self.zoomLevel + log2(scale) atPoint:gesturePoint animated:NO]; + } } + } else if (self.scrollEnabled && + _magnificationGestureRecognizer.state == NSGestureRecognizerStatePossible && + _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) { + // Scroll to pan. + _mbglMap->cancelTransitions(); + + CGFloat x = event.scrollingDeltaX; + CGFloat y = event.scrollingDeltaY; + if (x || y) { + [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; + } + + // Drift pan. + if (event.momentumPhase != NSEventPhaseNone) { + [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; + } + } } /// Returns the subview that the gesture is located in. - (NSView *)subviewContainingGesture:(NSGestureRecognizer *)gestureRecognizer { - if (NSPointInRect([gestureRecognizer locationInView:self.compass], self.compass.bounds)) { - return self.compass; - } - if (NSPointInRect([gestureRecognizer locationInView:self.zoomControls], self.zoomControls.bounds)) { - return self.zoomControls; - } - if (NSPointInRect([gestureRecognizer locationInView:self.attributionView], self.attributionView.bounds)) { - return self.attributionView; - } - return nil; + if (NSPointInRect([gestureRecognizer locationInView:self.compass], self.compass.bounds)) { + return self.compass; + } + if (NSPointInRect([gestureRecognizer locationInView:self.zoomControls], + self.zoomControls.bounds)) { + return self.zoomControls; + } + if (NSPointInRect([gestureRecognizer locationInView:self.attributionView], + self.attributionView.bounds)) { + return self.attributionView; + } + return nil; } // MARK: NSGestureRecognizerDelegate methods -- (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldAttemptToRecognizeWithEvent:(NSEvent *)event { - if (gestureRecognizer == _singleClickRecognizer) { - if (!self.selectedAnnotation) { - NSPoint gesturePoint = [self convertPoint:[event locationInWindow] fromView:nil]; - MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:NO]; - if (hitAnnotationTag == MLNAnnotationTagNotFound) { - return NO; - } - } +- (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer + shouldAttemptToRecognizeWithEvent:(NSEvent *)event { + if (gestureRecognizer == _singleClickRecognizer) { + if (!self.selectedAnnotation) { + NSPoint gesturePoint = [self convertPoint:[event locationInWindow] fromView:nil]; + MLNAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint + persistingResults:NO]; + if (hitAnnotationTag == MLNAnnotationTagNotFound) { + return NO; + } } - return YES; + } + return YES; } // MARK: Keyboard events - (void)keyDown:(NSEvent *)event { - // This is the recommended way to handle arrow key presses, causing - // methods like -moveUp: and -moveToBeginningOfParagraph: to be called - // for various standard keybindings. - [self interpretKeyEvents:@[event]]; + // This is the recommended way to handle arrow key presses, causing + // methods like -moveUp: and -moveToBeginningOfParagraph: to be called + // for various standard keybindings. + [self interpretKeyEvents:@[ event ]]; } // The following action methods are declared in NSResponder.h. - (void)insertTab:(id)sender { - if (self.window.firstResponder == self) { - [self.window selectNextKeyView:self]; - } + if (self.window.firstResponder == self) { + [self.window selectNextKeyView:self]; + } } - (void)insertBacktab:(id)sender { - if (self.window.firstResponder == self) { - [self.window selectPreviousKeyView:self]; - } + if (self.window.firstResponder == self) { + [self.window selectPreviousKeyView:self]; + } } - (void)insertText:(NSString *)insertString { - switch (insertString.length == 1 ? [insertString characterAtIndex:0] : 0) { - case '-': - [self moveToEndOfParagraph:nil]; - break; + switch (insertString.length == 1 ? [insertString characterAtIndex:0] : 0) { + case '-': + [self moveToEndOfParagraph:nil]; + break; - case '+': - case '=': - [self moveToBeginningOfParagraph:nil]; - break; + case '+': + case '=': + [self moveToBeginningOfParagraph:nil]; + break; - default: - [super insertText:insertString]; - break; - } + default: + [super insertText:insertString]; + break; + } } - (IBAction)moveUp:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(0, MLNKeyPanningIncrement) animated:YES]; + [self offsetCenterCoordinateBy:NSMakePoint(0, MLNKeyPanningIncrement) animated:YES]; } - (IBAction)moveDown:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(0, -MLNKeyPanningIncrement) animated:YES]; + [self offsetCenterCoordinateBy:NSMakePoint(0, -MLNKeyPanningIncrement) animated:YES]; } - (IBAction)moveLeft:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(MLNKeyPanningIncrement, 0) animated:YES]; + [self offsetCenterCoordinateBy:NSMakePoint(MLNKeyPanningIncrement, 0) animated:YES]; } - (IBAction)moveRight:(__unused id)sender { - [self offsetCenterCoordinateBy:NSMakePoint(-MLNKeyPanningIncrement, 0) animated:YES]; + [self offsetCenterCoordinateBy:NSMakePoint(-MLNKeyPanningIncrement, 0) animated:YES]; } - (IBAction)moveToBeginningOfParagraph:(__unused id)sender { - if (self.zoomEnabled) { - [self setZoomLevel:round(self.zoomLevel) + 1 animated:YES]; - } + if (self.zoomEnabled) { + [self setZoomLevel:round(self.zoomLevel) + 1 animated:YES]; + } } - (IBAction)moveToEndOfParagraph:(__unused id)sender { - if (self.zoomEnabled) { - [self setZoomLevel:round(self.zoomLevel) - 1 animated:YES]; - } + if (self.zoomEnabled) { + [self setZoomLevel:round(self.zoomLevel) - 1 animated:YES]; + } } - (IBAction)moveWordLeft:(__unused id)sender { - if (self.rotateEnabled) { - [self offsetDirectionBy:MLNKeyRotationIncrement animated:YES]; - } + if (self.rotateEnabled) { + [self offsetDirectionBy:MLNKeyRotationIncrement animated:YES]; + } } - (IBAction)moveWordRight:(__unused id)sender { - if (self.rotateEnabled) { - [self offsetDirectionBy:-MLNKeyRotationIncrement animated:YES]; - } + if (self.rotateEnabled) { + [self offsetDirectionBy:-MLNKeyRotationIncrement animated:YES]; + } } - (void)setZoomEnabled:(BOOL)zoomEnabled { - _zoomEnabled = zoomEnabled; - _zoomControls.enabled = zoomEnabled; - _zoomControls.hidden = !zoomEnabled; + _zoomEnabled = zoomEnabled; + _zoomControls.enabled = zoomEnabled; + _zoomControls.hidden = !zoomEnabled; } - (void)setRotateEnabled:(BOOL)rotateEnabled { - _rotateEnabled = rotateEnabled; - _compass.enabled = rotateEnabled; - _compass.hidden = !rotateEnabled; + _rotateEnabled = rotateEnabled; + _compass.enabled = rotateEnabled; + _compass.hidden = !rotateEnabled; } // MARK: Ornaments /// Updates the zoom controls’ enabled state based on the current zoom level. - (void)updateZoomControls { - [_zoomControls setEnabled:self.zoomLevel > self.minimumZoomLevel forSegment:0]; - [_zoomControls setEnabled:self.zoomLevel < self.maximumZoomLevel forSegment:1]; + [_zoomControls setEnabled:self.zoomLevel > self.minimumZoomLevel forSegment:0]; + [_zoomControls setEnabled:self.zoomLevel < self.maximumZoomLevel forSegment:1]; } /// Updates the compass to point in the same direction as the map. - (void)updateCompass { - // The circular slider control goes counterclockwise, whereas our map - // measures its direction clockwise. - _compass.doubleValue = -self.direction; + // The circular slider control goes counterclockwise, whereas our map + // measures its direction clockwise. + _compass.doubleValue = -self.direction; } - (IBAction)rotate:(NSSlider *)sender { - [self setDirection:-sender.doubleValue animated:YES]; + [self setDirection:-sender.doubleValue animated:YES]; } // MARK: Annotations -- (nullable NSArray> *)annotations { - if (_annotationContextsByAnnotationTag.empty()) { - return nil; - } - - // Map all the annotation tags to the annotations themselves. - std::vector> annotations; - std::transform(_annotationContextsByAnnotationTag.begin(), - _annotationContextsByAnnotationTag.end(), - std::back_inserter(annotations), - ^ id (const std::pair &pair) { - return pair.second.annotation; - }); - return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; -} - -- (nullable NSArray> *)visibleAnnotations -{ - return [self visibleAnnotationsInRect:self.bounds]; -} - -- (nullable NSArray> *)visibleAnnotationsInRect:(CGRect)rect -{ - if (_annotationContextsByAnnotationTag.empty()) - { - return nil; - } - - std::vector annotationTags = [self annotationTagsInRect:rect]; - std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; - - if (shapeAnnotationTags.size()) { - annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), shapeAnnotationTags.end()); - } - - if (annotationTags.size()) - { - NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; - - for (auto const& annotationTag: annotationTags) - { - if (!_annotationContextsByAnnotationTag.count(annotationTag) || - annotationTag == MLNAnnotationTagNotFound) - { - continue; - } - - MLNAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); - NSAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag); - if (annotationContext.annotation) - { - [annotations addObject:annotationContext.annotation]; - } - } - - return [annotations copy]; - } - +- (nullable NSArray> *)annotations { + if (_annotationContextsByAnnotationTag.empty()) { return nil; + } + + // Map all the annotation tags to the annotations themselves. + std::vector> annotations; + std::transform(_annotationContextsByAnnotationTag.begin(), + _annotationContextsByAnnotationTag.end(), std::back_inserter(annotations), + ^id(const std::pair &pair) { + return pair.second.annotation; + }); + return [NSArray arrayWithObjects:&annotations[0] count:annotations.size()]; +} + +- (nullable NSArray> *)visibleAnnotations { + return [self visibleAnnotationsInRect:self.bounds]; +} + +- (nullable NSArray> *)visibleAnnotationsInRect:(CGRect)rect { + if (_annotationContextsByAnnotationTag.empty()) { + return nil; + } + + std::vector annotationTags = [self annotationTagsInRect:rect]; + std::vector shapeAnnotationTags = [self shapeAnnotationTagsInRect:rect]; + + if (shapeAnnotationTags.size()) { + annotationTags.insert(annotationTags.end(), shapeAnnotationTags.begin(), + shapeAnnotationTags.end()); + } + + if (annotationTags.size()) { + NSMutableArray *annotations = [NSMutableArray arrayWithCapacity:annotationTags.size()]; + + for (auto const &annotationTag : annotationTags) { + if (!_annotationContextsByAnnotationTag.count(annotationTag) || + annotationTag == MLNAnnotationTagNotFound) { + continue; + } + + MLNAnnotationContext annotationContext = _annotationContextsByAnnotationTag.at(annotationTag); + NSAssert(annotationContext.annotation, @"Missing annotation for tag %llu.", annotationTag); + if (annotationContext.annotation) { + [annotations addObject:annotationContext.annotation]; + } + } + + return [annotations copy]; + } + + return nil; } /// Returns the annotation assigned the given tag. Cheap. -- (id )annotationWithTag:(MLNAnnotationTag)tag { - if ( ! _annotationContextsByAnnotationTag.count(tag) || - tag == MLNAnnotationTagNotFound) { - return nil; - } +- (id)annotationWithTag:(MLNAnnotationTag)tag { + if (!_annotationContextsByAnnotationTag.count(tag) || tag == MLNAnnotationTagNotFound) { + return nil; + } - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); - return annotationContext.annotation; + MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(tag); + return annotationContext.annotation; } /// Returns the annotation tag assigned to the given annotation. -- (MLNAnnotationTag)annotationTagForAnnotation:(id )annotation { - if (!annotation || _annotationTagsByAnnotation.count(annotation) == 0) { - return MLNAnnotationTagNotFound; - } +- (MLNAnnotationTag)annotationTagForAnnotation:(id)annotation { + if (!annotation || _annotationTagsByAnnotation.count(annotation) == 0) { + return MLNAnnotationTagNotFound; + } - return _annotationTagsByAnnotation.at(annotation); + return _annotationTagsByAnnotation.at(annotation); } -- (void)addAnnotation:(id )annotation { - if (annotation) { - [self addAnnotations:@[annotation]]; - } +- (void)addAnnotation:(id)annotation { + if (annotation) { + [self addAnnotations:@[ annotation ]]; + } } -- (void)addAnnotations:(NSArray> *)annotations { - if (!annotations) { - return; +- (void)addAnnotations:(NSArray> *)annotations { + if (!annotations) { + return; + } + + [self willChangeValueForKey:@"annotations"]; + + BOOL delegateHasImagesForAnnotations = + [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; + + for (id annotation in annotations) { + NSAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], + @"Annotation does not conform to MLNAnnotation"); + + // adding the same annotation object twice is a no-op + if (_annotationTagsByAnnotation.count(annotation) != 0) { + continue; } - [self willChangeValueForKey:@"annotations"]; + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + // The multipoint knows how to style itself (with the map view’s help). + MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; + if (!multiPoint.pointCount) { + continue; + } - BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; + _isChangingAnnotationLayers = YES; + MLNAnnotationTag annotationTag = + _mbglMap->addAnnotation([multiPoint annotationObjectWithDelegate:self]); + MLNAnnotationContext context; + context.annotation = annotation; + _annotationContextsByAnnotationTag[annotationTag] = context; + _annotationTagsByAnnotation[annotation] = annotationTag; - for (id annotation in annotations) { - NSAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"Annotation does not conform to MLNAnnotation"); + [(NSObject *)annotation addObserver:self + forKeyPath:@"coordinates" + options:0 + context:(void *)(NSUInteger)annotationTag]; + } else if (![annotation isKindOfClass:[MLNMultiPolyline class]] && + ![annotation isKindOfClass:[MLNMultiPolygon class]] && + ![annotation isKindOfClass:[MLNShapeCollection class]] && + ![annotation isKindOfClass:[MLNPointCollection class]]) { + MLNAnnotationImage *annotationImage = nil; + if (delegateHasImagesForAnnotations) { + annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; + } + if (!annotationImage) { + annotationImage = + [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; + } + if (!annotationImage) { + annotationImage = self.defaultAnnotationImage; + } - // adding the same annotation object twice is a no-op - if (_annotationTagsByAnnotation.count(annotation) != 0) { - continue; - } + NSString *symbolName = annotationImage.styleIconIdentifier; + if (!symbolName) { + symbolName = + [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; + annotationImage.styleIconIdentifier = symbolName; + } - if ([annotation isKindOfClass:[MLNMultiPoint class]]) { - // The multipoint knows how to style itself (with the map view’s help). - MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; - if (!multiPoint.pointCount) { - continue; - } + if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { + self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; + [self installAnnotationImage:annotationImage]; + } - _isChangingAnnotationLayers = YES; - MLNAnnotationTag annotationTag = _mbglMap->addAnnotation([multiPoint annotationObjectWithDelegate:self]); - MLNAnnotationContext context; - context.annotation = annotation; - _annotationContextsByAnnotationTag[annotationTag] = context; - _annotationTagsByAnnotation[annotation] = annotationTag; + MLNAnnotationTag annotationTag = _mbglMap->addAnnotation(mbgl::SymbolAnnotation{ + MLNPointFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String ?: ""}); - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinates" options:0 context:(void *)(NSUInteger)annotationTag]; - } else if (![annotation isKindOfClass:[MLNMultiPolyline class]] - && ![annotation isKindOfClass:[MLNMultiPolygon class]] - && ![annotation isKindOfClass:[MLNShapeCollection class]] - && ![annotation isKindOfClass:[MLNPointCollection class]]) { - MLNAnnotationImage *annotationImage = nil; - if (delegateHasImagesForAnnotations) { - annotationImage = [self.delegate mapView:self imageForAnnotation:annotation]; - } - if (!annotationImage) { - annotationImage = [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName]; - } - if (!annotationImage) { - annotationImage = self.defaultAnnotationImage; - } + MLNAnnotationContext context; + context.annotation = annotation; + context.imageReuseIdentifier = annotationImage.reuseIdentifier; + _annotationContextsByAnnotationTag[annotationTag] = context; + _annotationTagsByAnnotation[annotation] = annotationTag; - NSString *symbolName = annotationImage.styleIconIdentifier; - if (!symbolName) { - symbolName = [MLNAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; - annotationImage.styleIconIdentifier = symbolName; - } + if ([annotation isKindOfClass:[NSObject class]]) { + NSAssert(![annotation isKindOfClass:[MLNMultiPoint class]], + @"Point annotation should not be MLNMultiPoint."); + [(NSObject *)annotation addObserver:self + forKeyPath:@"coordinate" + options:0 + context:(void *)(NSUInteger)annotationTag]; + } - if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - [self installAnnotationImage:annotationImage]; - } - - MLNAnnotationTag annotationTag = _mbglMap->addAnnotation(mbgl::SymbolAnnotation { - MLNPointFromLocationCoordinate2D(annotation.coordinate), - symbolName.UTF8String ?: "" - }); - - MLNAnnotationContext context; - context.annotation = annotation; - context.imageReuseIdentifier = annotationImage.reuseIdentifier; - _annotationContextsByAnnotationTag[annotationTag] = context; - _annotationTagsByAnnotation[annotation] = annotationTag; - - if ([annotation isKindOfClass:[NSObject class]]) { - NSAssert(![annotation isKindOfClass:[MLNMultiPoint class]], @"Point annotation should not be MLNMultiPoint."); - [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; - } - - // Opt into potentially expensive tooltip tracking areas. - if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { - _wantsToolTipRects = YES; - } - } + // Opt into potentially expensive tooltip tracking areas. + if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { + _wantsToolTipRects = YES; + } } + } - [self didChangeValueForKey:@"annotations"]; - if (_isChangingAnnotationLayers) { - [self.style willChangeValueForKey:@"layers"]; - } + [self didChangeValueForKey:@"annotations"]; + if (_isChangingAnnotationLayers) { + [self.style willChangeValueForKey:@"layers"]; + } - [self updateAnnotationTrackingAreas]; + [self updateAnnotationTrackingAreas]; } /// Initializes and returns a default annotation image that depicts a round pin /// rising from the center, with a shadow slightly below center. The alignment /// rect therefore excludes the bottom half. - (MLNAnnotationImage *)defaultAnnotationImage { - NSImage *image = MLNDefaultMarkerImage(); - NSRect alignmentRect = image.alignmentRect; - alignmentRect.origin.y = NSMidY(alignmentRect); - alignmentRect.size.height /= 2; - image.alignmentRect = alignmentRect; - return [MLNAnnotationImage annotationImageWithImage:image - reuseIdentifier:MLNDefaultStyleMarkerSymbolName]; + NSImage *image = MLNDefaultMarkerImage(); + NSRect alignmentRect = image.alignmentRect; + alignmentRect.origin.y = NSMidY(alignmentRect); + alignmentRect.size.height /= 2; + image.alignmentRect = alignmentRect; + return [MLNAnnotationImage annotationImageWithImage:image + reuseIdentifier:MLNDefaultStyleMarkerSymbolName]; } /// Sends the raw pixel data of the annotation image’s image to mbgl and /// calculates state needed for hit testing later. - (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage { - NSString *iconIdentifier = annotationImage.styleIconIdentifier; - self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; + NSString *iconIdentifier = annotationImage.styleIconIdentifier; + self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - NSImage *image = annotationImage.image; - NSSize size = image.size; - if (size.width == 0 || size.height == 0 || !image.valid) { - // Can’t create an empty sprite. An image that hasn’t loaded is also useless. - return; - } + NSImage *image = annotationImage.image; + NSSize size = image.size; + if (size.width == 0 || size.height == 0 || !image.valid) { + // Can’t create an empty sprite. An image that hasn’t loaded is also useless. + return; + } - _mbglMap->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); + _mbglMap->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); - // Create a slop area with a “radius” equal to the annotation image’s entire - // size, allowing the eventual click to be on any point within this image. - // Union this slop area with any existing slop areas. - _unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width), - MAX(_unionedAnnotationImageSize.height, size.height)); + // Create a slop area with a “radius” equal to the annotation image’s entire + // size, allowing the eventual click to be on any point within this image. + // Union this slop area with any existing slop areas. + _unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width), + MAX(_unionedAnnotationImageSize.height, size.height)); - // Opt into potentially expensive cursor tracking areas. - if (annotationImage.cursor) { - _wantsCursorRects = YES; - } + // Opt into potentially expensive cursor tracking areas. + if (annotationImage.cursor) { + _wantsCursorRects = YES; + } } -- (void)removeAnnotation:(id )annotation { - if (annotation) { - [self removeAnnotations:@[annotation]]; - } +- (void)removeAnnotation:(id)annotation { + if (annotation) { + [self removeAnnotations:@[ annotation ]]; + } } -- (void)removeAnnotations:(NSArray> *)annotations { - if (!annotations) { - return; +- (void)removeAnnotations:(NSArray> *)annotations { + if (!annotations) { + return; + } + + [self willChangeValueForKey:@"annotations"]; + + for (id annotation in annotations) { + NSAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], + @"Annotation does not conform to MLNAnnotation"); + + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + NSAssert(annotationTag != MLNAnnotationTagNotFound, @"No ID for annotation %@", annotation); + + if (annotationTag == _selectedAnnotationTag) { + [self deselectAnnotation:annotation]; + } + if (annotationTag == _lastSelectedAnnotationTag) { + _lastSelectedAnnotationTag = MLNAnnotationTagNotFound; } - [self willChangeValueForKey:@"annotations"]; + _annotationContextsByAnnotationTag.erase(annotationTag); + _annotationTagsByAnnotation.erase(annotation); - for (id annotation in annotations) { - NSAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"Annotation does not conform to MLNAnnotation"); - - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - NSAssert(annotationTag != MLNAnnotationTagNotFound, @"No ID for annotation %@", annotation); - - if (annotationTag == _selectedAnnotationTag) { - [self deselectAnnotation:annotation]; - } - if (annotationTag == _lastSelectedAnnotationTag) { - _lastSelectedAnnotationTag = MLNAnnotationTagNotFound; - } - - _annotationContextsByAnnotationTag.erase(annotationTag); - _annotationTagsByAnnotation.erase(annotation); - - if ([annotation isKindOfClass:[NSObject class]] && - ![annotation isKindOfClass:[MLNMultiPoint class]]) { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag]; - } else if ([annotation isKindOfClass:[MLNMultiPoint class]]) { - [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinates" context:(void *)(NSUInteger)annotationTag]; - } - - _isChangingAnnotationLayers = YES; - _mbglMap->removeAnnotation(annotationTag); + if ([annotation isKindOfClass:[NSObject class]] && + ![annotation isKindOfClass:[MLNMultiPoint class]]) { + [(NSObject *)annotation removeObserver:self + forKeyPath:@"coordinate" + context:(void *)(NSUInteger)annotationTag]; + } else if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + [(NSObject *)annotation removeObserver:self + forKeyPath:@"coordinates" + context:(void *)(NSUInteger)annotationTag]; } - [self didChangeValueForKey:@"annotations"]; - if (_isChangingAnnotationLayers) { - [self.style willChangeValueForKey:@"layers"]; - } + _isChangingAnnotationLayers = YES; + _mbglMap->removeAnnotation(annotationTag); + } - [self updateAnnotationTrackingAreas]; + [self didChangeValueForKey:@"annotations"]; + if (_isChangingAnnotationLayers) { + [self.style willChangeValueForKey:@"layers"]; + } + + [self updateAnnotationTrackingAreas]; } -- (nullable MLNAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier { - return self.annotationImagesByIdentifier[identifier]; +- (nullable MLNAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier: + (NSString *)identifier { + return self.annotationImagesByIdentifier[identifier]; } -- (id )annotationAtPoint:(NSPoint)point { - return [self annotationWithTag:[self annotationTagAtPoint:point persistingResults:NO]]; +- (id)annotationAtPoint:(NSPoint)point { + return [self annotationWithTag:[self annotationTagAtPoint:point persistingResults:NO]]; } /** @@ -2388,936 +2500,987 @@ public: asking “what if?” */ - (MLNAnnotationTag)annotationTagAtPoint:(NSPoint)point persistingResults:(BOOL)persist { - // Look for any annotation near the click. An annotation is “near” if the - // distance between its center and the click is less than the maximum height - // or width of an installed annotation image. - NSRect queryRect = NSInsetRect({ point, NSZeroSize }, - -_unionedAnnotationImageSize.width / 2, - -_unionedAnnotationImageSize.height / 2); - queryRect = NSInsetRect(queryRect, -MLNAnnotationImagePaddingForHitTest, - -MLNAnnotationImagePaddingForHitTest); - std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; - std::vector nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect]; + // Look for any annotation near the click. An annotation is “near” if the + // distance between its center and the click is less than the maximum height + // or width of an installed annotation image. + NSRect queryRect = NSInsetRect({point, NSZeroSize}, -_unionedAnnotationImageSize.width / 2, + -_unionedAnnotationImageSize.height / 2); + queryRect = NSInsetRect(queryRect, -MLNAnnotationImagePaddingForHitTest, + -MLNAnnotationImagePaddingForHitTest); + std::vector nearbyAnnotations = [self annotationTagsInRect:queryRect]; + std::vector nearbyShapeAnnotations = [self shapeAnnotationTagsInRect:queryRect]; - if (nearbyShapeAnnotations.size()) { - nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), nearbyShapeAnnotations.end()); - } + if (nearbyShapeAnnotations.size()) { + nearbyAnnotations.insert(nearbyAnnotations.end(), nearbyShapeAnnotations.begin(), + nearbyShapeAnnotations.end()); + } - if (nearbyAnnotations.size()) { - // Assume that the user is fat-fingering an annotation. - NSRect hitRect = NSInsetRect({ point, NSZeroSize }, - -MLNAnnotationImagePaddingForHitTest, - -MLNAnnotationImagePaddingForHitTest); + if (nearbyAnnotations.size()) { + // Assume that the user is fat-fingering an annotation. + NSRect hitRect = NSInsetRect({point, NSZeroSize}, -MLNAnnotationImagePaddingForHitTest, + -MLNAnnotationImagePaddingForHitTest); - // Filter out any annotation whose image is unselectable or for which - // hit testing fails. - auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MLNAnnotationTag annotationTag) { - id annotation = [self annotationWithTag:annotationTag]; - NSAssert(annotation, @"Unknown annotation found nearby click"); - if (!annotation) { - return true; - } + // Filter out any annotation whose image is unselectable or for which + // hit testing fails. + auto end = std::remove_if( + nearbyAnnotations.begin(), nearbyAnnotations.end(), + [&](const MLNAnnotationTag annotationTag) { + id annotation = [self annotationWithTag:annotationTag]; + NSAssert(annotation, @"Unknown annotation found nearby click"); + if (!annotation) { + return true; + } - if ([annotation isKindOfClass:[MLNMultiPoint class]]) - { - if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) { - return !!(![self.delegate mapView:self shapeAnnotationIsEnabled:(MLNMultiPoint *)annotation]); - } else { - return false; - } - } - - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if (!annotationImage.selectable) { - return true; - } - - // Filter out the annotation if the fattened finger didn’t land on a - // translucent or opaque pixel in the image. - NSRect annotationRect = [self frameOfImage:annotationImage.image - centeredAtCoordinate:annotation.coordinate]; - return !!![annotationImage.image hitTestRect:hitRect withImageDestinationRect:annotationRect - context:nil hints:nil flipped:NO]; - }); - nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); - } - - MLNAnnotationTag hitAnnotationTag = MLNAnnotationTagNotFound; - if (nearbyAnnotations.size()) { - // The first selection in the cycle should be the one nearest to the - // tap. Also the annotation tags need to be stable in order to compare them with - // the remembered tags _annotationsNearbyLastClick. - CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self]; - std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { - CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; - CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; - CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude, - coordinateA.longitude - currentCoordinate.longitude); - CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude, - coordinateB.longitude - currentCoordinate.longitude); - return deltaA < deltaB; - }); - - if (nearbyAnnotations == _annotationsNearbyLastClick) { - // The last time we persisted a set of annotations, we had the same - // set of annotations as we do now. Cycle through them. - if (_lastSelectedAnnotationTag == MLNAnnotationTagNotFound - || _lastSelectedAnnotationTag == nearbyAnnotations.back()) { - // Either no annotation is selected or the last annotation in - // the set was selected. Wrap around to the first annotation in - // the set. - hitAnnotationTag = nearbyAnnotations.front(); + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + if ([self.delegate respondsToSelector:@selector(mapView:shapeAnnotationIsEnabled:)]) { + return !!(![self.delegate mapView:self + shapeAnnotationIsEnabled:(MLNMultiPoint *)annotation]); } else { - auto result = std::find(nearbyAnnotations.begin(), - nearbyAnnotations.end(), - _lastSelectedAnnotationTag); - if (result == nearbyAnnotations.end()) { - // An annotation from this set hasn’t been selected before. - // Select the first (nearest) one. - hitAnnotationTag = nearbyAnnotations.front(); - } else { - // Step to the next annotation in the set. - auto distance = std::distance(nearbyAnnotations.begin(), result); - hitAnnotationTag = nearbyAnnotations[distance + 1]; - } + return false; } + } + + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + if (!annotationImage.selectable) { + return true; + } + + // Filter out the annotation if the fattened finger didn’t land on a + // translucent or opaque pixel in the image. + NSRect annotationRect = [self frameOfImage:annotationImage.image + centeredAtCoordinate:annotation.coordinate]; + return !!![annotationImage.image hitTestRect:hitRect + withImageDestinationRect:annotationRect + context:nil + hints:nil + flipped:NO]; + }); + nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); + } + + MLNAnnotationTag hitAnnotationTag = MLNAnnotationTagNotFound; + if (nearbyAnnotations.size()) { + // The first selection in the cycle should be the one nearest to the + // tap. Also the annotation tags need to be stable in order to compare them with + // the remembered tags _annotationsNearbyLastClick. + CLLocationCoordinate2D currentCoordinate = [self convertPoint:point toCoordinateFromView:self]; + std::sort( + nearbyAnnotations.begin(), nearbyAnnotations.end(), + [&](const MLNAnnotationTag tagA, const MLNAnnotationTag tagB) { + CLLocationCoordinate2D coordinateA = [[self annotationWithTag:tagA] coordinate]; + CLLocationCoordinate2D coordinateB = [[self annotationWithTag:tagB] coordinate]; + CLLocationDegrees deltaA = hypot(coordinateA.latitude - currentCoordinate.latitude, + coordinateA.longitude - currentCoordinate.longitude); + CLLocationDegrees deltaB = hypot(coordinateB.latitude - currentCoordinate.latitude, + coordinateB.longitude - currentCoordinate.longitude); + return deltaA < deltaB; + }); + + if (nearbyAnnotations == _annotationsNearbyLastClick) { + // The last time we persisted a set of annotations, we had the same + // set of annotations as we do now. Cycle through them. + if (_lastSelectedAnnotationTag == MLNAnnotationTagNotFound || + _lastSelectedAnnotationTag == nearbyAnnotations.back()) { + // Either no annotation is selected or the last annotation in + // the set was selected. Wrap around to the first annotation in + // the set. + hitAnnotationTag = nearbyAnnotations.front(); + } else { + auto result = std::find(nearbyAnnotations.begin(), nearbyAnnotations.end(), + _lastSelectedAnnotationTag); + if (result == nearbyAnnotations.end()) { + // An annotation from this set hasn’t been selected before. + // Select the first (nearest) one. + hitAnnotationTag = nearbyAnnotations.front(); } else { - // Remember the nearby annotations for the next time this method is - // called. - if (persist) { - _annotationsNearbyLastClick = nearbyAnnotations; - } - - // Choose the first nearby annotation. - if (nearbyAnnotations.size()) { - hitAnnotationTag = nearbyAnnotations.front(); - } + // Step to the next annotation in the set. + auto distance = std::distance(nearbyAnnotations.begin(), result); + hitAnnotationTag = nearbyAnnotations[distance + 1]; } - } + } + } else { + // Remember the nearby annotations for the next time this method is + // called. + if (persist) { + _annotationsNearbyLastClick = nearbyAnnotations; + } - return hitAnnotationTag; + // Choose the first nearby annotation. + if (nearbyAnnotations.size()) { + hitAnnotationTag = nearbyAnnotations.front(); + } + } + } + + return hitAnnotationTag; } /// Returns the tags of the annotations coincident with the given rectangle. - (std::vector)annotationTagsInRect:(NSRect)rect { - // Cocoa origin is at the lower-left corner. - return self.renderer->queryPointAnnotations({ - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }); + // Cocoa origin is at the lower-left corner. + return self.renderer->queryPointAnnotations({ + {NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect)}, + {NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect)}, + }); } - (std::vector)shapeAnnotationTagsInRect:(NSRect)rect { - // Cocoa origin is at the lower-left corner. - return _rendererFrontend->getRenderer()->queryShapeAnnotations({ - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }); + // Cocoa origin is at the lower-left corner. + return _rendererFrontend->getRenderer()->queryShapeAnnotations({ + {NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect)}, + {NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect)}, + }); } -- (id )selectedAnnotation { - if ( ! _annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || - _selectedAnnotationTag == MLNAnnotationTagNotFound) { - return nil; +- (id)selectedAnnotation { + if (!_annotationContextsByAnnotationTag.count(_selectedAnnotationTag) || + _selectedAnnotationTag == MLNAnnotationTagNotFound) { + return nil; + } + + MLNAnnotationContext &annotationContext = + _annotationContextsByAnnotationTag.at(_selectedAnnotationTag); + return annotationContext.annotation; +} + +- (void)setSelectedAnnotation:(id)annotation { + MLNLogDebug(@"Selecting annotation: %@", annotation); + [self willChangeValueForKey:@"selectedAnnotations"]; + _selectedAnnotationTag = [self annotationTagForAnnotation:annotation]; + if (_selectedAnnotationTag != MLNAnnotationTagNotFound) { + _lastSelectedAnnotationTag = _selectedAnnotationTag; + } + [self didChangeValueForKey:@"selectedAnnotations"]; +} + +- (NSArray> *)selectedAnnotations { + id selectedAnnotation = self.selectedAnnotation; + return selectedAnnotation ? @[ selectedAnnotation ] : @[]; +} + +- (void)setSelectedAnnotations:(NSArray> *)selectedAnnotations { + MLNLogDebug(@"Selecting: %lu annotations", selectedAnnotations.count); + if (!selectedAnnotations.count) { + return; + } + + id firstAnnotation = selectedAnnotations[0]; + NSAssert([firstAnnotation conformsToProtocol:@protocol(MLNAnnotation)], + @"Annotation does not conform to MLNAnnotation"); + if ([firstAnnotation isKindOfClass:[MLNMultiPoint class]]) { + return; + } + + [self selectAnnotation:firstAnnotation]; +} + +- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id)annotation + animated:(BOOL)animated { + // Consider delegating + return [annotation isKindOfClass:[MLNPointAnnotation class]]; +} + +- (void)selectAnnotation:(id)annotation { + MLNLogDebug(@"Selecting annotation: %@", annotation); + [self selectAnnotation:annotation atPoint:NSZeroPoint]; +} + +- (void)selectAnnotation:(id)annotation atPoint:(NSPoint)gesturePoint { + MLNLogDebug(@"Selecting annotation: %@ atPoint: %@", annotation, NSStringFromPoint(gesturePoint)); + [self selectAnnotation:annotation atPoint:gesturePoint moveIntoView:YES animateSelection:YES]; +} + +- (void)selectAnnotation:(id)annotation + atPoint:(NSPoint)gesturePoint + moveIntoView:(BOOL)moveIntoView + animateSelection:(BOOL)animateSelection { + MLNLogDebug(@"Selecting annotation: %@ atPoint: %@ moveIntoView: %@ animateSelection: %@", + annotation, NSStringFromPoint(gesturePoint), MLNStringFromBOOL(moveIntoView), + MLNStringFromBOOL(animateSelection)); + id selectedAnnotation = self.selectedAnnotation; + if (annotation == selectedAnnotation) { + return; + } + + // Deselect the annotation before reselecting it. + [self deselectAnnotation:selectedAnnotation]; + + // Add the annotation to the map if it hasn’t been added yet. + MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; + if (annotationTag == MLNAnnotationTagNotFound) { + [self addAnnotation:annotation]; + } + + if (moveIntoView) { + moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation + animated:animateSelection]; + } + + // The annotation's anchor will bounce to the current click. + NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; + + // Check for invalid (zero) positioning rect + if (NSEqualRects(positioningRect, NSZeroRect)) { + CLLocationCoordinate2D origin = annotation.coordinate; + positioningRect.origin = [self convertCoordinate:origin toPointToView:self]; + } + + BOOL shouldShowCallout = ([annotation respondsToSelector:@selector(title)] && annotation.title && + !self.calloutForSelectedAnnotation.shown && + [self.delegate respondsToSelector:@selector(mapView: + annotationCanShowCallout:)] && + [self.delegate mapView:self annotationCanShowCallout:annotation]); + + if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { + if (!moveIntoView && !NSEqualPoints(gesturePoint, NSZeroPoint)) { + positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, + positioningRect.size.height); + } + } + // Onscreen or partially on-screen + else if (!shouldShowCallout) { + moveIntoView = NO; + } + + self.selectedAnnotation = annotation; + + // For the callout to be shown, the annotation must have a title, its + // callout must not already be shown, and the annotation must be able to + // show a callout according to the delegate. + if (shouldShowCallout) { + NSPopover *callout = [self calloutForAnnotation:annotation]; + + // Hang the callout off the right edge of the annotation image’s + // alignment rect, or off the left edge in a right-to-left UI. + callout.delegate = self; + self.calloutForSelectedAnnotation = callout; + + NSRectEdge edge = + (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft + ? NSMinXEdge + : NSMaxXEdge); + + // The following will do nothing if the positioning rect is not on-screen. See + // ``MLNMapView/updateAnnotationCallouts`` for presenting the callout when the selected + // annotation comes back on-screen. + [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge]; + } + + if (moveIntoView) { + moveIntoView = NO; + + NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { + return NSMakeRect(rect.origin.x + insets.left, rect.origin.y + insets.bottom, + rect.size.width - insets.left - insets.right, + rect.size.height - insets.top - insets.bottom); + }; + + // Add padding around the positioning rect (in essence an inset from the edge of the viewport + NSRect expandedPositioningRect = positioningRect; + + if (shouldShowCallout) { + // If we have a callout, expand this rect to include a buffer + expandedPositioningRect = + edgeInsetsInsetRect(positioningRect, MLNMapViewOffscreenAnnotationPadding); } - MLNAnnotationContext &annotationContext = _annotationContextsByAnnotationTag.at(_selectedAnnotationTag); - return annotationContext.annotation; -} + // Used for callout positioning, and moving offscreen annotations onscreen. + CGRect constrainedRect = edgeInsetsInsetRect(self.bounds, self.contentInsets); + CGRect bounds = constrainedRect; -- (void)setSelectedAnnotation:(id )annotation { - MLNLogDebug(@"Selecting annotation: %@", annotation); - [self willChangeValueForKey:@"selectedAnnotations"]; - _selectedAnnotationTag = [self annotationTagForAnnotation:annotation]; - if (_selectedAnnotationTag != MLNAnnotationTagNotFound) { - _lastSelectedAnnotationTag = _selectedAnnotationTag; - } - [self didChangeValueForKey:@"selectedAnnotations"]; -} + // Any one of these cases should trigger a move onscreen + CGFloat minX = CGRectGetMinX(expandedPositioningRect); -- (NSArray> *)selectedAnnotations { - id selectedAnnotation = self.selectedAnnotation; - return selectedAnnotation ? @[selectedAnnotation] : @[]; -} + if (minX < CGRectGetMinX(bounds)) { + constrainedRect.origin.x = minX; + moveIntoView = YES; + } else { + CGFloat maxX = CGRectGetMaxX(expandedPositioningRect); -- (void)setSelectedAnnotations:(NSArray> *)selectedAnnotations { - MLNLogDebug(@"Selecting: %lu annotations", selectedAnnotations.count); - if (!selectedAnnotations.count) { - return; + if (maxX > CGRectGetMaxX(bounds)) { + constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect); + moveIntoView = YES; + } } - id firstAnnotation = selectedAnnotations[0]; - NSAssert([firstAnnotation conformsToProtocol:@protocol(MLNAnnotation)], @"Annotation does not conform to MLNAnnotation"); - if ([firstAnnotation isKindOfClass:[MLNMultiPoint class]]) { - return; - } + CGFloat minY = CGRectGetMinY(expandedPositioningRect); - [self selectAnnotation:firstAnnotation]; -} + if (minY < CGRectGetMinY(bounds)) { + constrainedRect.origin.y = minY; + moveIntoView = YES; + } else { + CGFloat maxY = CGRectGetMaxY(expandedPositioningRect); -- (BOOL)isMovingAnnotationIntoViewSupportedForAnnotation:(id)annotation animated:(BOOL)animated { - // Consider delegating - return [annotation isKindOfClass:[MLNPointAnnotation class]]; -} - -- (void)selectAnnotation:(id )annotation -{ - MLNLogDebug(@"Selecting annotation: %@", annotation); - [self selectAnnotation:annotation atPoint:NSZeroPoint]; -} - -- (void)selectAnnotation:(id )annotation atPoint:(NSPoint)gesturePoint -{ - MLNLogDebug(@"Selecting annotation: %@ atPoint: %@", annotation, NSStringFromPoint(gesturePoint)); - [self selectAnnotation:annotation atPoint:gesturePoint moveIntoView:YES animateSelection:YES]; -} - -- (void)selectAnnotation:(id )annotation atPoint:(NSPoint)gesturePoint moveIntoView:(BOOL)moveIntoView animateSelection:(BOOL)animateSelection -{ - MLNLogDebug(@"Selecting annotation: %@ atPoint: %@ moveIntoView: %@ animateSelection: %@", annotation, NSStringFromPoint(gesturePoint), MLNStringFromBOOL(moveIntoView), MLNStringFromBOOL(animateSelection)); - id selectedAnnotation = self.selectedAnnotation; - if (annotation == selectedAnnotation) { - return; - } - - // Deselect the annotation before reselecting it. - [self deselectAnnotation:selectedAnnotation]; - - // Add the annotation to the map if it hasn’t been added yet. - MLNAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; - if (annotationTag == MLNAnnotationTagNotFound) { - [self addAnnotation:annotation]; + if (maxY > CGRectGetMaxY(bounds)) { + constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect); + moveIntoView = YES; + } } if (moveIntoView) { - moveIntoView = [self isMovingAnnotationIntoViewSupportedForAnnotation:annotation animated:animateSelection]; - } - - // The annotation's anchor will bounce to the current click. - NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; - - // Check for invalid (zero) positioning rect - if (NSEqualRects(positioningRect, NSZeroRect)) { - CLLocationCoordinate2D origin = annotation.coordinate; - positioningRect.origin = [self convertCoordinate:origin toPointToView:self]; - } - - BOOL shouldShowCallout = ([annotation respondsToSelector:@selector(title)] - && annotation.title - && !self.calloutForSelectedAnnotation.shown - && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] - && [self.delegate mapView:self annotationCanShowCallout:annotation]); - - if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { - if (!moveIntoView && !NSEqualPoints(gesturePoint, NSZeroPoint)) { - positioningRect = CGRectMake(gesturePoint.x, gesturePoint.y, positioningRect.size.width, positioningRect.size.height); - } - } - // Onscreen or partially on-screen - else if (!shouldShowCallout) { - moveIntoView = NO; - } - - self.selectedAnnotation = annotation; - - // For the callout to be shown, the annotation must have a title, its - // callout must not already be shown, and the annotation must be able to - // show a callout according to the delegate. - if (shouldShowCallout) { - NSPopover *callout = [self calloutForAnnotation:annotation]; - - // Hang the callout off the right edge of the annotation image’s - // alignment rect, or off the left edge in a right-to-left UI. - callout.delegate = self; - self.calloutForSelectedAnnotation = callout; - - NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft - ? NSMinXEdge - : NSMaxXEdge); - - // The following will do nothing if the positioning rect is not on-screen. See - // ``MLNMapView/updateAnnotationCallouts`` for presenting the callout when the selected - // annotation comes back on-screen. - [callout showRelativeToRect:positioningRect ofView:self preferredEdge:edge]; - } - - if (moveIntoView) - { - moveIntoView = NO; - - NSRect (^edgeInsetsInsetRect)(NSRect, NSEdgeInsets) = ^(NSRect rect, NSEdgeInsets insets) { - return NSMakeRect(rect.origin.x + insets.left, - rect.origin.y + insets.bottom, - rect.size.width - insets.left - insets.right, - rect.size.height - insets.top - insets.bottom); - }; - - // Add padding around the positioning rect (in essence an inset from the edge of the viewport - NSRect expandedPositioningRect = positioningRect; - - if (shouldShowCallout) { - // If we have a callout, expand this rect to include a buffer - expandedPositioningRect = edgeInsetsInsetRect(positioningRect, MLNMapViewOffscreenAnnotationPadding); - } - - // Used for callout positioning, and moving offscreen annotations onscreen. - CGRect constrainedRect = edgeInsetsInsetRect(self.bounds, self.contentInsets); - CGRect bounds = constrainedRect; - - // Any one of these cases should trigger a move onscreen - CGFloat minX = CGRectGetMinX(expandedPositioningRect); - - if (minX < CGRectGetMinX(bounds)) { - constrainedRect.origin.x = minX; - moveIntoView = YES; - } - else { - CGFloat maxX = CGRectGetMaxX(expandedPositioningRect); - - if (maxX > CGRectGetMaxX(bounds)) { - constrainedRect.origin.x = maxX - CGRectGetWidth(constrainedRect); - moveIntoView = YES; - } - } - - CGFloat minY = CGRectGetMinY(expandedPositioningRect); - - if (minY < CGRectGetMinY(bounds)) { - constrainedRect.origin.y = minY; - moveIntoView = YES; - } - else { - CGFloat maxY = CGRectGetMaxY(expandedPositioningRect); - - if (maxY > CGRectGetMaxY(bounds)) { - constrainedRect.origin.y = maxY - CGRectGetHeight(constrainedRect); - moveIntoView = YES; - } - } - - if (moveIntoView) - { - CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); - CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; - [self setCenterCoordinate:centerCoord animated:animateSelection]; - } + CGPoint center = CGPointMake(CGRectGetMidX(constrainedRect), CGRectGetMidY(constrainedRect)); + CLLocationCoordinate2D centerCoord = [self convertPoint:center toCoordinateFromView:self]; + [self setCenterCoordinate:centerCoord animated:animateSelection]; } + } } -- (void)showAnnotations:(NSArray> *)annotations animated:(BOOL)animated { - MLNLogDebug(@"Showing: %lu annotations animated: %@", annotations.count, MLNStringFromBOOL(animated)); - CGFloat maximumPadding = 100; - CGFloat yPadding = (NSHeight(self.bounds) / 5 <= maximumPadding) ? (NSHeight(self.bounds) / 5) : maximumPadding; - CGFloat xPadding = (NSWidth(self.bounds) / 5 <= maximumPadding) ? (NSWidth(self.bounds) / 5) : maximumPadding; +- (void)showAnnotations:(NSArray> *)annotations animated:(BOOL)animated { + MLNLogDebug(@"Showing: %lu annotations animated: %@", annotations.count, + MLNStringFromBOOL(animated)); + CGFloat maximumPadding = 100; + CGFloat yPadding = + (NSHeight(self.bounds) / 5 <= maximumPadding) ? (NSHeight(self.bounds) / 5) : maximumPadding; + CGFloat xPadding = + (NSWidth(self.bounds) / 5 <= maximumPadding) ? (NSWidth(self.bounds) / 5) : maximumPadding; - NSEdgeInsets edgeInsets = NSEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding); + NSEdgeInsets edgeInsets = NSEdgeInsetsMake(yPadding, xPadding, yPadding, xPadding); - [self showAnnotations:annotations edgePadding:edgeInsets animated:animated]; + [self showAnnotations:annotations edgePadding:edgeInsets animated:animated]; } -- (void)showAnnotations:(NSArray> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated { - [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil]; +- (void)showAnnotations:(NSArray> *)annotations + edgePadding:(NSEdgeInsets)insets + animated:(BOOL)animated { + [self showAnnotations:annotations edgePadding:insets animated:animated completionHandler:nil]; } -- (void)showAnnotations:(NSArray> *)annotations edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion { - if (!annotations.count) { - if (completion) { - completion(); - } - return; +- (void)showAnnotations:(NSArray> *)annotations + edgePadding:(NSEdgeInsets)insets + animated:(BOOL)animated + completionHandler:(nullable void (^)(void))completion { + if (!annotations.count) { + if (completion) { + completion(); } + return; + } - mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); + mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); - for (id annotation in annotations) { - if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) { - bounds.extend(MLNLatLngBoundsFromCoordinateBounds(((id )annotation).overlayBounds)); - } else { - bounds.extend(MLNLatLngFromLocationCoordinate2D(annotation.coordinate)); - } + for (id annotation in annotations) { + if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) { + bounds.extend( + MLNLatLngBoundsFromCoordinateBounds(((id)annotation).overlayBounds)); + } else { + bounds.extend(MLNLatLngFromLocationCoordinate2D(annotation.coordinate)); } + } - [self setVisibleCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(bounds) - edgePadding:insets - animated:animated - completionHandler:completion]; + [self setVisibleCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(bounds) + edgePadding:insets + animated:animated + completionHandler:completion]; } /// Returns a popover detailing the annotation. -- (NSPopover *)calloutForAnnotation:(id )annotation { - NSPopover *callout = [[NSPopover alloc] init]; - callout.behavior = NSPopoverBehaviorTransient; +- (NSPopover *)calloutForAnnotation:(id)annotation { + NSPopover *callout = [[NSPopover alloc] init]; + callout.behavior = NSPopoverBehaviorTransient; - NSViewController *viewController; - if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) { - NSViewController *viewControllerFromDelegate = [self.delegate mapView:self - calloutViewControllerForAnnotation:annotation]; - if (viewControllerFromDelegate) { - viewController = viewControllerFromDelegate; - } + NSViewController *viewController; + if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) { + NSViewController *viewControllerFromDelegate = [self.delegate mapView:self + calloutViewControllerForAnnotation:annotation]; + if (viewControllerFromDelegate) { + viewController = viewControllerFromDelegate; } - if (!viewController) { - viewController = self.calloutViewController; - } - NSAssert(viewController, @"Unable to load MLNAnnotationCallout view controller"); - // The popover’s view controller can bind to KVO-compliant key paths of the - // annotation. - viewController.representedObject = annotation; - callout.contentViewController = viewController; + } + if (!viewController) { + viewController = self.calloutViewController; + } + NSAssert(viewController, @"Unable to load MLNAnnotationCallout view controller"); + // The popover’s view controller can bind to KVO-compliant key paths of the + // annotation. + viewController.representedObject = annotation; + callout.contentViewController = viewController; - return callout; + return callout; } - (NSViewController *)calloutViewController { - // Lazily load a default view controller. - if (!_calloutViewController) { - _calloutViewController = [[NSViewController alloc] initWithNibName:@"MLNAnnotationCallout" - bundle:[NSBundle mgl_frameworkBundle]]; - } - return _calloutViewController; + // Lazily load a default view controller. + if (!_calloutViewController) { + _calloutViewController = + [[NSViewController alloc] initWithNibName:@"MLNAnnotationCallout" + bundle:[NSBundle mgl_frameworkBundle]]; + } + return _calloutViewController; } /// Returns the rectangle that represents the annotation image of the annotation /// with the given tag. This rectangle is fitted to the image’s alignment rect /// and is appropriate for positioning a popover. - (NSRect)positioningRectForCalloutForAnnotationWithTag:(MLNAnnotationTag)annotationTag { - id annotation = [self annotationWithTag:annotationTag]; - if (!annotation) { - return NSZeroRect; - } - if ([annotation isKindOfClass:[MLNMultiPoint class]]) { - CLLocationCoordinate2D origin = annotation.coordinate; - CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; - return CGRectMake(originPoint.x, originPoint.y, MLNAnnotationImagePaddingForHitTest, MLNAnnotationImagePaddingForHitTest); + id annotation = [self annotationWithTag:annotationTag]; + if (!annotation) { + return NSZeroRect; + } + if ([annotation isKindOfClass:[MLNMultiPoint class]]) { + CLLocationCoordinate2D origin = annotation.coordinate; + CGPoint originPoint = [self convertCoordinate:origin toPointToView:self]; + return CGRectMake(originPoint.x, originPoint.y, MLNAnnotationImagePaddingForHitTest, + MLNAnnotationImagePaddingForHitTest); + } - } + NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image; + if (!image) { + image = + [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName].image; + } + if (!image) { + return NSZeroRect; + } - NSImage *image = [self imageOfAnnotationWithTag:annotationTag].image; - if (!image) { - image = [self dequeueReusableAnnotationImageWithIdentifier:MLNDefaultStyleMarkerSymbolName].image; - } - if (!image) { - return NSZeroRect; - } - - NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; - positioningRect = NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y); - return NSInsetRect(positioningRect, -MLNAnnotationImagePaddingForCallout, - -MLNAnnotationImagePaddingForCallout); + NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; + positioningRect = + NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y); + return NSInsetRect(positioningRect, -MLNAnnotationImagePaddingForCallout, + -MLNAnnotationImagePaddingForCallout); } /// Returns the rectangle relative to the viewport that represents the given /// image centered at the given coordinate. - (NSRect)frameOfImage:(NSImage *)image centeredAtCoordinate:(CLLocationCoordinate2D)coordinate { - NSPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self]; - return NSInsetRect({ calloutAnchorPoint, NSZeroSize }, -image.size.width / 2, -image.size.height / 2); + NSPoint calloutAnchorPoint = [self convertCoordinate:coordinate toPointToView:self]; + return NSInsetRect({calloutAnchorPoint, NSZeroSize}, -image.size.width / 2, + -image.size.height / 2); } /// Returns the annotation image assigned to the annotation with the given tag. - (MLNAnnotationImage *)imageOfAnnotationWithTag:(MLNAnnotationTag)annotationTag { - if (annotationTag == MLNAnnotationTagNotFound - || _annotationContextsByAnnotationTag.count(annotationTag) == 0) { - return nil; - } + if (annotationTag == MLNAnnotationTagNotFound || + _annotationContextsByAnnotationTag.count(annotationTag) == 0) { + return nil; + } - NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; - NSString *symbolName = customSymbol.length ? customSymbol : MLNDefaultStyleMarkerSymbolName; + NSString *customSymbol = + _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; + NSString *symbolName = customSymbol.length ? customSymbol : MLNDefaultStyleMarkerSymbolName; - return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; + return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; } -- (void)deselectAnnotation:(id )annotation { - if (!annotation || self.selectedAnnotation != annotation) { - return; - } +- (void)deselectAnnotation:(id)annotation { + if (!annotation || self.selectedAnnotation != annotation) { + return; + } - // Close the callout popover gracefully. - NSPopover *callout = self.calloutForSelectedAnnotation; - [callout performClose:self]; + // Close the callout popover gracefully. + NSPopover *callout = self.calloutForSelectedAnnotation; + [callout performClose:self]; - self.selectedAnnotation = nil; + self.selectedAnnotation = nil; } /// Move the annotation callout to point to the selected annotation at its /// current position. - (void)updateAnnotationCallouts { - NSPopover *callout = self.calloutForSelectedAnnotation; - if (callout) { - NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; + NSPopover *callout = self.calloutForSelectedAnnotation; + if (callout) { + NSRect rect = [self positioningRectForCalloutForAnnotationWithTag:_selectedAnnotationTag]; - NSAssert(!NSEqualRects(rect, NSZeroRect), @"Positioning rect should be non-zero"); + NSAssert(!NSEqualRects(rect, NSZeroRect), @"Positioning rect should be non-zero"); - if (!NSIsEmptyRect(NSIntersectionRect(rect, self.bounds))) { - - // It's possible that the current callout hasn't been presented (since the original - // positioningRect was offscreen). We can check that the callout has a valid window - // This results in the callout being presented just as the annotation comes on screen - // which matches MapKit, but (currently) not iOS. - if (!callout.contentViewController.view.window) { - NSRectEdge edge = (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft - ? NSMinXEdge - : NSMaxXEdge); - // Re-present the callout - [callout showRelativeToRect:rect ofView:self preferredEdge:edge]; - } - else { - callout.positioningRect = rect; - } - } + if (!NSIsEmptyRect(NSIntersectionRect(rect, self.bounds))) { + // It's possible that the current callout hasn't been presented (since the original + // positioningRect was offscreen). We can check that the callout has a valid window + // This results in the callout being presented just as the annotation comes on screen + // which matches MapKit, but (currently) not iOS. + if (!callout.contentViewController.view.window) { + NSRectEdge edge = + (self.userInterfaceLayoutDirection == NSUserInterfaceLayoutDirectionRightToLeft + ? NSMinXEdge + : NSMaxXEdge); + // Re-present the callout + [callout showRelativeToRect:rect ofView:self preferredEdge:edge]; + } else { + callout.positioningRect = rect; + } } + } } // MARK: MLNMultiPointDelegate methods - (double)alphaForShapeAnnotation:(MLNShape *)annotation { - if (_delegateHasAlphasForShapeAnnotations) { - return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; - } - return 1.0; + if (_delegateHasAlphasForShapeAnnotations) { + return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; + } + return 1.0; } - (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation { - NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations - ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] - : [NSColor selectedMenuItemColor]); - return color.mgl_color; + NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations + ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] + : [NSColor selectedMenuItemColor]); + return color.mgl_color; } - (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation { - NSColor *color = (_delegateHasFillColorsForShapeAnnotations - ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] - : [NSColor selectedMenuItemColor]); - return color.mgl_color; + NSColor *color = (_delegateHasFillColorsForShapeAnnotations + ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] + : [NSColor selectedMenuItemColor]); + return color.mgl_color; } - (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation { - if (_delegateHasLineWidthsForShapeAnnotations) { - return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MLNPolyline *)annotation]; - } - return 3.0; + if (_delegateHasLineWidthsForShapeAnnotations) { + return [self.delegate mapView:self lineWidthForPolylineAnnotation:(MLNPolyline *)annotation]; + } + return 3.0; } // MARK: MLNPopoverDelegate methods - (void)popoverDidShow:(__unused NSNotification *)notification { - id annotation = self.selectedAnnotation; - if (annotation && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) { - [self.delegate mapView:self didSelectAnnotation:annotation]; - } + id annotation = self.selectedAnnotation; + if (annotation && [self.delegate respondsToSelector:@selector(mapView:didSelectAnnotation:)]) { + [self.delegate mapView:self didSelectAnnotation:annotation]; + } } - (void)popoverDidClose:(__unused NSNotification *)notification { - // Deselect the closed popover, in case the popover was closed due to user - // action. - id annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject; - self.calloutForSelectedAnnotation = nil; - self.selectedAnnotation = nil; + // Deselect the closed popover, in case the popover was closed due to user + // action. + id annotation = + self.calloutForSelectedAnnotation.contentViewController.representedObject; + self.calloutForSelectedAnnotation = nil; + self.selectedAnnotation = nil; - if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) { - [self.delegate mapView:self didDeselectAnnotation:annotation]; - } + if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) { + [self.delegate mapView:self didDeselectAnnotation:annotation]; + } } // MARK: Overlays -- (nonnull NSArray> *)overlays -{ - if (self.annotations == nil) { return @[]; } +- (nonnull NSArray> *)overlays { + if (self.annotations == nil) { + return @[]; + } - NSMutableArray> *mutableOverlays = [NSMutableArray array]; + NSMutableArray> *mutableOverlays = [NSMutableArray array]; - [self.annotations enumerateObjectsUsingBlock:^(id _Nonnull annotation, NSUInteger idx, BOOL * _Nonnull stop) { - if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) - { - [mutableOverlays addObject:(id)annotation]; - } - }]; - - return [NSArray arrayWithArray:mutableOverlays]; -} - -- (void)addOverlay:(id )overlay { - MLNLogDebug(@"Adding overlay: %@", overlay); - [self addOverlays:@[overlay]]; -} - -- (void)addOverlays:(NSArray> *)overlays -{ - MLNLogDebug(@"Adding: %lu overlays", overlays.count); -#if DEBUG - for (id overlay in overlays) { - NSAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], @"Overlay does not conform to MLNOverlay"); + [self.annotations enumerateObjectsUsingBlock:^(id _Nonnull annotation, + NSUInteger idx, BOOL *_Nonnull stop) { + if ([annotation conformsToProtocol:@protocol(MLNOverlay)]) { + [mutableOverlays addObject:(id)annotation]; } -#endif - [self addAnnotations:overlays]; + }]; + + return [NSArray arrayWithArray:mutableOverlays]; } -- (void)removeOverlay:(id )overlay { - MLNLogDebug(@"Removing overlay: %@", overlay); - [self removeOverlays:@[overlay]]; +- (void)addOverlay:(id)overlay { + MLNLogDebug(@"Adding overlay: %@", overlay); + [self addOverlays:@[ overlay ]]; } -- (void)removeOverlays:(NSArray> *)overlays { - MLNLogDebug(@"Removing: %lu overlays", overlays.count); +- (void)addOverlays:(NSArray> *)overlays { + MLNLogDebug(@"Adding: %lu overlays", overlays.count); #if DEBUG - for (id overlay in overlays) { - NSAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], @"Overlay does not conform to MLNOverlay"); - } + for (id overlay in overlays) { + NSAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], + @"Overlay does not conform to MLNOverlay"); + } #endif - [self removeAnnotations:overlays]; + [self addAnnotations:overlays]; +} + +- (void)removeOverlay:(id)overlay { + MLNLogDebug(@"Removing overlay: %@", overlay); + [self removeOverlays:@[ overlay ]]; +} + +- (void)removeOverlays:(NSArray> *)overlays { + MLNLogDebug(@"Removing: %lu overlays", overlays.count); +#if DEBUG + for (id overlay in overlays) { + NSAssert([overlay conformsToProtocol:@protocol(MLNOverlay)], + @"Overlay does not conform to MLNOverlay"); + } +#endif + [self removeAnnotations:overlays]; } // MARK: Tooltips and cursors - (void)updateAnnotationTrackingAreas { - if (_wantsToolTipRects) { - [self removeAllToolTips]; - std::vector annotationTags = [self annotationTagsInRect:self.bounds]; - for (MLNAnnotationTag annotationTag : annotationTags) { - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - id annotation = [self annotationWithTag:annotationTag]; - if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { - // Add a tooltip tracking area over the annotation image’s - // frame, accounting for the image’s alignment rect. - NSImage *image = annotationImage.image; - NSRect annotationRect = [self frameOfImage:image - centeredAtCoordinate:annotation.coordinate]; - annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); - if (!NSIsEmptyRect(annotationRect)) { - [self addToolTipRect:annotationRect owner:self userData:(void *)(NSUInteger)annotationTag]; - } - } - // Opt into potentially expensive cursor tracking areas. - if (annotationImage.cursor) { - _wantsCursorRects = YES; - } + if (_wantsToolTipRects) { + [self removeAllToolTips]; + std::vector annotationTags = [self annotationTagsInRect:self.bounds]; + for (MLNAnnotationTag annotationTag : annotationTags) { + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + id annotation = [self annotationWithTag:annotationTag]; + if ([annotation respondsToSelector:@selector(toolTip)] && annotation.toolTip.length) { + // Add a tooltip tracking area over the annotation image’s + // frame, accounting for the image’s alignment rect. + NSImage *image = annotationImage.image; + NSRect annotationRect = [self frameOfImage:image + centeredAtCoordinate:annotation.coordinate]; + annotationRect = + NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); + if (!NSIsEmptyRect(annotationRect)) { + [self addToolTipRect:annotationRect + owner:self + userData:(void *)(NSUInteger)annotationTag]; } + } + // Opt into potentially expensive cursor tracking areas. + if (annotationImage.cursor) { + _wantsCursorRects = YES; + } } + } - // Blow away any cursor tracking areas and rebuild them. That’s the - // potentially expensive part. - if (_wantsCursorRects) { - [self.window invalidateCursorRectsForView:self]; - } + // Blow away any cursor tracking areas and rebuild them. That’s the + // potentially expensive part. + if (_wantsCursorRects) { + [self.window invalidateCursorRectsForView:self]; + } } -- (NSString *)view:(__unused NSView *)view stringForToolTip:(__unused NSToolTipTag)tag point:(__unused NSPoint)point userData:(void *)data { - NSAssert((NSUInteger)data < MLNAnnotationTagNotFound, @"Invalid annotation tag in tooltip rect user data."); - MLNAnnotationTag annotationTag = (MLNAnnotationTag)MIN((NSUInteger)data, MLNAnnotationTagNotFound); - id annotation = [self annotationWithTag:annotationTag]; - return annotation.toolTip; +- (NSString *)view:(__unused NSView *)view + stringForToolTip:(__unused NSToolTipTag)tag + point:(__unused NSPoint)point + userData:(void *)data { + NSAssert((NSUInteger)data < MLNAnnotationTagNotFound, + @"Invalid annotation tag in tooltip rect user data."); + MLNAnnotationTag annotationTag = + (MLNAnnotationTag)MIN((NSUInteger)data, MLNAnnotationTagNotFound); + id annotation = [self annotationWithTag:annotationTag]; + return annotation.toolTip; } - (void)resetCursorRects { - // Drag to pan has a grabbing hand cursor. - if ([self isPanningWithGesture]) { - [self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]]; - return; - } + // Drag to pan has a grabbing hand cursor. + if ([self isPanningWithGesture]) { + [self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]]; + return; + } - // The rest of this method can be expensive, so bail if no annotations have - // ever had custom cursors. - if (!_wantsCursorRects) { - return; - } + // The rest of this method can be expensive, so bail if no annotations have + // ever had custom cursors. + if (!_wantsCursorRects) { + return; + } - std::vector annotationTags = [self annotationTagsInRect:self.bounds]; - for (MLNAnnotationTag annotationTag : annotationTags) { - id annotation = [self annotationWithTag:annotationTag]; - MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; - if (annotationImage.cursor) { - // Add a cursor tracking area over the annotation image, respecting - // the image’s alignment rect. - NSImage *image = annotationImage.image; - NSRect annotationRect = [self frameOfImage:image - centeredAtCoordinate:annotation.coordinate]; - annotationRect = NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); - [self addCursorRect:annotationRect cursor:annotationImage.cursor]; - } + std::vector annotationTags = [self annotationTagsInRect:self.bounds]; + for (MLNAnnotationTag annotationTag : annotationTags) { + id annotation = [self annotationWithTag:annotationTag]; + MLNAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; + if (annotationImage.cursor) { + // Add a cursor tracking area over the annotation image, respecting + // the image’s alignment rect. + NSImage *image = annotationImage.image; + NSRect annotationRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; + annotationRect = + NSOffsetRect(image.alignmentRect, annotationRect.origin.x, annotationRect.origin.y); + [self addCursorRect:annotationRect cursor:annotationImage.cursor]; } + } } // MARK: Data -- (NSArray> *)visibleFeaturesAtPoint:(NSPoint)point { - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromPoint(point)); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil]; +- (NSArray> *)visibleFeaturesAtPoint:(NSPoint)point { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@", NSStringFromPoint(point)); + return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:nil]; } -- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", NSStringFromPoint(point), styleLayerIdentifiers); - return [self visibleFeaturesAtPoint:point inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; +- (NSArray> *)visibleFeaturesAtPoint:(CGPoint)point + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@", + NSStringFromPoint(point), styleLayerIdentifiers); + return [self visibleFeaturesAtPoint:point + inStyleLayersWithIdentifiers:styleLayerIdentifiers + predicate:nil]; } -- (NSArray> *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers predicate:(NSPredicate *)predicate { - MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromPoint(point), styleLayerIdentifiers, predicate); - // Cocoa origin is at the lower-left corner. - mbgl::ScreenCoordinate screenCoordinate = { point.x, NSHeight(self.bounds) - point.y }; +- (NSArray> *)visibleFeaturesAtPoint:(NSPoint)point + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers + predicate:(NSPredicate *)predicate { + MLNLogDebug(@"Querying visibleFeaturesAtPoint: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", + NSStringFromPoint(point), styleLayerIdentifiers, predicate); + // Cocoa origin is at the lower-left corner. + mbgl::ScreenCoordinate screenCoordinate = {point.x, NSHeight(self.bounds) - point.y}; - std::optional> optionalLayerIDs; - if (styleLayerIdentifiers) { - __block std::vector layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); + std::optional> optionalLayerIDs; + if (styleLayerIdentifiers) { + __block std::vector layerIDs; + layerIDs.reserve(styleLayerIdentifiers.count); + [styleLayerIdentifiers + enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) { + layerIDs.push_back(identifier.UTF8String); }]; - optionalLayerIDs = layerIDs; - } + optionalLayerIDs = layerIDs; + } - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenCoordinate, { optionalLayerIDs, optionalFilter }); - return MLNFeaturesFromMBGLFeatures(features); + std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures( + screenCoordinate, {optionalLayerIDs, optionalFilter}); + return MLNFeaturesFromMBGLFeatures(features); } -- (NSArray> *)visibleFeaturesInRect:(NSRect)rect { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromRect(rect)); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil]; +- (NSArray> *)visibleFeaturesInRect:(NSRect)rect { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@", NSStringFromRect(rect)); + return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:nil]; } -- (NSArray> *)visibleFeaturesInRect:(CGRect)rect inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", NSStringFromRect(rect), styleLayerIdentifiers); - return [self visibleFeaturesInRect:rect inStyleLayersWithIdentifiers:styleLayerIdentifiers predicate:nil]; +- (NSArray> *)visibleFeaturesInRect:(CGRect)rect + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@", + NSStringFromRect(rect), styleLayerIdentifiers); + return [self visibleFeaturesInRect:rect + inStyleLayersWithIdentifiers:styleLayerIdentifiers + predicate:nil]; } -- (NSArray> *)visibleFeaturesInRect:(NSRect)rect inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers predicate:(NSPredicate *)predicate { - MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", NSStringFromRect(rect), styleLayerIdentifiers, predicate); - // Cocoa origin is at the lower-left corner. - mbgl::ScreenBox screenBox = { - { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, - { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, - }; +- (NSArray> *)visibleFeaturesInRect:(NSRect)rect + inStyleLayersWithIdentifiers:(NSSet *)styleLayerIdentifiers + predicate:(NSPredicate *)predicate { + MLNLogDebug(@"Querying visibleFeaturesInRect: %@ inStyleLayersWithIdentifiers: %@ predicate: %@", + NSStringFromRect(rect), styleLayerIdentifiers, predicate); + // Cocoa origin is at the lower-left corner. + mbgl::ScreenBox screenBox = { + {NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect)}, + {NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect)}, + }; - std::optional> optionalLayerIDs; - if (styleLayerIdentifiers) { - __block std::vector layerIDs; - layerIDs.reserve(styleLayerIdentifiers.count); - [styleLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { - layerIDs.push_back(identifier.UTF8String); + std::optional> optionalLayerIDs; + if (styleLayerIdentifiers) { + __block std::vector layerIDs; + layerIDs.reserve(styleLayerIdentifiers.count); + [styleLayerIdentifiers + enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) { + layerIDs.push_back(identifier.UTF8String); }]; - optionalLayerIDs = layerIDs; - } + optionalLayerIDs = layerIDs; + } - std::optional optionalFilter; - if (predicate) { - optionalFilter = predicate.mgl_filter; - } + std::optional optionalFilter; + if (predicate) { + optionalFilter = predicate.mgl_filter; + } - std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures(screenBox, { optionalLayerIDs, optionalFilter }); - return MLNFeaturesFromMBGLFeatures(features); + std::vector features = _rendererFrontend->getRenderer()->queryRenderedFeatures( + screenBox, {optionalLayerIDs, optionalFilter}); + return MLNFeaturesFromMBGLFeatures(features); } // MARK: User interface validation - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { - return NO; + return NO; } // MARK: Interface Builder methods - (void)prepareForInterfaceBuilder { - [super prepareForInterfaceBuilder]; + [super prepareForInterfaceBuilder]; - // Color the background a glorious Mapbox teal. - self.layer.borderColor = [NSColor colorWithRed:59/255. - green:178/255. - blue:208/255. - alpha:0.8].CGColor; - self.layer.borderWidth = 2; - self.layer.backgroundColor = [NSColor colorWithRed:59/255. - green:178/255. - blue:208/255. - alpha:0.6].CGColor; + // Color the background a glorious Mapbox teal. + self.layer.borderColor = + [NSColor colorWithRed:59 / 255. green:178 / 255. blue:208 / 255. alpha:0.8].CGColor; + self.layer.borderWidth = 2; + self.layer.backgroundColor = + [NSColor colorWithRed:59 / 255. green:178 / 255. blue:208 / 255. alpha:0.6].CGColor; - // Place a playful marker right smack dab in the middle. - self.layer.contents = MLNDefaultMarkerImage(); - self.layer.contentsGravity = kCAGravityCenter; - self.layer.contentsScale = [NSScreen mainScreen].backingScaleFactor; + // Place a playful marker right smack dab in the middle. + self.layer.contents = MLNDefaultMarkerImage(); + self.layer.contentsGravity = kCAGravityCenter; + self.layer.contentsScale = [NSScreen mainScreen].backingScaleFactor; } // MARK: Geometric methods -- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate toPointToView:(nullable NSView *)view { - if (!CLLocationCoordinate2DIsValid(coordinate)) { - return NSMakePoint(NAN, NAN); - } - return [self convertLatLng:MLNLatLngFromLocationCoordinate2D(coordinate) toPointToView:view]; +- (NSPoint)convertCoordinate:(CLLocationCoordinate2D)coordinate + toPointToView:(nullable NSView *)view { + if (!CLLocationCoordinate2DIsValid(coordinate)) { + return NSMakePoint(NAN, NAN); + } + return [self convertLatLng:MLNLatLngFromLocationCoordinate2D(coordinate) toPointToView:view]; } /// Converts a geographic coordinate to a point in the view’s coordinate system. - (NSPoint)convertLatLng:(mbgl::LatLng)latLng toPointToView:(nullable NSView *)view { - mbgl::ScreenCoordinate pixel = _mbglMap->pixelForLatLng(latLng); - // Cocoa origin is at the lower-left corner. - pixel.y = NSHeight(self.bounds) - pixel.y; - return [self convertPoint:NSMakePoint(pixel.x, pixel.y) toView:view]; + mbgl::ScreenCoordinate pixel = _mbglMap->pixelForLatLng(latLng); + // Cocoa origin is at the lower-left corner. + pixel.y = NSHeight(self.bounds) - pixel.y; + return [self convertPoint:NSMakePoint(pixel.x, pixel.y) toView:view]; } - (CLLocationCoordinate2D)convertPoint:(NSPoint)point toCoordinateFromView:(nullable NSView *)view { - return MLNLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]); + return MLNLocationCoordinate2DFromLatLng([self convertPoint:point toLatLngFromView:view]); } /// Converts a point in the view’s coordinate system to a geographic coordinate. - (mbgl::LatLng)convertPoint:(NSPoint)point toLatLngFromView:(nullable NSView *)view { - NSPoint convertedPoint = [self convertPoint:point fromView:view]; - return _mbglMap->latLngForPixel({ - convertedPoint.x, - // mbgl origin is at the top-left corner. - NSHeight(self.bounds) - convertedPoint.y, - }).wrapped(); + NSPoint convertedPoint = [self convertPoint:point fromView:view]; + return _mbglMap + ->latLngForPixel({ + convertedPoint.x, + // mbgl origin is at the top-left corner. + NSHeight(self.bounds) - convertedPoint.y, + }) + .wrapped(); } - (NSRect)convertCoordinateBounds:(MLNCoordinateBounds)bounds toRectToView:(nullable NSView *)view { - return [self convertLatLngBounds:MLNLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view]; + return [self convertLatLngBounds:MLNLatLngBoundsFromCoordinateBounds(bounds) toRectToView:view]; } /// Converts a geographic bounding box to a rectangle in the view’s coordinate /// system. - (NSRect)convertLatLngBounds:(mbgl::LatLngBounds)bounds toRectToView:(nullable NSView *)view { - auto northwest = bounds.northwest(); - auto northeast = bounds.northeast(); - auto southwest = bounds.southwest(); - auto southeast = bounds.southeast(); + auto northwest = bounds.northwest(); + auto northeast = bounds.northeast(); + auto southwest = bounds.southwest(); + auto southeast = bounds.southeast(); - auto center = [self convertPoint:{ NSMidX(view.bounds), NSMidY(view.bounds) } toLatLngFromView:view]; + auto center = [self convertPoint:{NSMidX(view.bounds), NSMidY(view.bounds)} + toLatLngFromView:view]; - // Extend bounds to account for the antimeridian - northwest.unwrapForShortestPath(center); - northeast.unwrapForShortestPath(center); - southwest.unwrapForShortestPath(center); - southeast.unwrapForShortestPath(center); + // Extend bounds to account for the antimeridian + northwest.unwrapForShortestPath(center); + northeast.unwrapForShortestPath(center); + southwest.unwrapForShortestPath(center); + southeast.unwrapForShortestPath(center); - auto correctedLatLngBounds = mbgl::LatLngBounds::empty(); - correctedLatLngBounds.extend(northwest); - correctedLatLngBounds.extend(northeast); - correctedLatLngBounds.extend(southwest); - correctedLatLngBounds.extend(southeast); + auto correctedLatLngBounds = mbgl::LatLngBounds::empty(); + correctedLatLngBounds.extend(northwest); + correctedLatLngBounds.extend(northeast); + correctedLatLngBounds.extend(southwest); + correctedLatLngBounds.extend(southeast); - NSRect rect = { [self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], CGSizeZero }; - rect = MLNExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() toPointToView:view]); - return rect; + NSRect rect = {[self convertLatLng:correctedLatLngBounds.southwest() toPointToView:view], + CGSizeZero}; + rect = MLNExtendRect(rect, [self convertLatLng:correctedLatLngBounds.northeast() + toPointToView:view]); + return rect; } - (MLNCoordinateBounds)convertRect:(NSRect)rect toCoordinateBoundsFromView:(nullable NSView *)view { - return MLNCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]); + return MLNCoordinateBoundsFromLatLngBounds([self convertRect:rect toLatLngBoundsFromView:view]); } /// Converts a rectangle in the given view’s coordinate system to a geographic /// bounding box. - (mbgl::LatLngBounds)convertRect:(NSRect)rect toLatLngBoundsFromView:(nullable NSView *)view { - auto bounds = mbgl::LatLngBounds::empty(); - auto bottomLeft = [self convertPoint:{ NSMinX(rect), NSMinY(rect) } toLatLngFromView:view]; - auto bottomRight = [self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]; - auto topRight = [self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]; - auto topLeft = [self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]; + auto bounds = mbgl::LatLngBounds::empty(); + auto bottomLeft = [self convertPoint:{NSMinX(rect), NSMinY(rect)} toLatLngFromView:view]; + auto bottomRight = [self convertPoint:{NSMaxX(rect), NSMinY(rect)} toLatLngFromView:view]; + auto topRight = [self convertPoint:{NSMaxX(rect), NSMaxY(rect)} toLatLngFromView:view]; + auto topLeft = [self convertPoint:{NSMinX(rect), NSMaxY(rect)} toLatLngFromView:view]; - // If the bounds straddles the antimeridian, unwrap it so that one side - // extends beyond ±180° longitude. - auto center = [self convertPoint:{ NSMidX(rect), NSMidY(rect) } toLatLngFromView:view]; - bottomLeft.unwrapForShortestPath(center); - bottomRight.unwrapForShortestPath(center); - topRight.unwrapForShortestPath(center); - topLeft.unwrapForShortestPath(center); + // If the bounds straddles the antimeridian, unwrap it so that one side + // extends beyond ±180° longitude. + auto center = [self convertPoint:{NSMidX(rect), NSMidY(rect)} toLatLngFromView:view]; + bottomLeft.unwrapForShortestPath(center); + bottomRight.unwrapForShortestPath(center); + topRight.unwrapForShortestPath(center); + topLeft.unwrapForShortestPath(center); - bounds.extend(bottomLeft); - bounds.extend(bottomRight); - bounds.extend(topRight); - bounds.extend(topLeft); + bounds.extend(bottomLeft); + bounds.extend(bottomRight); + bounds.extend(topRight); + bounds.extend(topLeft); - return bounds; + return bounds; } - (CLLocationDistance)metersPerPointAtLatitude:(CLLocationDegrees)latitude { - return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel); + return mbgl::Projection::getMetersPerPixelAtLatitude(latitude, self.zoomLevel); } // MARK: Debugging - (MLNMapDebugMaskOptions)debugMask { - mbgl::MapDebugOptions options = _mbglMap->getDebug(); - MLNMapDebugMaskOptions mask = 0; - if (options & mbgl::MapDebugOptions::TileBorders) { - mask |= MLNMapDebugTileBoundariesMask; - } - if (options & mbgl::MapDebugOptions::ParseStatus) { - mask |= MLNMapDebugTileInfoMask; - } - if (options & mbgl::MapDebugOptions::Timestamps) { - mask |= MLNMapDebugTimestampsMask; - } - if (options & mbgl::MapDebugOptions::Collision) { - mask |= MLNMapDebugCollisionBoxesMask; - } - if (options & mbgl::MapDebugOptions::Overdraw) { - mask |= MLNMapDebugOverdrawVisualizationMask; - } - if (options & mbgl::MapDebugOptions::StencilClip) { - mask |= MLNMapDebugStencilBufferMask; - } - if (options & mbgl::MapDebugOptions::DepthBuffer) { - mask |= MLNMapDebugDepthBufferMask; - } - return mask; + mbgl::MapDebugOptions options = _mbglMap->getDebug(); + MLNMapDebugMaskOptions mask = 0; + if (options & mbgl::MapDebugOptions::TileBorders) { + mask |= MLNMapDebugTileBoundariesMask; + } + if (options & mbgl::MapDebugOptions::ParseStatus) { + mask |= MLNMapDebugTileInfoMask; + } + if (options & mbgl::MapDebugOptions::Timestamps) { + mask |= MLNMapDebugTimestampsMask; + } + if (options & mbgl::MapDebugOptions::Collision) { + mask |= MLNMapDebugCollisionBoxesMask; + } + if (options & mbgl::MapDebugOptions::Overdraw) { + mask |= MLNMapDebugOverdrawVisualizationMask; + } + if (options & mbgl::MapDebugOptions::StencilClip) { + mask |= MLNMapDebugStencilBufferMask; + } + if (options & mbgl::MapDebugOptions::DepthBuffer) { + mask |= MLNMapDebugDepthBufferMask; + } + return mask; } - (void)setDebugMask:(MLNMapDebugMaskOptions)debugMask { - mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug; - if (debugMask & MLNMapDebugTileBoundariesMask) { - options |= mbgl::MapDebugOptions::TileBorders; - } - if (debugMask & MLNMapDebugTileInfoMask) { - options |= mbgl::MapDebugOptions::ParseStatus; - } - if (debugMask & MLNMapDebugTimestampsMask) { - options |= mbgl::MapDebugOptions::Timestamps; - } - if (debugMask & MLNMapDebugCollisionBoxesMask) { - options |= mbgl::MapDebugOptions::Collision; - } - if (debugMask & MLNMapDebugOverdrawVisualizationMask) { - options |= mbgl::MapDebugOptions::Overdraw; - } - if (debugMask & MLNMapDebugStencilBufferMask) { - options |= mbgl::MapDebugOptions::StencilClip; - } - if (debugMask & MLNMapDebugDepthBufferMask) { - options |= mbgl::MapDebugOptions::DepthBuffer; - } - _mbglMap->setDebug(options); + mbgl::MapDebugOptions options = mbgl::MapDebugOptions::NoDebug; + if (debugMask & MLNMapDebugTileBoundariesMask) { + options |= mbgl::MapDebugOptions::TileBorders; + } + if (debugMask & MLNMapDebugTileInfoMask) { + options |= mbgl::MapDebugOptions::ParseStatus; + } + if (debugMask & MLNMapDebugTimestampsMask) { + options |= mbgl::MapDebugOptions::Timestamps; + } + if (debugMask & MLNMapDebugCollisionBoxesMask) { + options |= mbgl::MapDebugOptions::Collision; + } + if (debugMask & MLNMapDebugOverdrawVisualizationMask) { + options |= mbgl::MapDebugOptions::Overdraw; + } + if (debugMask & MLNMapDebugStencilBufferMask) { + options |= mbgl::MapDebugOptions::StencilClip; + } + if (debugMask & MLNMapDebugDepthBufferMask) { + options |= mbgl::MapDebugOptions::DepthBuffer; + } + _mbglMap->setDebug(options); } -- (NSArray*)getActionJournalLog -{ - const auto& actionJournal = _mbglMap->getActionJournal(); - if (!actionJournal) { - return nil; - } +- (NSArray *)getActionJournalLog { + const auto &actionJournal = _mbglMap->getActionJournal(); + if (!actionJournal) { + return nil; + } - const auto& log = actionJournal->getLog(); - NSMutableArray* objcLog = [NSMutableArray new]; + const auto &log = actionJournal->getLog(); + NSMutableArray *objcLog = [NSMutableArray new]; - for (const auto& event : log) { - [objcLog addObject:[NSString stringWithUTF8String:event.c_str()]]; - } + for (const auto &event : log) { + [objcLog addObject:[NSString stringWithUTF8String:event.c_str()]]; + } - return objcLog; + return objcLog; } -- (void)clearActionJournalLog -{ - const auto& actionJournal = _mbglMap->getActionJournal(); - if (!actionJournal) { - return; - } +- (void)clearActionJournalLog { + const auto &actionJournal = _mbglMap->getActionJournal(); + if (!actionJournal) { + return; + } - actionJournal->clearLog(); + actionJournal->clearLog(); } #if MLN_RENDER_BACKEND_METAL - (MLNBackendResource *)backendResource { - return _mbglView->getObject(); + return _mbglView->getObject(); } #endif diff --git a/platform/macos/src/MLNOpenGLLayer.mm b/platform/macos/src/MLNOpenGLLayer.mm index 412e9339d..7af74a2c5 100644 --- a/platform/macos/src/MLNOpenGLLayer.mm +++ b/platform/macos/src/MLNOpenGLLayer.mm @@ -3,11 +3,11 @@ #import "MLNMapView_Private.h" @implementation MLNOpenGLLayer { - NSOpenGLContext *_context; + NSOpenGLContext *_context; } - (MLNMapView *)mapView { - return (MLNMapView *)super.view; + return (MLNMapView *)super.view; } // - (BOOL)isAsynchronous { @@ -15,44 +15,56 @@ // } - (BOOL)needsDisplayOnBoundsChange { - return YES; + return YES; } - (CGRect)frame { - return self.view.bounds; + return self.view.bounds; } - (NSOpenGLContext *)openGLContextForPixelFormat:(NSOpenGLPixelFormat *)pixelFormat { - if (!_context) { - _context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; - } - return _context; + if (!_context) { + _context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil]; + } + return _context; } - (NSOpenGLPixelFormat *)openGLPixelFormatForDisplayMask:(uint32_t)mask { - NSOpenGLPixelFormatAttribute pfas[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, - NSOpenGLPFAAccelerated, - NSOpenGLPFAClosestPolicy, - NSOpenGLPFAAccumSize, 32, - NSOpenGLPFAColorSize, 24, - NSOpenGLPFAAlphaSize, 8, - NSOpenGLPFADepthSize, 16, - NSOpenGLPFAStencilSize, 8, - NSOpenGLPFAScreenMask, mask, - NSOpenGLPFAAllowOfflineRenderers, // Allows using the integrated GPU - 0 - }; - return [[NSOpenGLPixelFormat alloc] initWithAttributes:pfas]; + NSOpenGLPixelFormatAttribute pfas[] = { + NSOpenGLPFAOpenGLProfile, + NSOpenGLProfileVersionLegacy, + NSOpenGLPFAAccelerated, + NSOpenGLPFAClosestPolicy, + NSOpenGLPFAAccumSize, + 32, + NSOpenGLPFAColorSize, + 24, + NSOpenGLPFAAlphaSize, + 8, + NSOpenGLPFADepthSize, + 16, + NSOpenGLPFAStencilSize, + 8, + NSOpenGLPFAScreenMask, + mask, + NSOpenGLPFAAllowOfflineRenderers, // Allows using the integrated GPU + 0}; + return [[NSOpenGLPixelFormat alloc] initWithAttributes:pfas]; } -- (BOOL)canDrawInOpenGLContext:(__unused NSOpenGLContext *)context pixelFormat:(__unused NSOpenGLPixelFormat *)pixelFormat forLayerTime:(__unused CFTimeInterval)t displayTime:(__unused const CVTimeStamp *)ts { - return !self.mapView.dormant; +- (BOOL)canDrawInOpenGLContext:(__unused NSOpenGLContext *)context + pixelFormat:(__unused NSOpenGLPixelFormat *)pixelFormat + forLayerTime:(__unused CFTimeInterval)t + displayTime:(__unused const CVTimeStamp *)ts { + return !self.mapView.dormant; } -- (void)drawInOpenGLContext:(NSOpenGLContext *)context pixelFormat:(NSOpenGLPixelFormat *)pixelFormat forLayerTime:(CFTimeInterval)t displayTime:(const CVTimeStamp *)ts { - [self.mapView renderSync]; - [super drawInOpenGLContext:context pixelFormat:pixelFormat forLayerTime:t displayTime:ts]; +- (void)drawInOpenGLContext:(NSOpenGLContext *)context + pixelFormat:(NSOpenGLPixelFormat *)pixelFormat + forLayerTime:(CFTimeInterval)t + displayTime:(const CVTimeStamp *)ts { + [self.mapView renderSync]; + [super drawInOpenGLContext:context pixelFormat:pixelFormat forLayerTime:t displayTime:ts]; } @end diff --git a/platform/macos/src/NSColor+MLNAdditions.mm b/platform/macos/src/NSColor+MLNAdditions.mm index f859d495c..31d683b54 100644 --- a/platform/macos/src/NSColor+MLNAdditions.mm +++ b/platform/macos/src/NSColor+MLNAdditions.mm @@ -3,68 +3,70 @@ @implementation NSColor (MLNAdditions) - (mbgl::Color)mgl_color { - CGFloat r, g, b, a; + CGFloat r, g, b, a; - // The MapLibre Style Spec does not specify a color space, but it is - // assumed to be sRGB for consistency with CSS. - NSColor *srgbColor = self; - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - srgbColor = [srgbColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; - } else { - srgbColor = [srgbColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; - } - [srgbColor getRed:&r green:&g blue:&b alpha:&a]; + // The MapLibre Style Spec does not specify a color space, but it is + // assumed to be sRGB for consistency with CSS. + NSColor *srgbColor = self; + if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { + srgbColor = [srgbColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } else { + srgbColor = [srgbColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + } + [srgbColor getRed:&r green:&g blue:&b alpha:&a]; - // NSColor provides non-premultiplied color components, so we have to premultiply each - // color component with the alpha value to transform it into a valid - // mbgl::Color which expects premultiplied color components. - return { static_cast(r*a), static_cast(g*a), static_cast(b*a), static_cast(a) }; + // NSColor provides non-premultiplied color components, so we have to premultiply each + // color component with the alpha value to transform it into a valid + // mbgl::Color which expects premultiplied color components. + return {static_cast(r * a), static_cast(g * a), static_cast(b * a), + static_cast(a)}; } - (mbgl::Color)mgl_colorForPremultipliedValue { - CGFloat r, g, b, a; + CGFloat r, g, b, a; - // The MapLibre Style Spec does not specify a color space, but it is - // assumed to be sRGB for consistency with CSS. - NSColor *srgbColor = self; - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - srgbColor = [srgbColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; - } else { - srgbColor = [srgbColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; - } - [srgbColor getRed:&r green:&g blue:&b alpha:&a]; + // The MapLibre Style Spec does not specify a color space, but it is + // assumed to be sRGB for consistency with CSS. + NSColor *srgbColor = self; + if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { + srgbColor = [srgbColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; + } else { + srgbColor = [srgbColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; + } + [srgbColor getRed:&r green:&g blue:&b alpha:&a]; - return { static_cast(r), static_cast(g), static_cast(b), static_cast(a) }; + return {static_cast(r), static_cast(g), static_cast(b), + static_cast(a)}; } + (NSColor *)mgl_colorWithColor:(mbgl::Color)color { - // If there is no alpha value, return original color values. - if (color.a == 0.0f) { - // macOS 10.12 Sierra and below uses calibrated RGB by default. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:color.r green:color.g blue:color.b alpha:color.a]; - } else { - return [NSColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; - } - } - - // mbgl::Color provides premultiplied color components, so we have to convert color - // components to non-premultiplied values to return a valid NSColor object. - float red = static_cast((color.r / color.a)); - float green = static_cast((color.g / color.a)); - float blue = static_cast((color.b / color.a)); - + // If there is no alpha value, return original color values. + if (color.a == 0.0f) { // macOS 10.12 Sierra and below uses calibrated RGB by default. if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:color.a]; + return [NSColor colorWithCalibratedRed:color.r green:color.g blue:color.b alpha:color.a]; } else { - return [NSColor colorWithRed:red green:green blue:blue alpha:color.a]; + return [NSColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; } + } + + // mbgl::Color provides premultiplied color components, so we have to convert color + // components to non-premultiplied values to return a valid NSColor object. + float red = static_cast((color.r / color.a)); + float green = static_cast((color.g / color.a)); + float blue = static_cast((color.b / color.a)); + + // macOS 10.12 Sierra and below uses calibrated RGB by default. + if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { + return [NSColor colorWithCalibratedRed:red green:green blue:blue alpha:color.a]; + } else { + return [NSColor colorWithRed:red green:green blue:blue alpha:color.a]; + } } - (mbgl::style::PropertyValue)mgl_colorPropertyValue { - mbgl::Color color = self.mgl_color; - return {{ color.r, color.g, color.b, color.a }}; + mbgl::Color color = self.mgl_color; + return {{color.r, color.g, color.b, color.a}}; } @end @@ -72,69 +74,68 @@ @implementation NSExpression (MLNColorAdditions) + (NSExpression *)mgl_expressionForRGBComponents:(NSArray *)components { - if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } + if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { + return [NSExpression expressionForConstantValue:color]; + } - NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; - NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:[components arrayByAddingObject:alpha]]; + NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; + NSExpression *alpha = [NSExpression expressionForConstantValue:@1.0]; + return [NSExpression expressionForFunction:color + selectorName:@"colorWithRed:green:blue:alpha:" + arguments:[components arrayByAddingObject:alpha]]; } + (NSExpression *)mgl_expressionForRGBAComponents:(NSArray *)components { - if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { - return [NSExpression expressionForConstantValue:color]; - } + if (NSColor *color = [self mgl_colorWithRGBComponents:components]) { + return [NSExpression expressionForConstantValue:color]; + } - NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; - return [NSExpression expressionForFunction:color - selectorName:@"colorWithRed:green:blue:alpha:" - arguments:components]; + NSExpression *color = [NSExpression expressionForConstantValue:[NSColor class]]; + return [NSExpression expressionForFunction:color + selectorName:@"colorWithRed:green:blue:alpha:" + arguments:components]; } /** Returns a color object corresponding to the given component expressions. */ + (NSColor *)mgl_colorWithRGBComponents:(NSArray *)componentExpressions { - // Map the component expressions to constant components. If any component is - // a non-constant expression, the components cannot be converted into a - // constant color value. - std::vector components; - for (NSExpression *componentExpression in componentExpressions) { - if (componentExpression.expressionType != NSConstantValueExpressionType) { - return nil; - } - - NSNumber *component = (NSNumber *)componentExpression.constantValue; - if (![component isKindOfClass:[NSNumber class]]) { - return nil; - } - - components.push_back(component.doubleValue / 255.0); + // Map the component expressions to constant components. If any component is + // a non-constant expression, the components cannot be converted into a + // constant color value. + std::vector components; + for (NSExpression *componentExpression in componentExpressions) { + if (componentExpression.expressionType != NSConstantValueExpressionType) { + return nil; } - if (components.size() < 4) { - components.push_back(1.0); - } else { - // Alpha - components.back() *= 255.0; + NSNumber *component = (NSNumber *)componentExpression.constantValue; + if (![component isKindOfClass:[NSNumber class]]) { + return nil; } + components.push_back(component.doubleValue / 255.0); + } - // macOS 10.12 Sierra and below uses calibrated RGB by default. - if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { - return [NSColor colorWithCalibratedRed:components[0] - green:components[1] - blue:components[2] - alpha:components[3]]; - } - // The MapLibre Style Spec does not specify a color space, but it is - // assumed to be sRGB for consistency with CSS. - return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] - components:&components[0] - count:components.size()]; + if (components.size() < 4) { + components.push_back(1.0); + } else { + // Alpha + components.back() *= 255.0; + } + + // macOS 10.12 Sierra and below uses calibrated RGB by default. + if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { + return [NSColor colorWithCalibratedRed:components[0] + green:components[1] + blue:components[2] + alpha:components[3]]; + } + // The MapLibre Style Spec does not specify a color space, but it is + // assumed to be sRGB for consistency with CSS. + return [NSColor colorWithColorSpace:[NSColorSpace sRGBColorSpace] + components:&components[0] + count:components.size()]; } @end diff --git a/platform/macos/src/NSImage+MLNAdditions.mm b/platform/macos/src/NSImage+MLNAdditions.mm index 7a7537f7b..fcd5e7390 100644 --- a/platform/macos/src/NSImage+MLNAdditions.mm +++ b/platform/macos/src/NSImage+MLNAdditions.mm @@ -3,87 +3,86 @@ #include BOOL MLNEdgeInsetsIsZero(NSEdgeInsets edgeInsets) { - return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && + edgeInsets.bottom == 0; } @implementation NSImage (MLNAdditions) -- (nullable instancetype)initWithMLNPremultipliedImage:(mbgl::PremultipliedImage&&)src { - CGImageRef image = CGImageCreateWithMLNPremultipliedImage(std::move(src)); - if (!image) { - return nil; - } +- (nullable instancetype)initWithMLNPremultipliedImage:(mbgl::PremultipliedImage &&)src { + CGImageRef image = CGImageCreateWithMLNPremultipliedImage(std::move(src)); + if (!image) { + return nil; + } - self = [self initWithCGImage:image size:NSZeroSize]; - CGImageRelease(image); - return self; + self = [self initWithCGImage:image size:NSZeroSize]; + CGImageRelease(image); + return self; } - (nullable instancetype)initWithMLNStyleImage:(const mbgl::style::Image &)styleImage { - CGImageRef image = CGImageCreateWithMLNPremultipliedImage(styleImage.getImage().clone()); - if (!image) { - return nil; - } + CGImageRef image = CGImageCreateWithMLNPremultipliedImage(styleImage.getImage().clone()); + if (!image) { + return nil; + } - NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image]; - CGImageRelease(image); - CGFloat scale = styleImage.getPixelRatio(); - NSSize size = NSMakeSize(styleImage.getImage().size.width / scale, - styleImage.getImage().size.height / scale); - if (self = [self initWithSize:size]) { - [self addRepresentation:rep]; - [self setTemplate:styleImage.isSdf()]; - if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) { - self.resizingMode = NSImageResizingModeStretch; - } else { - self.resizingMode = NSImageResizingModeTile; - } - if (auto content = styleImage.getContent()) { - self.capInsets = NSEdgeInsetsMake(content->top / scale, - content->left / scale, - size.height - content->bottom / scale, - size.width - content->right / scale); - } + NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image]; + CGImageRelease(image); + CGFloat scale = styleImage.getPixelRatio(); + NSSize size = NSMakeSize(styleImage.getImage().size.width / scale, + styleImage.getImage().size.height / scale); + if (self = [self initWithSize:size]) { + [self addRepresentation:rep]; + [self setTemplate:styleImage.isSdf()]; + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) { + self.resizingMode = NSImageResizingModeStretch; + } else { + self.resizingMode = NSImageResizingModeTile; } - return self; + if (auto content = styleImage.getContent()) { + self.capInsets = NSEdgeInsetsMake(content->top / scale, content->left / scale, + size.height - content->bottom / scale, + size.width - content->right / scale); + } + } + return self; } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { - mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; - auto imageWidth = cPremultipliedImage.size.width; + mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; + auto imageWidth = cPremultipliedImage.size.width; - float scale = static_cast(imageWidth) / self.size.width; - mbgl::style::ImageStretches stretchX, stretchY; - if (self.resizingMode == NSImageResizingModeStretch) { - stretchX.push_back({ - self.capInsets.left * scale, (self.size.width - self.capInsets.right) * scale, - }); - stretchY.push_back({ - self.capInsets.top * scale, (self.size.height - self.capInsets.bottom) * scale, - }); - } + float scale = static_cast(imageWidth) / self.size.width; + mbgl::style::ImageStretches stretchX, stretchY; + if (self.resizingMode == NSImageResizingModeStretch) { + stretchX.push_back({ + self.capInsets.left * scale, + (self.size.width - self.capInsets.right) * scale, + }); + stretchY.push_back({ + self.capInsets.top * scale, + (self.size.height - self.capInsets.bottom) * scale, + }); + } - std::optional imageContent; - if (!MLNEdgeInsetsIsZero(self.capInsets)) { - imageContent = (mbgl::style::ImageContent){ - .left = static_cast(self.capInsets.left * scale), - .top = static_cast(self.capInsets.top * scale), - .right = static_cast((self.size.width - self.capInsets.right) * scale), - .bottom = static_cast((self.size.height - self.capInsets.bottom) * scale), - }; - } + std::optional imageContent; + if (!MLNEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * scale), + .top = static_cast(self.capInsets.top * scale), + .right = static_cast((self.size.width - self.capInsets.right) * scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * scale), + }; + } - return std::make_unique([identifier UTF8String], - std::move(cPremultipliedImage), - scale, - [self isTemplate], - stretchX, stretchY, - imageContent); + return std::make_unique([identifier UTF8String], + std::move(cPremultipliedImage), scale, + [self isTemplate], stretchX, stretchY, imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage { - CGImageRef ref = [self CGImageForProposedRect:nullptr context:nullptr hints:nullptr]; - return MLNPremultipliedImageFromCGImage(ref); + CGImageRef ref = [self CGImageForProposedRect:nullptr context:nullptr hints:nullptr]; + return MLNPremultipliedImageFromCGImage(ref); } @end diff --git a/render-test/ios/iosTestRunner.mm b/render-test/ios/iosTestRunner.mm index 54a2859ec..526a81b35 100644 --- a/render-test/ios/iosTestRunner.mm +++ b/render-test/ios/iosTestRunner.mm @@ -8,7 +8,7 @@ @interface IosTestRunner () -@property (nullable) TestRunner* runner; +@property (nullable) TestRunner *runner; @property (copy, nullable) NSString *styleResultPath; @@ -22,126 +22,124 @@ @implementation IosTestRunner --(instancetype)init -{ - self = [super init]; - if (!self) { - return nil; +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + self.testStatus = NO; + self.runner = new TestRunner(); + NSError *error; + BOOL success; + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *xcTestBundleRoot = [[NSBundle bundleForClass:[self class]] resourcePath]; + NSString *testDataDir = [xcTestBundleRoot stringByAppendingString:@"/TestData.bundle"]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDir = [paths objectAtIndex:0]; + + NSString *destinationPath = [documentsDir stringByAppendingPathComponent:@"test-data"]; + NSString *path = destinationPath; + + // delete destinationPath if it exists + if ([fileManager fileExistsAtPath:destinationPath]) { + success = [fileManager removeItemAtPath:destinationPath error:&error]; + if (!success) { + NSAssert1(0, @"Failed to delete '%@'.", destinationPath); } + } - self.testStatus = NO; - self.runner = new TestRunner(); - NSError *error; - BOOL success; - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *xcTestBundleRoot = [[NSBundle bundleForClass:[self class]] resourcePath]; - NSString *testDataDir = [xcTestBundleRoot stringByAppendingString:@"/TestData.bundle"]; + success = [fileManager copyItemAtPath:testDataDir toPath:destinationPath error:&error]; + if (!success) { + NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]); + NSLog(@"Failed to copy %@ file, error %@", testDataDir, [error localizedDescription]); + } else { + path = destinationPath; + NSLog(@"File copied %@ OK", testDataDir); + } - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDir = [paths objectAtIndex: 0]; - - NSString *destinationPath = [documentsDir stringByAppendingPathComponent: @"test-data"]; - NSString *path = destinationPath; - - // delete destinationPath if it exists - if ([fileManager fileExistsAtPath:destinationPath]) { - success = [fileManager removeItemAtPath: destinationPath error: &error]; - if (!success) { - NSAssert1(0, @"Failed to delete '%@'.", destinationPath); - } - } - - success = [fileManager copyItemAtPath: testDataDir toPath: destinationPath error: &error]; - if (!success){ - NSAssert1(0, @"Failed to copy file '%@'.", [error localizedDescription]); - NSLog(@"Failed to copy %@ file, error %@", testDataDir, [error localizedDescription]); - } - else { - path = destinationPath; - NSLog(@"File copied %@ OK", testDataDir); - } - - if (path) { - std::string basePath = std::string([path UTF8String]); - self.testStatus = self.runner->startTest(basePath) ? YES : NO; + if (path) { + std::string basePath = std::string([path UTF8String]); + self.testStatus = self.runner->startTest(basePath) ? YES : NO; #if MLN_RENDER_BACKEND_METAL - self.styleResultPath = [path stringByAppendingPathComponent:@"/ios-metal-render-test-runner-style.html"]; - self.metricResultPath = [path stringByAppendingPathComponent:@"/ios-metal-render-test-runner-metrics.html"]; + self.styleResultPath = + [path stringByAppendingPathComponent:@"/ios-metal-render-test-runner-style.html"]; + self.metricResultPath = + [path stringByAppendingPathComponent:@"/ios-metal-render-test-runner-metrics.html"]; #else - self.styleResultPath = [path stringByAppendingPathComponent:@"/ios-render-test-runner-style.html"]; - self.metricResultPath = [path stringByAppendingPathComponent:@"/ios-render-test-runner-metrics.html"]; + self.styleResultPath = + [path stringByAppendingPathComponent:@"/ios-render-test-runner-style.html"]; + self.metricResultPath = + [path stringByAppendingPathComponent:@"/ios-render-test-runner-metrics.html"]; #endif - BOOL fileFound = [fileManager fileExistsAtPath: self.styleResultPath]; - if (fileFound == NO) { - NSLog(@"Style test result file '%@' does not exit ", self.styleResultPath); - self.testStatus = NO; - } - - fileFound = [fileManager fileExistsAtPath: self.metricResultPath]; - if (fileFound == NO) { - NSLog(@"Metric test result file '%@' does not exit ", self.metricResultPath); - self.testStatus = NO; - } - - NSString *rebaselinePath = [path stringByAppendingPathComponent:@"/baselines"]; - BOOL needArchiving = NO; - BOOL isDir = NO; - NSArray *subpaths = [[NSArray alloc] init]; - if ([fileManager fileExistsAtPath:rebaselinePath isDirectory: &isDir] && isDir){ - subpaths = [fileManager subpathsAtPath:rebaselinePath]; - for(NSString *path in subpaths) - { - NSString *longPath = [rebaselinePath stringByAppendingPathComponent:path]; - if([fileManager fileExistsAtPath:longPath isDirectory:&isDir] && !isDir) - { - needArchiving = YES; - break; - } - } - } - else { - NSLog(@"Metric path '%@' does not exit ", rebaselinePath); - } - - if (needArchiving) { - NSString *archivePath = [path stringByAppendingString:@"/metrics.zip"]; - BOOL success = [SSZipArchive createZipFileAtPath:archivePath - withContentsOfDirectory:rebaselinePath - keepParentDirectory:NO - compressionLevel:-1 - password:nil - AES:YES - progressHandler:nil]; - - if (success) { - NSLog(@"Successfully archived all of the metrics into metrics.zip"); - self.metricPath = archivePath; - } - else { - NSLog(@"Failed to archive rebaselined metrics into metrics.zip"); - } - } + BOOL fileFound = [fileManager fileExistsAtPath:self.styleResultPath]; + if (fileFound == NO) { + NSLog(@"Style test result file '%@' does not exit ", self.styleResultPath); + self.testStatus = NO; } - delete self.runner; - self.runner = nullptr; - return self; + fileFound = [fileManager fileExistsAtPath:self.metricResultPath]; + if (fileFound == NO) { + NSLog(@"Metric test result file '%@' does not exit ", self.metricResultPath); + self.testStatus = NO; + } + + NSString *rebaselinePath = [path stringByAppendingPathComponent:@"/baselines"]; + BOOL needArchiving = NO; + BOOL isDir = NO; + NSArray *subpaths = [[NSArray alloc] init]; + if ([fileManager fileExistsAtPath:rebaselinePath isDirectory:&isDir] && isDir) { + subpaths = [fileManager subpathsAtPath:rebaselinePath]; + for (NSString *path in subpaths) { + NSString *longPath = [rebaselinePath stringByAppendingPathComponent:path]; + if ([fileManager fileExistsAtPath:longPath isDirectory:&isDir] && !isDir) { + needArchiving = YES; + break; + } + } + } else { + NSLog(@"Metric path '%@' does not exit ", rebaselinePath); + } + + if (needArchiving) { + NSString *archivePath = [path stringByAppendingString:@"/metrics.zip"]; + BOOL success = [SSZipArchive createZipFileAtPath:archivePath + withContentsOfDirectory:rebaselinePath + keepParentDirectory:NO + compressionLevel:-1 + password:nil + AES:YES + progressHandler:nil]; + + if (success) { + NSLog(@"Successfully archived all of the metrics into metrics.zip"); + self.metricPath = archivePath; + } else { + NSLog(@"Failed to archive rebaselined metrics into metrics.zip"); + } + } + } + + delete self.runner; + self.runner = nullptr; + return self; } -- (NSString*) getStyleResultPath { - return self.styleResultPath; +- (NSString *)getStyleResultPath { + return self.styleResultPath; } -- (NSString*) getMetricResultPath { - return self.metricResultPath; +- (NSString *)getMetricResultPath { + return self.metricResultPath; } -- (NSString*) getMetricPath { - return self.metricPath; +- (NSString *)getMetricPath { + return self.metricPath; } -- (BOOL) getTestStatus { - return self.testStatus; +- (BOOL)getTestStatus { + return self.testStatus; } @end diff --git a/test/src/main.mm b/test/src/main.mm index 12d5de4d5..908930f2d 100644 --- a/test/src/main.mm +++ b/test/src/main.mm @@ -3,6 +3,6 @@ #import int main(int argc, char* argv[]) { - [[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] bundlePath]]; - return mbgl::runTests(argc, argv); + [[NSFileManager defaultManager] changeCurrentDirectoryPath:[[NSBundle mainBundle] bundlePath]]; + return mbgl::runTests(argc, argv); }