ios: apply clang-format to Objective-C++ sources (#4060)

This commit is contained in:
Bart Louwers
2026-02-24 14:37:35 +01:00
committed by GitHub
parent 41832b518d
commit 5787f9cc38
134 changed files with 29751 additions and 27896 deletions

View File

@@ -20,4 +20,5 @@ Language: Cpp
Language: ObjC Language: ObjC
BasedOnStyle: Google BasedOnStyle: Google
ObjCSpaceAfterProperty: true ObjCSpaceAfterProperty: true
AccessModifierOffset: -2
... ...

View File

@@ -22,7 +22,7 @@ repos:
rev: v20.1.8 rev: v20.1.8
hooks: hooks:
- id: clang-format - id: clang-format
files: '.*\.(hpp|cpp|h)' files: '.*\.(hpp|cpp|h|mm)'
exclude: 'vendor/.*' exclude: 'vendor/.*'
- repo: https://github.com/keith/pre-commit-buildifier - repo: https://github.com/keith/pre-commit-buildifier

View File

@@ -1,17 +1,17 @@
#import "ExampleCustomDrawableStyleLayer.h" #import "ExampleCustomDrawableStyleLayer.h"
#import "MLNStyleLayer.h"
#import "MLNCustomDrawableStyleLayer.h" #import "MLNCustomDrawableStyleLayer.h"
#import "MLNStyleLayer.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#include <mbgl/util/image+MLNAdditions.hpp>
#include <mbgl/layermanager/layer_factory.hpp> #include <mbgl/layermanager/layer_factory.hpp>
#include <mbgl/style/layer.hpp> #include <mbgl/style/layer.hpp>
#include <mbgl/style/layers/custom_drawable_layer.hpp> #include <mbgl/style/layers/custom_drawable_layer.hpp>
#include <mbgl/util/constants.hpp> #include <mbgl/util/constants.hpp>
#include <mbgl/util/image+MLNAdditions.hpp>
#include <memory>
#include <cmath> #include <cmath>
#include <memory>
@interface MLNCustomDrawableStyleLayer (Internal) @interface MLNCustomDrawableStyleLayer (Internal)
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer; - (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer;
@@ -22,8 +22,8 @@ class ExampleCustomDrawableStyleLayerHost;
@implementation ExampleCustomDrawableStyleLayer @implementation ExampleCustomDrawableStyleLayer
- (instancetype)initWithIdentifier:(NSString *)identifier { - (instancetype)initWithIdentifier:(NSString *)identifier {
auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(identifier.UTF8String, auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(
std::make_unique<ExampleCustomDrawableStyleLayerHost>(self)); identifier.UTF8String, std::make_unique<ExampleCustomDrawableStyleLayerHost>(self));
return self = [super initWithPendingLayer:std::move(layer)]; return self = [super initWithPendingLayer:std::move(layer)];
} }
@@ -37,15 +37,15 @@ public:
} }
void initialize() override { void initialize() override {
if (layerRef == nil) return; if (layerRef == nil)
else if (layer == nil) layer = layerRef; return;
else if (layer == nil)
layer = layerRef;
} }
void update(Interface& interface) override { void update(Interface &interface) override {
// if we have built our drawable(s) already, either update or skip // if we have built our drawable(s) already, either update or skip
if (interface.getDrawableCount()) if (interface.getDrawableCount()) return;
return;
constexpr float extent = mbgl::util::EXTENT; constexpr float extent = mbgl::util::EXTENT;
@@ -57,15 +57,21 @@ public:
interface.setTileID({11, 327, 792}); interface.setTileID({11, 327, 792});
constexpr auto numLines = 6; constexpr auto numLines = 6;
Interface::LineOptions options[numLines] { 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=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() }, /*width=*/8.0f, /*color=*/Color::red()},
{/*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=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
{/*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) }, /*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() }, {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
{/*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) }, /*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) { for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt; opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt; opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter; opt.geometry.joinType = style::LineJoinType::Miter;
@@ -74,11 +80,12 @@ public:
constexpr auto numPoints = 10; constexpr auto numPoints = 10;
GeometryCoordinates polyline; GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); 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 index{0}; index < numLines; ++index) {
for(auto &p : polyline) { for (auto &p : polyline) {
p.y += extent / numLines; p.y += extent / numLines;
} }
@@ -98,15 +105,21 @@ public:
interface.setTileID({11, 327, 792}); interface.setTileID({11, 327, 792});
constexpr auto numLines = 6; constexpr auto numLines = 6;
Interface::LineOptions options[numLines] { 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=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() }, /*width=*/8.0f, /*color=*/Color::red()},
{/*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=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
{/*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) }, /*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() }, {/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
{/*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) }, /*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) { for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt; opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt; opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter; opt.geometry.joinType = style::LineJoinType::Miter;
@@ -115,11 +128,12 @@ public:
constexpr auto numPoints = 10; constexpr auto numPoints = 10;
GeometryCoordinates polyline; GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) { for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f); 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 index{0}; index < numLines; ++index) {
for(auto &p : polyline) { for (auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines; if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines; p.y += extent / numLines;
} }
@@ -131,11 +145,11 @@ public:
interface.addPolyline(polyline, Interface::LineShaderType::WideVector); interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone // add clone
for(auto &p : polyline) { for (auto &p : polyline) {
p.y += 0.05f * extent / numLines; p.y += 0.05f * extent / numLines;
} }
interface.addPolyline(polyline, Interface::LineShaderType::WideVector); interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for(auto &p : polyline) { for (auto &p : polyline) {
p.y -= 0.05f * extent / numLines; p.y -= 0.05f * extent / numLines;
} }
} }
@@ -151,19 +165,19 @@ public:
GeometryCollection geometry{ GeometryCollection geometry{
{ {
// ring 1 // ring 1
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.7f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 1.0f)}, {static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
{static_cast<int16_t>(extent* 0.0f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
}, },
{ {
// ring 2 // ring 2
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
{static_cast<int16_t>(extent* 0.15f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.25f), static_cast<int16_t>(extent* 0.45f)}, {static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
}, },
}; };
@@ -181,23 +195,29 @@ public:
// set tile // set tile
interface.setTileID({11, 327, 789}); interface.setTileID({11, 327, 789});
GeometryCoordinate position {static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)}; GeometryCoordinate position{static_cast<int16_t>(extent * 0.5f),
static_cast<int16_t>(extent * 0.5f)};
// load image // load image
UIImage *assetImage = [UIImage imageNamed:@"pin"]; UIImage *assetImage = [UIImage imageNamed:@"pin"];
assert(assetImage.CGImage != NULL); assert(assetImage.CGImage != NULL);
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(MLNPremultipliedImageFromCGImage(assetImage.CGImage)); std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(
MLNPremultipliedImageFromCGImage(assetImage.CGImage));
// set symbol options // set symbol options
Interface::SymbolOptions options; Interface::SymbolOptions options;
options.texture = interface.context.createTexture2D(); options.texture = interface.context.createTexture2D();
options.texture->setImage(image); options.texture->setImage(image);
options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
const std::array<std::array<float, 2>, 2> textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}}; gfx::TextureWrapType::Clamp,
gfx::TextureWrapType::Clamp});
const std::array<std::array<float, 2>, 2> textureCoordinates = {
{{0.0f, 0.08f}, {1.0f, 0.9f}}};
const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0]; const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0];
const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1]; const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1];
assert(xspan > 0.0f && yspan > 0.0f); assert(xspan > 0.0f && yspan > 0.0f);
options.size = {static_cast<uint32_t>(image->size.width * xspan), static_cast<uint32_t>(image->size.height * yspan)}; options.size = {static_cast<uint32_t>(image->size.width * xspan),
static_cast<uint32_t>(image->size.height * yspan)};
options.anchor = {0.5f, 0.95f}; options.anchor = {0.5f, 0.95f};
options.angleDegrees = 45.0f; options.angleDegrees = 45.0f;
options.scaleWithMap = true; options.scaleWithMap = true;
@@ -213,14 +233,20 @@ public:
using namespace mbgl; using namespace mbgl;
// add polyline with geographic coordinates // 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} }; 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.beginCap = style::LineCapType::Square;
options.geometry.endCap = style::LineCapType::Square; options.geometry.endCap = style::LineCapType::Square;
options.geometry.joinType = style::LineJoinType::Bevel; options.geometry.joinType = style::LineJoinType::Bevel;
options.geometry.type = FeatureType::LineString; options.geometry.type = FeatureType::LineString;
interface.setLineOptions(options); interface.setLineOptions(options);
LineString<double> polyline_geo { LineString<double> polyline_geo{
// San Francisco // San Francisco
{-122.38186800073211, 37.77466003457463}, {-122.38186800073211, 37.77466003457463},
{-122.3869373450997, 37.774352128895615}, {-122.3869373450997, 37.774352128895615},
@@ -245,8 +271,13 @@ public:
// set tile // set tile
interface.setTileID({11, 327, 790}); interface.setTileID({11, 327, 790});
Interface::LineOptions options Interface::LineOptions options{/*geometry=*/{},
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/7.0f, /*color=*/{1.0f, 0, 0, .5f} }; /*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.beginCap = style::LineCapType::Round;
options.geometry.endCap = style::LineCapType::Round; options.geometry.endCap = style::LineCapType::Round;
options.geometry.joinType = style::LineJoinType::Round; options.geometry.joinType = style::LineJoinType::Round;
@@ -255,17 +286,17 @@ public:
GeometryCollection polyline_tile{ GeometryCollection polyline_tile{
{ {
// ring 1 // ring 1
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.7f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 1.0f)}, {static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
{static_cast<int16_t>(extent* 0.0f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
}, },
{ {
// ring 2 // ring 2
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)}, {static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
{static_cast<int16_t>(extent* 0.15f), static_cast<int16_t>(extent* 0.5f)}, {static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent* 0.25f), static_cast<int16_t>(extent* 0.45f)}, {static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
}, },
}; };
@@ -273,7 +304,6 @@ public:
interface.setLineOptions(options); interface.setLineOptions(options);
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector); interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector); interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
} }
// finish // finish
@@ -286,7 +316,8 @@ public:
layerRef = layer; layerRef = layer;
layer = nil; layer = nil;
} }
private: private:
__weak ExampleCustomDrawableStyleLayer * layerRef; __weak ExampleCustomDrawableStyleLayer *layerRef;
ExampleCustomDrawableStyleLayer * layer = nil; ExampleCustomDrawableStyleLayer *layer = nil;
}; };

View File

@@ -2,28 +2,23 @@
@implementation PluginLayerExample @implementation PluginLayerExample
// This is the layer type in the style that is used // This is the layer type in the style that is used
+(MLNPluginLayerCapabilities *)layerCapabilities { + (MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-test"; tempResult.layerID = @"plugin-layer-test";
tempResult.requiresPass3D = YES; tempResult.requiresPass3D = YES;
return tempResult; return tempResult;
} }
// The overrides // The overrides
-(void)onRenderLayer { - (void)onRenderLayer {
NSLog(@"PluginLayerExample: On Render Layer"); NSLog(@"PluginLayerExample: On Render Layer");
} }
-(void)onUpdateLayer { - (void)onUpdateLayer {
} }
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// NSLog(@"Layer Properties: %@", layerProperties); // NSLog(@"Layer Properties: %@", layerProperties);
} }

View File

@@ -10,8 +10,7 @@
#import <MetalKit/MetalKit.h> #import <MetalKit/MetalKit.h>
#import "Mapbox.h" #import "Mapbox.h"
typedef struct typedef struct {
{
vector_float2 position; vector_float2 position;
vector_float4 color; vector_float4 color;
} Vertex; } Vertex;
@@ -26,20 +25,15 @@ typedef struct
float _offsetY; float _offsetY;
float _scale; float _scale;
float _r, _g, _b, _a; float _r, _g, _b, _a;
} }
@end @end
@implementation PluginLayerExampleMetalRendering @implementation PluginLayerExampleMetalRendering
// This is a static class method that is used by the MLNMapView to create the // This is a static class method that is used by the MLNMapView to create the
// required marshalling classes to interface with the MapLibre core. // required marshalling classes to interface with the MapLibre core.
+(MLNPluginLayerCapabilities *)layerCapabilities { + (MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-metal-rendering"; tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES; tempResult.requiresPass3D = YES;
@@ -60,43 +54,42 @@ typedef struct
]; ];
return tempResult; return tempResult;
} }
- (void)createShaders:(MLNMapView *)mapView { - (void)createShaders:(MLNMapView *)mapView {
MLNBackendResource* resource = [mapView backendResource]; MLNBackendResource *resource = [mapView backendResource];
NSString *shaderSource = @ NSString *shaderSource =
@
" #include <metal_stdlib>\n" " #include <metal_stdlib>\n"
" using namespace metal;\n" " using namespace metal;\n"
" typedef struct\n" " typedef struct\n"
" {\n" " {\n"
" vector_float2 position;\n" " vector_float2 position;\n"
" vector_float4 color;\n" " vector_float4 color;\n"
" } Vertex;\n" " } Vertex;\n"
" struct RasterizerData\n" " struct RasterizerData\n"
" {\n" " {\n"
" float4 position [[position]];\n" " float4 position [[position]];\n"
" float4 color;\n" " float4 color;\n"
" };\n" " };\n"
" vertex RasterizerData\n" " vertex RasterizerData\n"
" vertexShader(uint vertexID [[vertex_id]],\n" " vertexShader(uint vertexID [[vertex_id]],\n"
" constant Vertex *vertices [[buffer(0)]],\n" " constant Vertex *vertices [[buffer(0)]],\n"
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n" " constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
" {\n" " {\n"
" RasterizerData out;\n" " RasterizerData out;\n"
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n" " float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n" " vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n" " out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n" " out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
" out.color = vertices[vertexID].color;\n" " out.color = vertices[vertexID].color;\n"
" return out;\n" " return out;\n"
" }\n" " }\n"
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n" " fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
" {\n" " {\n"
" return in.color;\n" " return in.color;\n"
" }\n"; " }\n";
NSError *error = nil; NSError *error = nil;
id<MTLDevice> _device = resource.device; id<MTLDevice> _device = resource.device;
@@ -120,30 +113,28 @@ typedef struct
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled // Notice that we don't configure the stencilTest property, leaving stencil testing disabled
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init]; MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed depthStencilDescriptor.depthCompareFunction =
MTLCompareFunctionAlways; // Or another value as needed
depthStencilDescriptor.depthWriteEnabled = NO; depthStencilDescriptor.depthWriteEnabled = NO;
_depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor]; _depthStencilStateWithoutStencil =
[_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
} }
// Setup the rendering environment // Setup the rendering environment
-(void)setupRendering:(MLNMapView *)mapView { - (void)setupRendering:(MLNMapView *)mapView {
[self createShaders:mapView]; [self createShaders:mapView];
if (_scale == 0) { if (_scale == 0) {
_scale = 1; _scale = 1;
} }
// Set the initial // Set the initial
_a = 1; _a = 1;
} }
// The overrides // The overrides
-(void)onRenderLayer:(MLNMapView *)mapView - (void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder { renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
MLNBackendResource *resource = [mapView backendResource];
MLNBackendResource* resource = [mapView backendResource];
if (_pipelineState == nil) { if (_pipelineState == nil) {
[self setupRendering:mapView]; [self setupRendering:mapView];
@@ -153,16 +144,15 @@ typedef struct
return; return;
} }
vector_uint2 _viewportSize; vector_uint2 _viewportSize;
_viewportSize.x = resource.mtkView.drawableSize.width; _viewportSize.x = resource.mtkView.drawableSize.width;
_viewportSize.y = resource.mtkView.drawableSize.height; _viewportSize.y = resource.mtkView.drawableSize.height;
Vertex triangleVerticesWithColor[] = { Vertex triangleVerticesWithColor[] = {
// 2D positions, RGBA colors // 2D positions, RGBA colors
{ { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } }, {{(250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
{ { (-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 } }, {{(0 + _offsetX) * _scale, (250 + _offsetY) * _scale}, {_r, _g, _b, _a}},
}; };
[renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setRenderPipelineState:_pipelineState];
@@ -173,23 +163,19 @@ typedef struct
length:sizeof(triangleVerticesWithColor) length:sizeof(triangleVerticesWithColor)
atIndex:0]; atIndex:0];
[renderEncoder setVertexBytes:&_viewportSize [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
length:sizeof(_viewportSize)
atIndex:1];
// Draw the triangle. // Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
vertexStart:0
vertexCount:3];
} }
-(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { - (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// This is called before the render call and is a place where // This is called before the render call and is a place where
// any animations/etc can be updated // any animations/etc can be updated
} }
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
//NSLog(@"Metal Layer Rendering Properties: %@", layerProperties); // NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
// These two properties are in the "properties" section of the style sheet and // 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 // are passed in at layer creation. These are not expression based properties
@@ -214,7 +200,7 @@ typedef struct
NSString *fillColor = [layerProperties objectForKey:@"fill-color"]; NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (fillColor) { if (fillColor) {
//NSLog(@"Fill Color: %@", fillColor); // NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""]; fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""]; fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","]; NSArray *components = [fillColor componentsSeparatedByString:@","];
@@ -225,9 +211,7 @@ typedef struct
_b = [[components objectAtIndex:2] floatValue] / 255.0; _b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue]; _a = [[components objectAtIndex:3] floatValue];
} }
} }
} }
@end @end

View File

@@ -11,11 +11,9 @@ class Collator::Impl {
public: public:
Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_) Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
: options((caseSensitive ? 0 : NSCaseInsensitiveSearch) | : options((caseSensitive ? 0 : NSCaseInsensitiveSearch) |
(diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)) (diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)),
, locale(locale_ ? locale(locale_ ? [[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())]
[[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())] : : [NSLocale currentLocale]) {}
[NSLocale currentLocale])
{}
bool operator==(const Impl& other) const { bool operator==(const Impl& other) const {
return options == other.options && return options == other.options &&
@@ -37,31 +35,30 @@ public:
// Following documentation at: // Following documentation at:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9 // 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 // 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. // may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47
// Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is used by // 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. // `currentLocale`, we don't want to return the underscore.
return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"].UTF8String; return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
.UTF8String;
} }
private: private:
NSStringCompareOptions options; NSStringCompareOptions options;
NSLocale* locale; NSLocale* locale;
}; };
Collator::Collator(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_) Collator::Collator(bool caseSensitive, bool diacriticSensitive,
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_)) const std::optional<std::string>& locale_)
{} : impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_)) {}
bool Collator::operator==(const Collator& other) const { bool Collator::operator==(const Collator& other) const { return *impl == *(other.impl); }
return *impl == *(other.impl);
}
int Collator::compare(const std::string& lhs, const std::string& rhs) const { 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 { std::string Collator::resolvedLocale() const { return impl->resolvedLocale(); }
return impl->resolvedLocale();
}
} // namespace platform } // namespace platform
} // namespace mbgl } // namespace mbgl

View File

@@ -3,17 +3,17 @@
#include <mbgl/storage/resource_options.hpp> #include <mbgl/storage/resource_options.hpp>
#include <mbgl/storage/response.hpp> #include <mbgl/storage/response.hpp>
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/client_options.hpp> #include <mbgl/util/client_options.hpp>
#include <mbgl/util/http_header.hpp> #include <mbgl/util/http_header.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/version.hpp> #include <mbgl/util/version.hpp>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <mbgl/interface/native_apple_interface.h> #include <mbgl/interface/native_apple_interface.h>
#include <mutex>
#include <chrono> #include <chrono>
#include <mutex>
@interface MBGLBundleCanary : NSObject @interface MBGLBundleCanary : NSObject
@end @end
@@ -26,12 +26,10 @@ namespace mbgl {
// Data that is shared between the requesting thread and the thread running the completion handler. // Data that is shared between the requesting thread and the thread running the completion handler.
class HTTPRequestShared { class HTTPRequestShared {
public: public:
HTTPRequestShared(Response& response_, util::AsyncTask& async_) HTTPRequestShared(Response &response_, util::AsyncTask &async_)
: response(response_), : response(response_), async(async_) {}
async(async_) {
}
void notify(const Response& response_) { void notify(const Response &response_) {
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
if (!cancelled) { if (!cancelled) {
response = response_; response = response_;
@@ -48,16 +46,14 @@ private:
std::mutex mutex; std::mutex mutex;
bool cancelled = false; bool cancelled = false;
Response& response; Response &response;
util::AsyncTask& async; util::AsyncTask &async;
}; };
class HTTPRequest : public AsyncRequest { class HTTPRequest : public AsyncRequest {
public: public:
HTTPRequest(FileSource::Callback callback_) HTTPRequest(FileSource::Callback callback_)
: shared(std::make_shared<HTTPRequestShared>(response, async)), : shared(std::make_shared<HTTPRequestShared>(response, async)), callback(callback_) {}
callback(callback_) {
}
~HTTPRequest() override { ~HTTPRequest() override {
shared->cancel(); shared->cancel();
@@ -68,25 +64,26 @@ public:
} }
std::shared_ptr<HTTPRequestShared> shared; std::shared_ptr<HTTPRequestShared> shared;
NSURLSessionDataTask* task = nil; NSURLSessionDataTask *task = nil;
private: private:
FileSource::Callback callback; FileSource::Callback callback;
Response response; Response response;
util::AsyncTask async { [this] { util::AsyncTask async{[this] {
// Calling `callback` may result in deleting `this`. Copy data to temporaries first. // Calling `callback` may result in deleting `this`. Copy data to temporaries first.
auto callback_ = callback; auto callback_ = callback;
auto response_ = response; auto response_ = response;
callback_(response_); callback_(response_);
} }; }};
}; };
class HTTPFileSource::Impl { class HTTPFileSource::Impl {
public: public:
Impl(const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_) Impl(const ResourceOptions &resourceOptions_, const ClientOptions &clientOptions_)
: resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) { : resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {
NSURLSessionConfiguration *sessionConfig = MLNNativeNetworkManager.sharedManager.sessionConfiguration; NSURLSessionConfiguration *sessionConfig =
MLNNativeNetworkManager.sharedManager.sessionConfiguration;
session = [NSURLSession sessionWithConfiguration:sessionConfig]; session = [NSURLSession sessionWithConfiguration:sessionConfig];
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) { if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
@@ -102,12 +99,12 @@ public:
void setClientOptions(ClientOptions options); void setClientOptions(ClientOptions options);
ClientOptions getClientOptions(); ClientOptions getClientOptions();
NSURLSession* session = nil; NSURLSession *session = nil;
NSString* userAgent = nil; NSString *userAgent = nil;
private: private:
NSString* getUserAgent() const; NSString *getUserAgent() const;
NSBundle* getSDKBundle() const; NSBundle *getSDKBundle() const;
ResourceOptions resourceOptions; ResourceOptions resourceOptions;
ClientOptions clientOptions; ClientOptions clientOptions;
}; };
@@ -116,17 +113,11 @@ void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) {
resourceOptions = options; resourceOptions = options;
} }
ResourceOptions HTTPFileSource::Impl::getResourceOptions() { ResourceOptions HTTPFileSource::Impl::getResourceOptions() { return resourceOptions.clone(); }
return resourceOptions.clone();
}
void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { clientOptions = options; }
clientOptions = options;
}
ClientOptions HTTPFileSource::Impl::getClientOptions() { ClientOptions HTTPFileSource::Impl::getClientOptions() { return clientOptions.clone(); }
return clientOptions.clone();
}
NSString *HTTPFileSource::Impl::getUserAgent() const { NSString *HTTPFileSource::Impl::getUserAgent() const {
NSMutableArray *userAgentComponents = [NSMutableArray array]; NSMutableArray *userAgentComponents = [NSMutableArray array];
@@ -134,8 +125,12 @@ NSString *HTTPFileSource::Impl::getUserAgent() const {
NSBundle *appBundle = [NSBundle mainBundle]; NSBundle *appBundle = [NSBundle mainBundle];
if (appBundle) { if (appBundle) {
NSString *appName = appBundle.infoDictionary[@"CFBundleName"]; NSString *appName = appBundle.infoDictionary[@"CFBundleName"];
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", [userAgentComponents
appName.length ? appName : appBundle.infoDictionary[@"CFBundleIdentifier"], addObject:[NSString
stringWithFormat:@"%@/%@",
appName.length
? appName
: appBundle.infoDictionary[@"CFBundleIdentifier"],
appBundle.infoDictionary[@"CFBundleShortVersionString"]]]; appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
} else { } else {
[userAgentComponents addObject:[NSProcessInfo processInfo].processName]; [userAgentComponents addObject:[NSProcessInfo processInfo].processName];
@@ -145,8 +140,9 @@ NSString *HTTPFileSource::Impl::getUserAgent() const {
if (sdkBundle) { if (sdkBundle) {
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"]; NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
if (versionString) { if (versionString) {
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", [userAgentComponents
sdkBundle.infoDictionary[@"CFBundleName"], versionString]]; addObject:[NSString stringWithFormat:@"%@/%@", sdkBundle.infoDictionary[@"CFBundleName"],
versionString]];
} }
} }
@@ -171,8 +167,9 @@ NSString *HTTPFileSource::Impl::getUserAgent() const {
NSString *systemVersion = nil; NSString *systemVersion = nil;
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) { if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
systemVersion = [NSString stringWithFormat:@"%ld.%ld.%ld", systemVersion =
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion]; [NSString stringWithFormat:@"%ld.%ld.%ld", (long)osVersion.majorVersion,
(long)osVersion.minorVersion, (long)osVersion.patchVersion];
} }
if (systemVersion) { if (systemVersion) {
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]]; [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
@@ -206,28 +203,26 @@ NSBundle *HTTPFileSource::Impl::getSDKBundle() const {
return bundle; return bundle;
} }
HTTPFileSource::HTTPFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) HTTPFileSource::HTTPFileSource(const ResourceOptions &resourceOptions,
: impl(std::make_unique<Impl>(resourceOptions, clientOptions)) { const ClientOptions &clientOptions)
} : impl(std::make_unique<Impl>(resourceOptions, clientOptions)) {}
HTTPFileSource::~HTTPFileSource() = default; HTTPFileSource::~HTTPFileSource() = default;
MLN_APPLE_EXPORT MLN_APPLE_EXPORT
BOOL isValidMapboxEndpoint(NSURL *url) { BOOL isValidMapboxEndpoint(NSURL *url) {
return ([url.host isEqualToString:@"mapbox.com"] || return ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"] ||
[url.host hasSuffix:@".mapbox.com"] || [url.host isEqualToString:@"mapbox.cn"] || [url.host hasSuffix:@".mapbox.cn"]);
[url.host isEqualToString:@"mapbox.cn"] ||
[url.host hasSuffix:@".mapbox.cn"]);
} }
MLN_APPLE_EXPORT MLN_APPLE_EXPORT
NSURL *resourceURL(const Resource& resource) { NSURL *resourceURL(const Resource &resource) {
NSURL *url = [NSURL URLWithString:@(resource.url.c_str())]; NSURL *url = [NSURL URLWithString:@(resource.url.c_str())];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
if (isValidMapboxEndpoint(url)) { if (isValidMapboxEndpoint(url)) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
NSMutableArray *queryItems = [NSMutableArray array]; NSMutableArray *queryItems = [NSMutableArray array];
if (resource.usage == Resource::Usage::Offline) { if (resource.usage == Resource::Usage::Offline) {
@@ -245,9 +240,11 @@ NSURL *resourceURL(const Resource& resource) {
return url; return url;
} }
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, std::function<void(Response)> callback) { std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource &resource,
std::function<void(Response)> callback) {
auto request = std::make_unique<HTTPRequest>(std::move(callback)); auto request = std::make_unique<HTTPRequest>(std::move(callback));
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below. auto shared = request->shared; // Explicit copy so that it also gets copied into the completion
// handler block below.
@autoreleasepool { @autoreleasepool {
NSURL *url = resourceURL(resource); NSURL *url = resourceURL(resource);
@@ -255,16 +252,15 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url]; NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
if (resource.priorEtag) { if (resource.priorEtag) {
[req addValue:@(resource.priorEtag->c_str()) [req addValue:@(resource.priorEtag->c_str()) forHTTPHeaderField:@"If-None-Match"];
forHTTPHeaderField:@"If-None-Match"];
} else if (resource.priorModified) { } else if (resource.priorModified) {
[req addValue:@(util::rfc1123(*resource.priorModified).c_str()) [req addValue:@(util::rfc1123(*resource.priorModified).c_str())
forHTTPHeaderField:@"If-Modified-Since"]; forHTTPHeaderField:@"If-Modified-Since"];
} }
if (resource.dataRange) { if (resource.dataRange) {
NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld", NSString *rangeHeader = [NSString
static_cast<long long>(resource.dataRange->first), stringWithFormat:@"bytes=%lld-%lld", static_cast<long long>(resource.dataRange->first),
static_cast<long long>(resource.dataRange->second)]; static_cast<long long>(resource.dataRange->second)];
[req setValue:rangeHeader forHTTPHeaderField:@"Range"]; [req setValue:rangeHeader forHTTPHeaderField:@"Range"];
} }
@@ -298,18 +294,18 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
request->task = [session request->task = [session
dataTaskWithRequest:req dataTaskWithRequest:req
completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
session = nil; session = nil;
if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) { if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNInternalNetworkResponse *networkResponse = [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error]; MLNInternalNetworkResponse *networkResponse =
[MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error];
networkResponse = [networkManager.delegate didReceiveResponse:networkResponse]; networkResponse = [networkManager.delegate didReceiveResponse:networkResponse];
data = networkResponse.data; data = networkResponse.data;
res = networkResponse.response; res = networkResponse.response;
error = networkResponse.error; error = networkResponse.error;
} }
if (error && [error code] == NSURLErrorCancelled) { if (error && [error code] == NSURLErrorCancelled) {
[MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res]; [MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res];
return; return;
@@ -319,11 +315,13 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
using Error = Response::Error; using Error = Response::Error;
if (error) { if (error) {
[MLNNativeNetworkManager.sharedManager errorLog:@"Requesting: %@ failed with error: %@", req.URL, error.debugDescription]; [MLNNativeNetworkManager.sharedManager
errorLog:@"Requesting: %@ failed with error: %@", req.URL,
error.debugDescription];
if (data) { if (data) {
response.data = response.data =
std::make_shared<std::string>((const char*)[data bytes], [data length]); std::make_shared<std::string>((const char *)[data bytes], [data length]);
} }
switch ([error code]) { switch ([error code]) {
@@ -352,7 +350,9 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
} }
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
const long responseCode = [(NSHTTPURLResponse *)res statusCode]; const long responseCode = [(NSHTTPURLResponse *)res statusCode];
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath, responseCode]; [MLNNativeNetworkManager.sharedManager
debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath,
responseCode];
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
NSString *cache_control = [headers objectForKey:@"Cache-Control"]; NSString *cache_control = [headers objectForKey:@"Cache-Control"];
@@ -378,7 +378,8 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
} }
if (responseCode == 200 || responseCode == 206) { if (responseCode == 200 || responseCode == 206) {
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]); response.data =
std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else if (responseCode == 204 || (responseCode == 404 && isTile)) { } else if (responseCode == 204 || (responseCode == 404 && isTile)) {
response.noContent = true; response.noContent = true;
} else if (responseCode == 304) { } else if (responseCode == 304) {
@@ -401,18 +402,21 @@ std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource,
xRateLimitReset = std::string([xReset UTF8String]); xRateLimitReset = std::string([xReset UTF8String]);
} }
response.error = std::make_unique<Error>(Error::Reason::RateLimit, "HTTP status code 429", http::parseRetryHeaders(retryAfter, xRateLimitReset));
} else if (responseCode >= 500 && responseCode < 600) {
response.error = response.error =
std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::make_unique<Error>(Error::Reason::RateLimit, "HTTP status code 429",
std::to_string(responseCode)); http::parseRetryHeaders(retryAfter, xRateLimitReset));
} else if (responseCode >= 500 && responseCode < 600) {
response.error = std::make_unique<Error>(
Error::Reason::Server,
std::string{"HTTP status code "} + std::to_string(responseCode));
} else { } else {
response.error = response.error =
std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::make_unique<Error>(Error::Reason::Other, std::string{"HTTP status code "} +
std::to_string(responseCode)); std::to_string(responseCode));
} }
} else if ([url isFileURL]) { } else if ([url isFileURL]) {
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]); response.data =
std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else { } else {
// This should never happen. // This should never happen.
response.error = std::make_unique<Error>(Error::Reason::Other, response.error = std::make_unique<Error>(Error::Reason::Other,
@@ -432,16 +436,12 @@ void HTTPFileSource::setResourceOptions(ResourceOptions options) {
impl->setResourceOptions(options.clone()); impl->setResourceOptions(options.clone());
} }
ResourceOptions HTTPFileSource::getResourceOptions() { ResourceOptions HTTPFileSource::getResourceOptions() { return impl->getResourceOptions(); }
return impl->getResourceOptions();
}
void HTTPFileSource::setClientOptions(ClientOptions options) { void HTTPFileSource::setClientOptions(ClientOptions options) {
impl->setClientOptions(options.clone()); impl->setClientOptions(options.clone());
} }
ClientOptions HTTPFileSource::getClientOptions() { ClientOptions HTTPFileSource::getClientOptions() { return impl->getClientOptions(); }
return impl->getClientOptions();
}
} } // namespace mbgl

View File

@@ -34,19 +34,18 @@ CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src
constexpr const size_t bytesPerPixel = 4; constexpr const size_t bytesPerPixel = 4;
constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel; constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel;
const size_t bytesPerRow = bytesPerPixel * src.size.width; const size_t bytesPerRow = bytesPerPixel * src.size.width;
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast); const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel, return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel, bytesPerRow,
bytesPerRow, *colorSpace, *colorSpace, bitmapInfo, *provider, NULL, false, kCGRenderingIntentDefault);
bitmapInfo, *provider,
NULL, false, kCGRenderingIntentDefault);
} }
mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) { mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) {
const size_t width = CGImageGetWidth(src); const size_t width = CGImageGetWidth(src);
const size_t height = CGImageGetHeight(src); const size_t height = CGImageGetHeight(src);
mbgl::PremultipliedImage image({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) }); mbgl::PremultipliedImage image({static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) { if (!colorSpace) {
@@ -56,10 +55,11 @@ mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) {
constexpr const size_t bitsPerComponent = 8; constexpr const size_t bitsPerComponent = 8;
constexpr const size_t bytesPerPixel = 4; constexpr const size_t bytesPerPixel = 4;
const size_t bytesPerRow = bytesPerPixel * width; const size_t bytesPerRow = bytesPerPixel * width;
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast); const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
CGContextHandle context(CGBitmapContextCreate( CGContextHandle context(CGBitmapContextCreate(image.data.get(), width, height, bitsPerComponent,
image.data.get(), width, height, bitsPerComponent, bytesPerRow, *colorSpace, bitmapInfo)); bytesPerRow, *colorSpace, bitmapInfo));
if (!context) { if (!context) {
throw std::runtime_error("CGBitmapContextCreate failed"); throw std::runtime_error("CGBitmapContextCreate failed");
} }

View File

@@ -1,15 +1,15 @@
#include <mbgl/text/local_glyph_rasterizer.hpp> #include <mbgl/text/local_glyph_rasterizer.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/i18n.hpp> #include <mbgl/util/i18n.hpp>
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp> #include <mbgl/util/platform.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/interface/native_apple_interface.h> #include <mbgl/interface/native_apple_interface.h>
#include <unordered_map> #include <unordered_map>
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h> #import <CoreText/CoreText.h>
#import <Foundation/Foundation.h>
#import <ImageIO/ImageIO.h> #import <ImageIO/ImageIO.h>
#import "CFHandle.hpp" #import "CFHandle.hpp"
@@ -78,11 +78,13 @@ public:
glyph rasterization globally. The system font is a good default value to glyph rasterization globally. The system font is a good default value to
pass into this constructor. pass into this constructor.
*/ */
Impl(const std::optional<std::string> fallbackFontNames_) Impl(const std::optional<std::string> fallbackFontNames_) {
{ fallbackFontNames =
fallbackFontNames = [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"]; [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"];
if (fallbackFontNames_) { if (fallbackFontNames_) {
fallbackFontNames = [fallbackFontNames ?: @[] arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str()) componentsSeparatedByString:@"\n"]]; fallbackFontNames = [fallbackFontNames ?: @[]
arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str())
componentsSeparatedByString:@"\n"]];
} }
} }
@@ -105,14 +107,16 @@ public:
@post The caller is responsible for releasing the font descriptor. @post The caller is responsible for releasing the font descriptor.
*/ */
CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack) { CTFontDescriptorRef createFontDescriptor(const FontStack &fontStack) {
NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count]; NSMutableArray *fontNames =
for (auto& fontName : fontStack) { [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count];
for (auto &fontName : fontStack) {
// Per the MapLibre Style Spec, the text-font property comes // Per the MapLibre Style Spec, the text-font property comes
// with these last resort fonts by default, but they shouldn’t take // with these last resort fonts by default, but they shouldn’t take
// precedence over any application or system fallback font that may // precedence over any application or system fallback font that may
// be more appropriate to the current device. // be more appropriate to the current device.
if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) { if (fontName != util::LAST_RESORT_ALPHABETIC_FONT &&
fontName != util::LAST_RESORT_PAN_UNICODE_FONT) {
[fontNames addObject:@(fontName.c_str())]; [fontNames addObject:@(fontName.c_str())];
} }
} }
@@ -120,7 +124,7 @@ public:
if (!fontNames.count) { if (!fontNames.count) {
NSDictionary *fontAttributes = @{ NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM), (NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
}; };
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes); return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
} }
@@ -128,17 +132,19 @@ public:
// Apply the first font name to the returned font descriptor; apply the // Apply the first font name to the returned font descriptor; apply the
// rest of the font names to the cascade list. // rest of the font names to the cascade list.
CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject; CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject;
CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks)); CFMutableArrayRefHandle fallbackDescriptors(
CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks));
for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) { for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) {
NSDictionary *fontAttributes = @{ NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM), (NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
// This attribute is technically supposed to be a font’s // This attribute is technically supposed to be a font’s
// PostScript name, but Core Text will fall back to matching // PostScript name, but Core Text will fall back to matching
// font display names and font family names. // font display names and font family names.
(NSString *)kCTFontNameAttribute: name, (NSString *)kCTFontNameAttribute : name,
}; };
CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes)); CTFontDescriptorRefHandle descriptor(
CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
CFArrayAppendValue(*fallbackDescriptors, *descriptor); CFArrayAppendValue(*fallbackDescriptors, *descriptor);
} }
@@ -148,15 +154,14 @@ public:
kCTFontCascadeListAttribute, kCTFontCascadeListAttribute,
}; };
CFTypeRef values[] = { CFTypeRef values[] = {
(__bridge CFNumberRef)@(util::ONE_EM), (__bridge CFNumberRef) @(util::ONE_EM),
mainFontName, mainFontName,
*fallbackDescriptors, *fallbackDescriptors,
}; };
CFDictionaryRefHandle attributes( CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values,
(const void**)&values, sizeof(keys) / sizeof(keys[0]), sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks)); &kCFTypeDictionaryValueCallBacks));
return CTFontDescriptorCreateWithAttributes(*attributes); return CTFontDescriptorCreateWithAttributes(*attributes);
} }
@@ -165,12 +170,10 @@ private:
NSArray<NSString *> *fallbackFontNames; NSArray<NSString *> *fallbackFontNames;
}; };
LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional<std::string>& fontFamily) LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional<std::string> &fontFamily)
: impl(std::make_unique<Impl>(fontFamily)) : impl(std::make_unique<Impl>(fontFamily)) {}
{}
LocalGlyphRasterizer::~LocalGlyphRasterizer() LocalGlyphRasterizer::~LocalGlyphRasterizer() {}
{}
/** /**
Returns whether the rasterizer can rasterize a glyph for the given codepoint. Returns whether the rasterizer can rasterize a glyph for the given codepoint.
@@ -178,7 +181,7 @@ LocalGlyphRasterizer::~LocalGlyphRasterizer()
@param glyphID A font-agnostic Unicode codepoint, not a glyph index. @param glyphID A font-agnostic Unicode codepoint, not a glyph index.
@returns Whether a glyph for the codepoint can be rasterized. @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 #if MBGL_DARWIN_NO_REMOTE_FONTS
return impl->isEnabled(); return impl->isEnabled();
#else #else
@@ -197,36 +200,39 @@ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID)
@param isBold use kCTFontBoldTrait if it is true. @param isBold use kCTFontBoldTrait if it is true.
@returns An image containing the glyph. @returns An image containing the glyph.
*/ */
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics, BOOL isBold) { PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics &metrics,
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1)); BOOL isBold) {
CFStringRefHandle string(
CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar *>(&glyphID), 1));
if (!string) { if (!string) {
throw std::runtime_error("Unable to create string from codepoint"); throw std::runtime_error("Unable to create string from codepoint");
} }
// Create a bold variant of the font // Create a bold variant of the font
CTFontRefHandle boldFont(CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait)); CTFontRefHandle boldFont(
CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait));
if (!boldFont) { if (!boldFont) {
CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font)); CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font));
NSString* familyName = (__bridge NSString *)(*familyNameHandle); NSString *familyName = (__bridge NSString *)(*familyNameHandle);
std::string stdFamilyName(familyName.UTF8String); std::string stdFamilyName(familyName.UTF8String);
Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName); 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 }; CFStringRef keys[] = {kCTFontAttributeName};
CFTypeRef values[] = { drawFont }; CFTypeRef values[] = {drawFont};
CFDictionaryRefHandle attributes( CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys, CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values,
(const void**)&values, sizeof(keys) / sizeof(keys[0]), sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks)); &kCFTypeDictionaryValueCallBacks));
if (!attributes) { if (!attributes) {
throw std::runtime_error("Unable to create attributed string attributes dictionary"); throw std::runtime_error("Unable to create attributed string attributes dictionary");
} }
CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes)); CFAttributedStringRefHandle attrString(
CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
if (!attrString) { if (!attrString) {
throw std::runtime_error("Unable to create attributed string"); throw std::runtime_error("Unable to create attributed string");
} }
@@ -249,15 +255,11 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics
constexpr const size_t bitsPerComponent = 8; constexpr const size_t bitsPerComponent = 8;
constexpr const size_t bytesPerPixel = 4; constexpr const size_t bytesPerPixel = 4;
const size_t bytesPerRow = bytesPerPixel * size.width; const size_t bytesPerRow = bytesPerPixel * size.width;
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast); const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
CGContextHandle context(CGBitmapContextCreate( CGContextHandle context(CGBitmapContextCreate(rgbaBitmap.data.get(), size.width, size.height,
rgbaBitmap.data.get(), bitsPerComponent, bytesPerRow, *colorSpace,
size.width,
size.height,
bitsPerComponent,
bytesPerRow,
*colorSpace,
bitmapInfo)); bitmapInfo));
if (!context) { if (!context) {
throw std::runtime_error("CGBitmapContextCreate failed"); throw std::runtime_error("CGBitmapContextCreate failed");
@@ -294,7 +296,7 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics
@returns A glyph representation of the given codepoint with its bitmap data and @returns A glyph representation of the given codepoint with its bitmap data and
metrics set. metrics set.
*/ */
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) { Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack &fontStack, GlyphID glyphID) {
Glyph manufacturedGlyph; Glyph manufacturedGlyph;
CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack)); CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack));
CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL)); CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL));
@@ -307,12 +309,14 @@ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID g
// Only check the first font name to detect if the user prefers using bold // Only check the first font name to detect if the user prefers using bold
if (!fontStack.empty()) { if (!fontStack.empty()) {
std::string lowercaseFont = platform::lowercase(fontStack.front()); std::string lowercaseFont = platform::lowercase(fontStack.front());
if (lowercaseFont.find("bold") != std::string::npos && lowercaseFont.find("semibold") == std::string::npos) { if (lowercaseFont.find("bold") != std::string::npos &&
lowercaseFont.find("semibold") == std::string::npos) {
isBold = YES; isBold = YES;
} }
} }
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold); PremultipliedImage rgbaBitmap =
drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height); Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
// Copy alpha values from RGBA bitmap into the AlphaImage output // Copy alpha values from RGBA bitmap into the AlphaImage output

View File

@@ -1,14 +1,15 @@
#include <mbgl/util/logging.hpp>
#include <mbgl/util/enum.hpp> #include <mbgl/util/enum.hpp>
#include <mbgl/util/logging.hpp>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
namespace mbgl { namespace mbgl {
void Log::platformRecord(EventSeverity severity, const std::string &msg) { void Log::platformRecord(EventSeverity severity, const std::string &msg) {
NSString *message = NSString *message = [[NSString alloc] initWithBytes:msg.data()
[[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding]; length:msg.size()
encoding:NSUTF8StringEncoding];
NSLog(@"[%s] %@", Enum<EventSeverity>::toString(severity), message); NSLog(@"[%s] %@", Enum<EventSeverity>::toString(severity), message);
}
} }
} // namespace mbgl

View File

@@ -1,8 +1,8 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <mbgl/platform/thread.hpp>
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp> #include <mbgl/util/platform.hpp>
#include <mbgl/platform/thread.hpp>
#include <pthread.h> #include <pthread.h>
@@ -42,11 +42,9 @@ void setCurrentThreadPriority(double priority) {
} }
} }
void attachThread() { void attachThread() {}
}
void detachThread() { void detachThread() {}
}
} } // namespace platform
} } // namespace mbgl

View File

@@ -7,11 +7,11 @@ namespace platform {
std::string formatNumber(double number, const std::string& localeId, const std::string& currency, std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits) { uint8_t minFractionDigits, uint8_t maxFractionDigits) {
// Create a new formatter each time to avoid state pollution between calls // Create a new formatter each time to avoid state pollution between calls
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil; numberFormatter.locale =
!localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil; numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
if (currency.empty()) { if (currency.empty()) {
@@ -21,7 +21,7 @@ std::string formatNumber(double number, const std::string& localeId, const std::
} else { } else {
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
} }
NSString *formatted = [numberFormatter stringFromNumber:@(number)]; NSString* formatted = [numberFormatter stringFromNumber:@(number)];
std::string result = std::string([formatted UTF8String]); std::string result = std::string([formatted UTF8String]);
return result; return result;
} }

View File

@@ -11,7 +11,7 @@ std::string uppercase(const std::string &string) {
encoding:NSUTF8StringEncoding encoding:NSUTF8StringEncoding
freeWhenDone:NO]; freeWhenDone:NO];
NSString *uppercase = [original uppercaseString]; NSString *uppercase = [original uppercaseString];
std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding], std::string result{[uppercase cStringUsingEncoding:NSUTF8StringEncoding],
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; [uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result; return result;
} }
@@ -22,7 +22,7 @@ std::string lowercase(const std::string &string) {
encoding:NSUTF8StringEncoding encoding:NSUTF8StringEncoding
freeWhenDone:NO]; freeWhenDone:NO];
NSString *lowercase = [original lowercaseString]; NSString *lowercase = [original lowercaseString];
std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding], std::string result{[lowercase cStringUsingEncoding:NSUTF8StringEncoding],
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]}; [lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result; return result;
} }

View File

@@ -10,7 +10,8 @@
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>(); _actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_actionJournalOptionsInternal->withPath([paths.firstObject stringByAppendingPathComponent:@""].UTF8String); _actionJournalOptionsInternal->withPath(
[paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
} }
return self; return self;
} }
@@ -27,11 +28,11 @@
return _actionJournalOptionsInternal->enabled(); return _actionJournalOptionsInternal->enabled();
} }
- (void)setPath:(NSString*)value { - (void)setPath:(NSString *)value {
_actionJournalOptionsInternal->withPath(value.UTF8String); _actionJournalOptionsInternal->withPath(value.UTF8String);
} }
- (NSString*)path { - (NSString *)path {
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()]; return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
} }

View File

@@ -1,35 +1,37 @@
#import "MLNAttributionInfo_Private.h" #import "MLNAttributionInfo_Private.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#else #else
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#endif #endif
#import "MLNSettings.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNMapCamera.h" #import "MLNMapCamera.h"
#import "MLNSettings.h"
#import "NSArray+MLNAdditions.h" #import "NSArray+MLNAdditions.h"
#import "NSBundle+MLNAdditions.h" #import "NSBundle+MLNAdditions.h"
#import "NSString+MLNAdditions.h" #import "NSString+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#include <string> #include <string>
@implementation MLNAttributionInfo @implementation MLNAttributionInfo
+ (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { + (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString
fontSize:(CGFloat)fontSize
linkColor:(nullable MLNColor *)linkColor {
if (!htmlString) { if (!htmlString) {
return @[]; return @[];
} }
NSDictionary *options = @{ NSDictionary *options = @{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding), NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
}; };
// Apply a bogus, easily detectable style rule to any feedback link, since // Apply a bogus, easily detectable style rule to any feedback link, since
// NSAttributedString doesn’t preserve the class attribute. // NSAttributedString doesn’t preserve the class attribute.
NSMutableString *css = [NSMutableString stringWithString: NSMutableString *css = [NSMutableString
@"html { font-family: -apple-system, -apple-system-font, sans-serif; }" stringWithString:@"html { font-family: -apple-system, -apple-system-font, sans-serif; }"
@".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"]; @".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
if (fontSize) { if (fontSize) {
NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize]; NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize];
@@ -58,17 +60,18 @@
} }
#endif #endif
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha]; [linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
[css appendFormat: [css appendFormat:@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", red * 100, green * 100,
@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", blue * 100, alpha];
red * 100, green * 100, blue * 100, alpha];
} }
NSString *styledHTML = [NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString]; NSString *styledHTML =
[NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding]; NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
__block NSMutableAttributedString *attributedString; __block NSMutableAttributedString *attributedString;
dispatch_block_t initialization = ^{ dispatch_block_t initialization = ^{
// This initializer should be called from a global or main queue. https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata // 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 attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
options:options options:options
documentAttributes:nil documentAttributes:nil
@@ -81,18 +84,20 @@
initialization(); initialization();
} }
#else #else
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithHTML:htmlData
options:options options:options
documentAttributes:nil]; documentAttributes:nil];
#endif #endif
NSMutableArray *infos = [NSMutableArray array]; NSMutableArray *infos = [NSMutableArray array];
[attributedString enumerateAttribute:NSLinkAttributeName [attributedString
enumerateAttribute:NSLinkAttributeName
inRange:attributedString.mgl_wholeRange inRange:attributedString.mgl_wholeRange
options:0 options:0
usingBlock: usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { MLNCAssert(!value || [value isKindOfClass:[NSURL class]],
MLNCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL."); @"If present, URL attribute must be an NSURL.");
// Detect feedback links by the bogus style rule applied above. // Detect feedback links by the bogus style rule applied above.
NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
@@ -104,8 +109,10 @@
[attributedString removeAttribute:NSStrokeWidthAttributeName range:range]; [attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
} }
// Clean up strings by stripping punctuation and whitespace (often present for the web). // Clean up strings by stripping punctuation and whitespace (often present for the
NSMutableCharacterSet *charset = [NSMutableCharacterSet whitespaceAndNewlineCharacterSet]; // web).
NSMutableCharacterSet *charset =
[NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; [charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range] NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
mgl_attributedStringByTrimmingCharactersInSet:charset]; mgl_attributedStringByTrimmingCharactersInSet:charset];
@@ -118,16 +125,19 @@
// Remove the link, because it forces the text to be blue on macOS 10.12 // Remove the link, because it forces the text to be blue on macOS 10.12
// and above. // and above.
NSMutableAttributedString *unlinkedTitle = [title mutableCopy]; NSMutableAttributedString *unlinkedTitle = [title mutableCopy];
[unlinkedTitle removeAttribute:NSLinkAttributeName range:unlinkedTitle.mgl_wholeRange]; [unlinkedTitle removeAttribute:NSLinkAttributeName
range:unlinkedTitle.mgl_wholeRange];
MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle URL:value]; MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle
URL:value];
info.feedbackLink = isFeedbackLink; info.feedbackLink = isFeedbackLink;
[infos addObject:info]; [infos addObject:info];
}]; }];
return infos; return infos;
} }
+ (NSAttributedString *)attributedStringForAttributionInfos:(NSArray<MLNAttributionInfo *> *)attributionInfos { + (NSAttributedString *)attributedStringForAttributionInfos:
(NSArray<MLNAttributionInfo *> *)attributionInfos {
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count]; NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
for (MLNAttributionInfo *info in attributionInfos) { for (MLNAttributionInfo *info in attributionInfos) {
NSMutableAttributedString *title = info.title.mutableCopy; NSMutableAttributedString *title = info.title.mutableCopy;
@@ -147,27 +157,29 @@
return self; return self;
} }
- (id)copyWithZone:(nullable NSZone *)zone - (id)copyWithZone:(nullable NSZone *)zone {
{ MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title URL:_URL];
MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title
URL:_URL];
info.feedbackLink = _feedbackLink; info.feedbackLink = _feedbackLink;
return info; return info;
} }
- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style - (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style {
{ NSString *openStreetMap =
NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", @"OpenStreetMap full name attribution"); NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap",
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", @"OpenStreetMap short name attribution"); @"OpenStreetMap full name attribution");
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM",
@"OpenStreetMap short name attribution");
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title]; NSMutableAttributedString *title =
[[NSMutableAttributedString alloc] initWithAttributedString:self.title];
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; [title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort); BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap"
withString:isAbbreviated ? OSM : openStreetMap
options:NSCaseInsensitiveSearch options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [title.mutableString length])]; range:NSMakeRange(0, [title.mutableString length])];
} }
@@ -176,7 +188,8 @@
} }
- (BOOL)isEqual:(id)object { - (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 { - (NSUInteger)hash {
@@ -210,7 +223,8 @@
- (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info { - (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info {
__block BOOL didInsertInfo = NO; __block BOOL didInsertInfo = NO;
__block BOOL shouldAddInfo = YES; __block BOOL shouldAddInfo = YES;
[self enumerateObjectsUsingBlock:^(MLNAttributionInfo * _Nonnull existingInfo, NSUInteger idx, BOOL * _Nonnull stop) { [self enumerateObjectsUsingBlock:^(MLNAttributionInfo *_Nonnull existingInfo, NSUInteger idx,
BOOL *_Nonnull stop) {
switch ([info subsetCompare:existingInfo]) { switch ([info subsetCompare:existingInfo]) {
case NSOrderedDescending: case NSOrderedDescending:
// The existing info object is a subset of the one we’re adding. // The existing info object is a subset of the one we’re adding.

View File

@@ -1,10 +1,10 @@
#import "MLNComputedShapeSource_Private.h" #import "MLNComputedShapeSource_Private.h"
#import "MLNMapView_Private.h"
#import "MLNSource_Private.h"
#import "MLNShape_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNMapView_Private.h"
#import "MLNShapeCollection.h" #import "MLNShapeCollection.h"
#import "MLNShape_Private.h"
#import "MLNSource_Private.h"
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp> #include <mbgl/style/sources/custom_geometry_source.hpp>
@@ -13,10 +13,13 @@
const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException"; const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException";
const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates = @"MLNShapeSourceOptionWrapsCoordinates"; const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates =
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates = @"MLNShapeSourceOptionClipsCoordinates"; @"MLNShapeSourceOptionWrapsCoordinates";
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates =
@"MLNShapeSourceOptionClipsCoordinates";
mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) { mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(
NSDictionary<MLNShapeSourceOption, id> *options) {
mbgl::style::CustomGeometrySource::Options sourceOptions; mbgl::style::CustomGeometrySource::Options sourceOptions;
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
@@ -90,13 +93,15 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
@property (nonatomic, weak, nullable) id<MLNComputedShapeSourceDataSource> dataSource; @property (nonatomic, weak, nullable) id<MLNComputedShapeSourceDataSource> dataSource;
@property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource; @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 @end
@implementation MLNComputedShapeSourceFetchOperation @implementation MLNComputedShapeSourceFetchOperation
- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID { - (instancetype)initForSource:(MLNComputedShapeSource *)source
tile:(const mbgl::CanonicalTileID &)tileID {
self = [super init]; self = [super init];
_z = tileID.z; _z = tileID.z;
_x = tileID.x; _x = tileID.x;
@@ -104,7 +109,8 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
_dataSource = source.dataSource; _dataSource = source.dataSource;
mbgl::style::CustomGeometrySource *rawSource = static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource); mbgl::style::CustomGeometrySource *rawSource =
static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
_rawSource = rawSource; _rawSource = rawSource;
return self; return self;
} }
@@ -114,24 +120,23 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
return; return;
} }
NSArray<MLNShape <MLNFeature> *> *data; NSArray<MLNShape<MLNFeature> *> *data;
if(!self.dataSource) { if (!self.dataSource) {
data = nil; data = nil;
} else if(self.dataSourceImplementsFeaturesForTile) { } else if (self.dataSourceImplementsFeaturesForTile) {
data = [self.dataSource featuresInTileAtX:self.x data = [self.dataSource featuresInTileAtX:self.x y:self.y zoomLevel:self.z];
y:self.y
zoomLevel:self.z];
} else { } else {
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
data = [self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds) data =
[self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds)
zoomLevel:self.z]; zoomLevel:self.z];
} }
if(![self isCancelled]) { if (![self isCancelled]) {
mbgl::FeatureCollection featureCollection; mbgl::FeatureCollection featureCollection;
featureCollection.reserve(data.count); featureCollection.reserve(data.count);
for (MLNShape <MLNFeature> * feature in data) { for (MLNShape<MLNFeature> *feature in data) {
if ([feature isMemberOfClass:[MLNShapeCollection class]]) { if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
@@ -146,7 +151,7 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
const auto geojson = mbgl::GeoJSON{featureCollection}; const auto geojson = mbgl::GeoJSON{featureCollection};
// Note: potential race condition with `cancel` // Note: potential race condition with `cancel`
if(![self isCancelled]) { if (![self isCancelled]) {
if (auto *rawSource = self.rawSource) { if (auto *rawSource = self.rawSource) {
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson); rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
} }
@@ -163,19 +168,21 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
@implementation MLNComputedShapeSource @implementation MLNComputedShapeSource
- (instancetype)initWithIdentifier:(NSString *)identifier options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init]; NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier]; requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
requestQueue.qualityOfService = NSQualityOfServiceUtility; requestQueue.qualityOfService = NSQualityOfServiceUtility;
requestQueue.maxConcurrentOperationCount = 4; requestQueue.maxConcurrentOperationCount = 4;
auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options); auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options);
sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID]; NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self
tile:tileID];
[requestQueue addOperation:operation]; [requestQueue addOperation:operation];
}; };
sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) { for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) {
if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) { if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) {
[operation cancel]; [operation cancel];
@@ -183,7 +190,8 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
} }
}; };
auto source = std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions); auto source =
std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
if (self = [super initWithPendingSource:std::move(source)]) { if (self = [super initWithPendingSource:std::move(source)]) {
_requestQueue = requestQueue; _requestQueue = requestQueue;
@@ -191,7 +199,9 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
return self; return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
if (self = [self initWithIdentifier:identifier options:options]) { if (self = [self initWithIdentifier:identifier options:options]) {
[self setDataSource:dataSource]; [self setDataSource:dataSource];
} }
@@ -202,12 +212,16 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
[self.requestQueue cancelAllOperations]; [self.requestQueue cancelAllOperations];
} }
- (void)setFeatures:(NSArray<MLNShape <MLNFeature> *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel { - (void)setFeatures:(NSArray<MLNShape<MLNFeature> *> *)features
inTileAtX:(NSUInteger)x
y:(NSUInteger)y
zoomLevel:(NSUInteger)zoomLevel {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); mbgl::CanonicalTileID tileID =
mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
mbgl::FeatureCollection featureCollection; mbgl::FeatureCollection featureCollection;
featureCollection.reserve(features.count); featureCollection.reserve(features.count);
for (MLNShape <MLNFeature> * feature in features) { for (MLNShape<MLNFeature> *feature in features) {
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>(); mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject); featureCollection.push_back(geoJsonObject);
if ([feature isMemberOfClass:[MLNShapeCollection class]]) { if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
@@ -226,13 +240,17 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
- (void)setDataSource:(id<MLNComputedShapeSourceDataSource>)dataSource { - (void)setDataSource:(id<MLNComputedShapeSourceDataSource>)dataSource {
[self.requestQueue cancelAllOperations]; [self.requestQueue cancelAllOperations];
// Check which method the datasource implements, to avoid having to check for each tile // Check which method the datasource implements, to avoid having to check for each tile
self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)]; self.dataSourceImplementsFeaturesForTile =
self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)]; [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)];
self.dataSourceImplementsFeaturesForBounds =
[dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)];
if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) { if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) {
[NSException raise:MLNInvalidDatasourceException [NSException
raise:MLNInvalidDatasourceException
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"]; format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
} else if (self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) { } else if (self.dataSourceImplementsFeaturesForBounds &&
self.dataSourceImplementsFeaturesForTile) {
[NSException raise:MLNInvalidDatasourceException [NSException raise:MLNInvalidDatasourceException
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"]; format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
} }
@@ -240,14 +258,16 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
_dataSource = dataSource; _dataSource = dataSource;
} }
- (void) invalidateBounds:(MLNCoordinateBounds)bounds { - (void)invalidateBounds:(MLNCoordinateBounds)bounds {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds)); ((mbgl::style::CustomGeometrySource *)self.rawSource)
->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds));
} }
- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z { - (void)invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y)); ((mbgl::style::CustomGeometrySource *)self.rawSource)
->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y));
} }
@end @end

View File

@@ -8,14 +8,14 @@ namespace conversion {
// A wrapper class for `id`, so as not to confuse ARC. // A wrapper class for `id`, so as not to confuse ARC.
class Holder { class Holder {
public: public:
Holder(const id v) : value(v) {} Holder(const id v) : value(v) {}
const id value; const id value;
}; };
template <> template <>
class ConversionTraits<Holder> { class ConversionTraits<Holder> {
public: public:
static bool isUndefined(const Holder& holder) { static bool isUndefined(const Holder& holder) {
const id value = holder.value; const id value = holder.value;
return !value || value == [NSNull null]; return !value || value == [NSNull null];
@@ -146,7 +146,7 @@ class ConversionTraits<Holder> {
} }
} }
private: private:
static bool _isBool(const id value) { static bool _isBool(const id value) {
if (![value isKindOfClass:[NSNumber class]]) return false; if (![value isKindOfClass:[NSNumber class]]) return false;
// char: 32-bit boolean // char: 32-bit boolean

View File

@@ -2,15 +2,15 @@
#import "MLNStyleLayer.h" #import "MLNStyleLayer.h"
#import "MLNCustomDrawableStyleLayer_Private.h" #import "MLNCustomDrawableStyleLayer_Private.h"
#import "MLNStyle_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNStyle_Private.h"
#include <mbgl/layermanager/custom_drawable_layer_factory.hpp> #include <mbgl/layermanager/custom_drawable_layer_factory.hpp>
#include <mbgl/style/layers/custom_drawable_layer.hpp> #include <mbgl/style/layers/custom_drawable_layer.hpp>
#include <memory>
#include <cmath> #include <cmath>
#include <memory>
#include <mbgl/style/layer.hpp> #include <mbgl/style/layer.hpp>
@@ -20,7 +20,7 @@
@implementation MLNCustomDrawableStyleLayer @implementation MLNCustomDrawableStyleLayer
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { - (instancetype)initWithRawLayer:(mbgl::style::Layer*)rawLayer {
return [super initWithRawLayer:rawLayer]; return [super initWithRawLayer:rawLayer];
} }

View File

@@ -1,17 +1,17 @@
#import "MLNCustomStyleLayer.h" #import "MLNCustomStyleLayer.h"
#import "MLNCustomStyleLayer_Private.h" #import "MLNCustomStyleLayer_Private.h"
#import "MLNMapView_Private.h"
#import "MLNStyle_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNMapView_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNStyle_Private.h"
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL
#import <MetalKit/MetalKit.h> #import <MetalKit/MetalKit.h>
#endif #endif
#include <mbgl/style/layers/custom_layer.hpp>
#include <mbgl/math/wrap.hpp> #include <mbgl/math/wrap.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL
#include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp> #include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp>
@@ -29,9 +29,7 @@ class MLNCustomLayerHost;
- (instancetype)initWithIdentifier:(NSString *)identifier { - (instancetype)initWithIdentifier:(NSString *)identifier {
auto layer = std::make_unique<mbgl::style::CustomLayer>( auto layer = std::make_unique<mbgl::style::CustomLayer>(
identifier.UTF8String, identifier.UTF8String, std::make_unique<MLNCustomLayerHost>(self));
std::make_unique<MLNCustomLayerHost>(self)
);
return self = [super initWithPendingLayer:std::move(layer)]; return self = [super initWithPendingLayer:std::move(layer)];
} }
@@ -91,20 +89,23 @@ public:
} }
void initialize() { void initialize() {
if (layerRef == nil) return; if (layerRef == nil)
else if (layer == nil) layer = layerRef; return;
else if (layer == nil)
layer = layerRef;
if (layer.mapView) { if (layer.mapView) {
[layer didMoveToMapView:layer.mapView]; [layer didMoveToMapView:layer.mapView];
} }
} }
void render(const mbgl::style::CustomLayerRenderParameters& parameters) { void render(const mbgl::style::CustomLayerRenderParameters &parameters) {
if (!layer) return; if (!layer) return;
#if MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_METAL
MTL::RenderCommandEncoder* ptr = MTL::RenderCommandEncoder *ptr =
static_cast<const mbgl::style::mtl::CustomLayerRenderParameters&>(parameters).encoder.get(); static_cast<const mbgl::style::mtl::CustomLayerRenderParameters &>(parameters)
.encoder.get();
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr; id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
layer.renderEncoder = encoder; layer.renderEncoder = encoder;
#endif #endif
@@ -116,16 +117,14 @@ public:
.direction = mbgl::util::wrap(parameters.bearing, 0., 360.), .direction = mbgl::util::wrap(parameters.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(parameters.pitch), .pitch = static_cast<CGFloat>(parameters.pitch),
.fieldOfView = static_cast<CGFloat>(parameters.fieldOfView), .fieldOfView = static_cast<CGFloat>(parameters.fieldOfView),
.projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix) .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)};
};
if (layer.mapView) { if (layer.mapView) {
[layer drawInMapView:layer.mapView withContext:drawingContext]; [layer drawInMapView:layer.mapView withContext:drawingContext];
} }
} }
void contextLost() { void contextLost() {}
}
void deinitialize() { void deinitialize() {
if (layer == nil) return; if (layer == nil) return;
@@ -138,13 +137,13 @@ public:
} }
private: private:
__weak MLNCustomStyleLayer * layerRef; __weak MLNCustomStyleLayer *layerRef;
MLNCustomStyleLayer * layer = nil; MLNCustomStyleLayer *layer = nil;
}; };
namespace mbgl { namespace mbgl {
MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { MLNStyleLayer *CustomStyleLayerPeerFactory::createPeer(style::Layer *rawLayer) {
return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer]; return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer];
} }

View File

@@ -1,29 +1,29 @@
#import "MLNFoundation_Private.h"
#import "MLNFeature_Private.h"
#import "MLNCluster.h" #import "MLNCluster.h"
#import "MLNFeature_Private.h"
#import "MLNFoundation_Private.h"
#import "MLNPointAnnotation.h" #import "MLNPointAnnotation.h"
#import "MLNPolyline.h"
#import "MLNPolygon.h" #import "MLNPolygon.h"
#import "MLNPolyline.h"
#import "MLNValueEvaluator.h" #import "MLNValueEvaluator.h"
#import "MLNShape_Private.h"
#import "MLNPointCollection_Private.h" #import "MLNPointCollection_Private.h"
#import "MLNPolyline_Private.h"
#import "MLNPolygon_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 "MLNLoggingConfiguration_Private.h"
#import "NSArray+MLNAdditions.h"
#import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import <mbgl/util/geometry.hpp>
#import <mbgl/style/conversion/geojson.hpp>
#import <mapbox/feature.hpp> #import <mapbox/feature.hpp>
#import <mbgl/style/conversion/geojson.hpp>
#import <mbgl/util/geometry.hpp>
// Cluster constants // Cluster constants
static NSString * const MLNClusterIdentifierKey = @"cluster_id"; static NSString *const MLNClusterIdentifierKey = @"cluster_id";
static NSString * const MLNClusterCountKey = @"point_count"; static NSString *const MLNClusterCountKey = @"point_count";
const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax; const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax;
@interface MLNEmptyFeature () @interface MLNEmptyFeature ()
@@ -45,7 +45,7 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry(@{@"type": @"Empty"}, self.attributes, self.identifier); return NSDictionaryFeatureForGeometry(@{@"type" : @"Empty"}, self.attributes, self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
@@ -56,11 +56,12 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
return mbgl::EmptyGeometry(); return mbgl::EmptyGeometry();
} }
- (NSString *)description - (NSString *)description {
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>", return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier
? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
} }
@@ -85,18 +86,20 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
self.coordinate.latitude, self.coordinate.longitude, self.coordinate.latitude, self.coordinate.longitude,
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
} }
@@ -106,7 +109,8 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
@implementation MLNPointFeatureCluster @implementation MLNPointFeatureCluster
- (NSUInteger)clusterIdentifier { - (NSUInteger)clusterIdentifier {
NSNumber *clusterNumber = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber); NSNumber *clusterNumber =
MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
MLNAssert(clusterNumber, @"Clusters should have a cluster_id"); MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
if (!clusterNumber) { if (!clusterNumber) {
@@ -127,7 +131,6 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
@end @end
@interface MLNPolylineFeature () @interface MLNPolylineFeature ()
@end @end
@@ -147,18 +150,20 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)[self pointCount], (unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds), MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
@@ -185,18 +190,20 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)[self pointCount], (unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds), MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
@@ -223,7 +230,8 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
@@ -251,18 +259,20 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)self.polylines.count, (unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds), MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
@@ -289,18 +299,20 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)self.polygons.count, (unsigned long)self.polygons.count,
MLNStringFromCoordinateBounds(self.overlayBounds), MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"]; self.attributes.count ? self.attributes : @"none"];
@@ -332,15 +344,17 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier); return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
mbgl::FeatureCollection featureCollection; mbgl::FeatureCollection featureCollection;
featureCollection.reserve(self.shapes.count); featureCollection.reserve(self.shapes.count);
for (MLNShape <MLNFeature> *feature in self.shapes) { for (MLNShape<MLNFeature> *feature in self.shapes) {
auto geoJSONObject = feature.geoJSONObject; auto geoJSONObject = feature.geoJSONObject;
MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(), @"Feature collection must only contain features."); MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(),
@"Feature collection must only contain features.");
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>()); featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
} }
return featureCollection; return featureCollection;
@@ -359,16 +373,14 @@ private:
const bool is_in_feature; const bool is_in_feature;
public: public:
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false): GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false)
shared_properties(properties), : shared_properties(properties), is_in_feature(isInFeature) {}
is_in_feature(isInFeature)
{}
MLNShape * operator()(const mbgl::EmptyGeometry &) const { MLNShape *operator()(const mbgl::EmptyGeometry &) const {
return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init]; return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init];
} }
MLNShape * operator()(const mbgl::Point<T> &geometry) const { MLNShape *operator()(const mbgl::Point<T> &geometry) const {
Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class]; Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class];
// If we're dealing with a cluster, we should change the class type. // If we're dealing with a cluster, we should change the class type.
@@ -391,27 +403,29 @@ public:
return shape; return shape;
} }
MLNShape * operator()(const mbgl::LineString<T> &geometry) const { MLNShape *operator()(const mbgl::LineString<T> &geometry) const {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class]; Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class];
return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
} }
MLNShape * operator()(const mbgl::Polygon<T> &geometry) const { MLNShape *operator()(const mbgl::Polygon<T> &geometry) const {
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature); return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature);
} }
MLNShape * operator()(const mbgl::MultiPoint<T> &geometry) const { MLNShape *operator()(const mbgl::MultiPoint<T> &geometry) const {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
Class shapeClass = is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class]; Class shapeClass =
is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class];
return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()]; return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
} }
MLNShape * operator()(const mbgl::MultiLineString<T> &geometry) const { MLNShape *operator()(const mbgl::MultiLineString<T> &geometry) const {
NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()];
for (auto &lineString : geometry) { for (auto &lineString : geometry) {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString); std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString);
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0]
count:coordinates.size()];
[polylines addObject:polyline]; [polylines addObject:polyline];
} }
@@ -419,7 +433,7 @@ public:
return [shapeClass multiPolylineWithPolylines:polylines]; return [shapeClass multiPolylineWithPolylines:polylines];
} }
MLNShape * operator()(const mbgl::MultiPolygon<T> &geometry) const { MLNShape *operator()(const mbgl::MultiPolygon<T> &geometry) const {
NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()]; NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
for (auto &polygon : geometry) { for (auto &polygon : geometry) {
[polygons addObject:toShape(polygon, false)]; [polygons addObject:toShape(polygon, false)];
@@ -429,14 +443,16 @@ public:
return [shapeClass multiPolygonWithPolygons:polygons]; return [shapeClass multiPolygonWithPolygons:polygons];
} }
MLNShape * operator()(const mapbox::geometry::geometry_collection<T> &collection) const { MLNShape *operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
for (auto &geometry : collection) { 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. // 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<T>::visit(geometry, *this); MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, *this);
[shapes addObject:shape]; [shapes addObject:shape];
} }
Class shapeClass = is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class]; Class shapeClass =
is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class];
return [shapeClass shapeCollectionWithShapes:shapes]; return [shapeClass shapeCollectionWithShapes:shapes];
} }
@@ -445,14 +461,16 @@ private:
return CLLocationCoordinate2DMake(point.y, point.x); return CLLocationCoordinate2DMake(point.y, point.x);
} }
static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(const std::vector<mbgl::Point<T>> &points) { static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(
const std::vector<mbgl::Point<T>> &points) {
std::vector<CLLocationCoordinate2D> coordinates; std::vector<CLLocationCoordinate2D> coordinates;
coordinates.reserve(points.size()); coordinates.reserve(points.size());
std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D); std::transform(points.begin(), points.end(), std::back_inserter(coordinates),
toLocationCoordinate2D);
return coordinates; return coordinates;
} }
template<typename U = MLNPolygon, typename V = MLNPolygonFeature> template <typename U = MLNPolygon, typename V = MLNPolygonFeature>
static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) { static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) {
auto &linearRing = geometry.front(); auto &linearRing = geometry.front();
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing); std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing);
@@ -462,31 +480,34 @@ private:
for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) { for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) {
auto &innerRing = *iter; auto &innerRing = *iter;
std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing); std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing);
MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0] count:innerCoordinates.size()]; MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0]
count:innerCoordinates.size()];
[innerPolygons addObject:innerPolygon]; [innerPolygons addObject:innerPolygon];
} }
} }
Class shapeClass = isInFeature ? [V class] : [U class]; Class shapeClass = isInFeature ? [V class] : [U class];
return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons]; return [shapeClass polygonWithCoordinates:&coordinates[0]
count:coordinates.size()
interiorPolygons:innerPolygons];
} }
}; };
template <typename T> template <typename T>
class GeoJSONEvaluator { class GeoJSONEvaluator {
public: public:
MLNShape * operator()(const mbgl::Geometry<T> &geometry) const { MLNShape *operator()(const mbgl::Geometry<T> &geometry) const {
GeometryEvaluator<T> evaluator; GeometryEvaluator<T> evaluator;
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator); MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
return shape; return shape;
} }
MLNShape <MLNFeature> * operator()(const mbgl::GeoJSONFeature &feature) const { MLNShape<MLNFeature> *operator()(const mbgl::GeoJSONFeature &feature) const {
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)MLNFeatureFromMBGLFeature(feature); MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)MLNFeatureFromMBGLFeature(feature);
return shape; return shape;
} }
MLNShape <MLNFeature> * operator()(const mbgl::FeatureCollection &collection) const { MLNShape<MLNFeature> *operator()(const mbgl::FeatureCollection &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()]; NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
for (const auto &feature : collection) { for (const auto &feature : collection) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)]; [shapes addObject:MLNFeatureFromMBGLFeature(feature)];
@@ -495,7 +516,8 @@ public:
} }
}; };
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) { NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
const std::vector<mbgl::Feature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) { for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))]; [shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
@@ -503,7 +525,8 @@ NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<
return shapes; return shapes;
} }
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features) { NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
const std::vector<mbgl::GeoJSONFeature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) { for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)]; [shapes addObject:MLNFeatureFromMBGLFeature(feature)];
@@ -511,15 +534,17 @@ NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<
return shapes; return shapes;
} }
id <MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) { id<MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()]; NSMutableDictionary *attributes =
[NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
for (auto &pair : feature.properties) { for (auto &pair : feature.properties) {
auto &value = pair.second; auto &value = pair.second;
ValueEvaluator evaluator; ValueEvaluator evaluator;
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
} }
GeometryEvaluator<double> evaluator(&feature.properties, true); GeometryEvaluator<double> evaluator(&feature.properties, true);
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator); MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)mapbox::geometry::geometry<double>::visit(
feature.geometry, evaluator);
if (!feature.id.is<mapbox::feature::null_value_t>()) { if (!feature.id.is<mapbox::feature::null_value_t>()) {
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
} }
@@ -528,14 +553,14 @@ id <MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
return shape; return shape;
} }
MLNShape* MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) { MLNShape *MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
GeoJSONEvaluator<double> evaluator; GeoJSONEvaluator<double> evaluator;
MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator); MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator);
return shape; return shape;
} }
mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary *attributes) mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier,
{ NSDictionary *attributes) {
if (identifier) { if (identifier) {
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier]; NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
feature.id = [identifierExpression mgl_featureIdentifier]; feature.id = [identifierExpression mgl_featureIdentifier];
@@ -544,10 +569,11 @@ mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NS
return feature; return feature;
} }
NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier) { NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry,
NSMutableDictionary *feature = [@{@"type": @"Feature", NSDictionary *attributes,
@"properties": attributes, id identifier) {
@"geometry": geometry} mutableCopy]; NSMutableDictionary *feature =
[@{@"type" : @"Feature", @"properties" : attributes, @"geometry" : geometry} mutableCopy];
feature[@"id"] = identifier; feature[@"id"] = identifier;
return [feature copy]; return [feature copy];
} }

View File

@@ -11,14 +11,13 @@
- (NSString *)description { - (NSString *)description {
if (self.rawLayer) { if (self.rawLayer) {
return [NSString stringWithFormat: return [NSString
@"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>", stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, NSStringFromClass([self class]), (void *)self, self.identifier,
self.sourceIdentifier, self.visible ? @"YES" : @"NO"]; self.sourceIdentifier, self.visible ? @"YES" : @"NO"];
} } else {
else { return [NSString
return [NSString stringWithFormat: stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; visible = NO>",
@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; visible = NO>",
NSStringFromClass([self class]), (void *)self, self.identifier]; NSStringFromClass([self class]), (void *)self, self.identifier];
} }
} }

View File

@@ -1,6 +1,4 @@
#import "MLNFoundation_Private.h" #import "MLNFoundation_Private.h"
/// Initializes the run loop shim that lives on the main thread. /// Initializes the run loop shim that lives on the main thread.
void MLNInitializeRunLoop() { void MLNInitializeRunLoop() { static mbgl::util::RunLoop runLoop; }
static mbgl::util::RunLoop runLoop;
}

View File

@@ -45,58 +45,75 @@ mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate
} }
} }
CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) { CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch,
CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel); CLLocationDegrees latitude, CGSize size) {
CLLocationDistance metersPerPixel =
mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
CLLocationDistance metersTall = metersPerPixel * size.height; CLLocationDistance metersTall = metersPerPixel * size.height;
CLLocationDistance altitude = metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); CLLocationDistance altitude =
metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_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) { double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch,
CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2); CLLocationDegrees latitude, CGSize size) {
CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); 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; CLLocationDistance metersPerPixel = metersTall / size.height;
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel; CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI *
mbgl::util::EARTH_RADIUS_M / metersPerPixel;
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D); return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
} }
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) { MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from,
double a = pow(sin((to.latitude - from.latitude) / 2), 2) MLNRadianCoordinate2D to) {
+ pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude); 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) { MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from,
MLNRadianCoordinate2D to) {
double a = sin(to.longitude - from.longitude) * cos(to.latitude); double a = sin(to.longitude - from.longitude) * cos(to.latitude);
double b = cos(from.latitude) * sin(to.latitude) double b = cos(from.latitude) * sin(to.latitude) -
- sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude); sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
return atan2(a, b); return atan2(a, b);
} }
MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate, MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate,
MLNRadianDistance distance, MLNRadianDistance distance,
MLNRadianDirection direction) { MLNRadianDirection direction) {
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) +
+ cos(coordinate.latitude) * sin(distance) * cos(direction)); cos(coordinate.latitude) * sin(distance) * cos(direction));
double otherLongitude = coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude), double otherLongitude =
coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude)); cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude); return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude);
} }
CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, CLLocationCoordinate2D secondCoordinate) { CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate,
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) && MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) { CLLocationCoordinate2D secondCoordinate) {
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) &&
MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
return 0; 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 CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) *
MLNRadianCoordinate2D firstRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(firstCoordinate); cos(secondRadianCoordinate.latitude);
MLNRadianCoordinate2D secondRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(secondCoordinate); CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude) -
sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) *
CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) * cos(secondRadianCoordinate.latitude); cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude)
- sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
MLNRadianDirection radianDirection = atan2(a, b); MLNRadianDirection radianDirection = atan2(a, b);
return radianDirection * 180 / M_PI; return radianDirection * 180 / M_PI;
} }
@@ -107,25 +124,36 @@ CGPoint MLNPointRounded(CGPoint point) {
#elif TARGET_OS_MAC #elif TARGET_OS_MAC
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor; CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
#endif #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) { MLNMapPoint MLNMapPointForCoordinate(CLLocationCoordinate2D coordinate, double zoomLevel) {
mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel)); mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(
MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel));
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel); return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel);
} }
MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) { MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) {
MLNMatrix4 mat4 = { MLNMatrix4 mat4 = {.m00 = array[0],
.m00 = array[0], .m01 = array[1], .m02 = array[2], .m03 = array[3], .m01 = array[1],
.m10 = array[4], .m11 = array[5], .m12 = array[6], .m13 = array[7], .m02 = array[2],
.m20 = array[8], .m21 = array[9], .m22 = array[10], .m23 = array[11], .m03 = array[3],
.m30 = array[12], .m31 = array[13], .m32 = array[14], .m33 = array[15] .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; return mat4;
} }
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) {
{
return MAX(left, right) - MIN(left, right) <= accuracy; return MAX(left, right) - MIN(left, right) <= accuracy;
} }

View File

@@ -6,16 +6,17 @@
#import "MLNTileSource_Private.h" #import "MLNTileSource_Private.h"
#import "NSURL+MLNAdditions.h" #import "NSURL+MLNAdditions.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h" #import "UIImage+MLNAdditions.h"
#else #else
#import "NSImage+MLNAdditions.h" #import "NSImage+MLNAdditions.h"
#endif #endif
#include <mbgl/style/sources/image_source.hpp> #include <mbgl/style/sources/image_source.hpp>
#include <mbgl/util/premultiply.hpp> #include <mbgl/util/premultiply.hpp>
@interface MLNImageSource () @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; @property (nonatomic, readonly) mbgl::style::ImageSource *rawSource;
@@ -23,23 +24,25 @@
@implementation MLNImageSource @implementation MLNImageSource
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad { - (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad {
const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad); const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad);
auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray); auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray);
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad URL:(NSURL *)url { coordinateQuad:(MLNCoordinateQuad)coordinateQuad
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; URL:(NSURL *)url {
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
self.URL = url; self.URL = url;
return self; return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad image:(MLNImage *)image { coordinateQuad:(MLNCoordinateQuad)coordinateQuad
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; image:(MLNImage *)image {
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
self.image = image; self.image = image;
return self; return self;
@@ -66,7 +69,7 @@
if (image != nullptr) { if (image != nullptr) {
self.rawSource->setImage(image.mgl_premultipliedImage); self.rawSource->setImage(image.mgl_premultipliedImage);
} else { } else {
self.rawSource->setImage(mbgl::PremultipliedImage({0,0})); self.rawSource->setImage(mbgl::PremultipliedImage({0, 0}));
} }
_image = image; _image = image;
} }
@@ -76,21 +79,21 @@
return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates()); return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates());
} }
- (void)setCoordinates: (MLNCoordinateQuad)coordinateQuad { - (void)setCoordinates:(MLNCoordinateQuad)coordinateQuad {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad)); self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad));
} }
- (NSString *)description { - (NSString *)description {
if (self.rawSource) { if (self.rawSource) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>", return [NSString
stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, NSStringFromClass([self class]), (void *)self, self.identifier,
MLNStringFromCoordinateQuad(self.coordinates), MLNStringFromCoordinateQuad(self.coordinates), self.URL, self.image];
self.URL, } else {
self.image]; return [NSString
} stringWithFormat:
else { @"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.image]; NSStringFromClass([self class]), (void *)self, self.identifier, self.image];
} }
} }
@@ -101,7 +104,8 @@
- (NSString *)attributionHTMLString { - (NSString *)attributionHTMLString {
if (!self.rawSource) { if (!self.rawSource) {
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
self.identifier);
return nil; return nil;
} }

View File

@@ -1,5 +1,5 @@
#include <mbgl/util/logging.hpp>
#include <mbgl/util/enum.hpp> #include <mbgl/util/enum.hpp>
#include <mbgl/util/logging.hpp>
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
@@ -10,12 +10,14 @@
namespace mbgl { namespace mbgl {
class MLNCoreLoggingObserver : public Log :: Observer { class MLNCoreLoggingObserver : public Log ::Observer {
public: public:
//Return true not print messages at core level, and filter at platform level. // 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{ bool onRecord(EventSeverity severity, Event event, int64_t code,
const std::string &msg) override {
NSString *message = [NSString stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code, [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]]; NSString *message = [NSString
stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code,
[NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
switch (severity) { switch (severity) {
case EventSeverity::Debug: case EventSeverity::Debug:
MLNLogDebug(message); MLNLogDebug(message);
@@ -35,10 +37,9 @@ public:
return true; return true;
} }
}; };
} } // namespace mbgl
@implementation MLNLoggingConfiguration @implementation MLNLoggingConfiguration {
{
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver; std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
} }
@@ -52,15 +53,14 @@ public:
return sharedConfiguration; return sharedConfiguration;
} }
- (id)init{ - (id)init {
if(self = [super init]){ if (self = [super init]) {
mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>()); mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>());
} }
return self; return self;
} }
- (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler { - (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler {
if (!handler) { if (!handler) {
_handler = [self defaultBlockHandler]; _handler = [self defaultBlockHandler];
} else { } else {
@@ -68,19 +68,21 @@ public:
} }
} }
- (void)logCallingFunction:(const char *)callingFunction functionLine:(NSUInteger)functionLine messageType:(MLNLoggingLevel)type format:(id)messageFormat, ... { - (void)logCallingFunction:(const char *)callingFunction
functionLine:(NSUInteger)functionLine
messageType:(MLNLoggingLevel)type
format:(id)messageFormat, ... {
va_list formatList; va_list formatList;
va_start(formatList, messageFormat); va_start(formatList, messageFormat);
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList]; NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
va_end(formatList); va_end(formatList);
_handler(type, @(callingFunction), functionLine, formattedMessage); _handler(type, @(callingFunction), functionLine, formattedMessage);
} }
- (MLNLoggingBlockHandler)defaultBlockHandler { - (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, *)) { if (@available(iOS 10.0, macOS 10.12.0, *)) {
static dispatch_once_t once; static dispatch_once_t once;
static os_log_t info_log; static os_log_t info_log;
@@ -89,15 +91,13 @@ public:
#endif #endif
static os_log_t error_log; static os_log_t error_log;
static os_log_t fault_log; static os_log_t fault_log;
static os_log_type_t log_types[] = { OS_LOG_TYPE_DEFAULT, static os_log_type_t log_types[] = {OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_INFO,
OS_LOG_TYPE_INFO,
#if MLN_LOGGING_ENABLE_DEBUG #if MLN_LOGGING_ENABLE_DEBUG
OS_LOG_TYPE_DEBUG, OS_LOG_TYPE_DEBUG,
#endif #endif
OS_LOG_TYPE_ERROR, OS_LOG_TYPE_ERROR, OS_LOG_TYPE_FAULT};
OS_LOG_TYPE_FAULT }; constexpr const char *const subsystem = "org.maplibre.Native";
constexpr const char* const subsystem = "org.maplibre.Native"; dispatch_once(&once, ^{
dispatch_once(&once, ^ {
info_log = os_log_create(subsystem, "INFO"); info_log = os_log_create(subsystem, "INFO");
#if MLN_LOGGING_ENABLE_DEBUG #if MLN_LOGGING_ENABLE_DEBUG
debug_log = os_log_create(subsystem, "DEBUG"); debug_log = os_log_create(subsystem, "DEBUG");
@@ -129,7 +129,8 @@ public:
} }
os_log_type_t logType = log_types[level]; os_log_type_t logType = log_types[level];
os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line, message); os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line,
message);
} else { } else {
NSString *category; NSString *category;
switch (level) { switch (level) {

View File

@@ -8,23 +8,21 @@
@implementation MLNMapCamera @implementation MLNMapCamera
+ (BOOL)supportsSecureCoding + (BOOL)supportsSecureCoding {
{
return YES; return YES;
} }
+ (instancetype)camera + (instancetype)camera {
{
return [[self alloc] init]; return [[self alloc] init];
} }
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
eyeAltitude:(CLLocationDistance)eyeAltitude eyeAltitude:(CLLocationDistance)eyeAltitude {
{
CLLocationDirection heading = -1; CLLocationDirection heading = -1;
CGFloat pitch = -1; CGFloat pitch = -1;
if (CLLocationCoordinate2DIsValid(centerCoordinate) && CLLocationCoordinate2DIsValid(eyeCoordinate)) { if (CLLocationCoordinate2DIsValid(centerCoordinate) &&
CLLocationCoordinate2DIsValid(eyeCoordinate)) {
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate); heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
@@ -45,8 +43,7 @@
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
acrossDistance:(CLLocationDistance)distance acrossDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading heading:(CLLocationDirection)heading {
{
// Angle at the viewpoint formed by the straight lines running perpendicular // Angle at the viewpoint formed by the straight lines running perpendicular
// to the ground and toward the center coordinate. // to the ground and toward the center coordinate.
CLLocationDirection eyeAngle = 90 - pitch; CLLocationDirection eyeAngle = 90 - pitch;
@@ -61,8 +58,7 @@
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
altitude:(CLLocationDistance)altitude altitude:(CLLocationDistance)altitude
pitch:(CGFloat)pitch pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading heading:(CLLocationDirection)heading {
{
return [[self alloc] initWithCenterCoordinate:centerCoordinate return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude altitude:altitude
pitch:pitch pitch:pitch
@@ -72,9 +68,9 @@
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromDistance:(CLLocationDistance)distance fromDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading heading:(CLLocationDirection)heading {
{ // TODO: Remove this compatibility shim in favor of
// TODO: Remove this compatibility shim in favor of `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:. // `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:.
// https://github.com/mapbox/mapbox-gl-native/issues/12943 // https://github.com/mapbox/mapbox-gl-native/issues/12943
return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
altitude:distance altitude:distance
@@ -85,11 +81,10 @@
- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate - (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
altitude:(CLLocationDistance)altitude altitude:(CLLocationDistance)altitude
pitch:(CGFloat)pitch pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading heading:(CLLocationDirection)heading {
{ MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°",
MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°", MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading); MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading);
if (self = [super init]) if (self = [super init]) {
{
_centerCoordinate = centerCoordinate; _centerCoordinate = centerCoordinate;
_altitude = altitude; _altitude = altitude;
_pitch = pitch; _pitch = pitch;
@@ -99,11 +94,9 @@
return self; return self;
} }
- (nullable instancetype)initWithCoder:(NSCoder *)coder - (nullable instancetype)initWithCoder:(NSCoder *)coder {
{
MLNLogInfo(@"Initialiazing with coder."); MLNLogInfo(@"Initialiazing with coder.");
if (self = [super init]) if (self = [super init]) {
{
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"], _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
[coder decodeDoubleForKey:@"centerLongitude"]); [coder decodeDoubleForKey:@"centerLongitude"]);
_altitude = [coder decodeDoubleForKey:@"altitude"]; _altitude = [coder decodeDoubleForKey:@"altitude"];
@@ -114,8 +107,7 @@
return self; return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"]; [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"]; [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
[coder encodeDouble:_altitude forKey:@"altitude"]; [coder encodeDouble:_altitude forKey:@"altitude"];
@@ -124,8 +116,7 @@
[coder encodeDouble:_roll forKey:@"roll"]; [coder encodeDouble:_roll forKey:@"roll"];
} }
- (id)copyWithZone:(nullable NSZone *)zone - (id)copyWithZone:(nullable NSZone *)zone {
{
MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
altitude:_altitude altitude:_altitude
pitch:_pitch pitch:_pitch
@@ -149,50 +140,47 @@
self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle)); self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
} }
- (NSString *)description - (NSString *)description {
{ return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; "
return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; heading = %.0f°; pitch = %.0f°; roll = %.0f°>", @"heading = %.0f°; pitch = %.0f°; roll = %.0f°>",
NSStringFromClass([self class]), (void *)self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch, _roll]; NSStringFromClass([self class]), (void *)self,
_centerCoordinate.latitude, _centerCoordinate.longitude,
_altitude, _heading, _pitch, _roll];
} }
- (BOOL)isEqual:(id)other - (BOOL)isEqual:(id)other {
{ if (![other isKindOfClass:[self class]]) {
if ( ! [other isKindOfClass:[self class]])
{
return NO; return NO;
} }
if (other == self) if (other == self) {
{
return YES; return YES;
} }
MLNMapCamera *otherCamera = other; MLNMapCamera *otherCamera = other;
return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude &&
&& _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude &&
&& _altitude == otherCamera.altitude _altitude == otherCamera.altitude && _pitch == otherCamera.pitch &&
&& _pitch == otherCamera.pitch && _heading == otherCamera.heading _heading == otherCamera.heading && _roll == otherCamera.roll);
&& _roll == otherCamera.roll);
} }
- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera - (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera {
{ if (otherCamera == self) {
if (otherCamera == self)
{
return YES; return YES;
} }
return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-8) return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude,
&& MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-8) otherCamera.centerCoordinate.latitude, 1e-8) &&
&& MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) MLNEqualFloatWithAccuracy(_centerCoordinate.longitude,
&& MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) otherCamera.centerCoordinate.longitude, 1e-8) &&
&& MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) &&
&& MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2)); MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) &&
MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) &&
MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2));
} }
- (NSUInteger)hash - (NSUInteger)hash {
{ return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash +
return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
+ @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
} }
@end @end

View File

@@ -6,11 +6,9 @@
@implementation MLNMapOptions @implementation MLNMapOptions
-(instancetype _Nonnull)init - (instancetype _Nonnull)init {
{
self = [super init]; self = [super init];
if (self) if (self) {
{
_styleURL = nil; _styleURL = nil;
_styleJSON = nil; _styleJSON = nil;
_pluginLayers = nil; _pluginLayers = nil;

View File

@@ -2,80 +2,100 @@
#import <mbgl/actor/actor.hpp> #import <mbgl/actor/actor.hpp>
#import <mbgl/actor/scheduler.hpp> #import <mbgl/actor/scheduler.hpp>
#import <mbgl/util/geo.hpp> #import <mbgl/map/camera.hpp>
#import <mbgl/map/map_options.hpp> #import <mbgl/map/map_options.hpp>
#import <mbgl/map/map_snapshotter.hpp> #import <mbgl/map/map_snapshotter.hpp>
#import <mbgl/map/camera.hpp>
#import <mbgl/storage/resource_options.hpp> #import <mbgl/storage/resource_options.hpp>
#import <mbgl/util/client_options.hpp> #import <mbgl/util/client_options.hpp>
#import <mbgl/util/geo.hpp>
#import <mbgl/util/string.hpp> #import <mbgl/util/string.hpp>
#import "MLNOfflineStorage_Private.h" #import "MLNAnnotationImage_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNStyle_Private.h"
#import "MLNAttributionInfo_Private.h" #import "MLNAttributionInfo_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNRendererConfiguration.h"
#import "MLNMapSnapshotter_Private.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 "MLNSettings_Private.h"
#import "MLNShape.h" #import "MLNShape.h"
#import "MLNPolyline.h"
#import "MLNPolygon.h"
#import "MLNMultiPoint_Private.h"
#import "MLNShapeCollection.h" #import "MLNShapeCollection.h"
#import "MLNPointCollection.h" #import "MLNStyle_Private.h"
#import "MLNAnnotationImage_Private.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h"
#import "UIColor+MLNAdditions.h" #import "UIColor+MLNAdditions.h"
#import "UIImage+MLNAdditions.h"
#else #else
#import "NSImage+MLNAdditions.h"
#import "NSColor+MLNAdditions.h"
#import <CoreGraphics/CoreGraphics.h> #import <CoreGraphics/CoreGraphics.h>
#import <CoreImage/CIContext.h> #import <CoreImage/CIContext.h>
#import <CoreImage/CIFilter.h> #import <CoreImage/CIFilter.h>
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import "NSColor+MLNAdditions.h"
#import "NSImage+MLNAdditions.h"
#endif #endif
#import "NSBundle+MLNAdditions.h" #import "NSBundle+MLNAdditions.h"
const CGPoint MLNLogoImagePosition = CGPointMake(8, 8); const CGPoint MLNLogoImagePosition = CGPointMake(8, 8);
const CGFloat MLNSnapshotterMinimumPixelSize = 64; 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); MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage,
MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError); mbgl::MapSnapshotter::PointForFn pointForFn,
NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnapshotter::Attributions attributions); 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<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(
mbgl::MapSnapshotter::Attributions attributions);
class MLNMapSnapshotterDelegateHost: public mbgl::MapSnapshotterObserver { class MLNMapSnapshotterDelegateHost : public mbgl::MapSnapshotterObserver {
public: public:
MLNMapSnapshotterDelegateHost(MLNMapSnapshotter *snapshotter_) : snapshotter(snapshotter_) {} MLNMapSnapshotterDelegateHost(MLNMapSnapshotter *snapshotter_) : snapshotter(snapshotter_) {}
void onDidFailLoadingStyle(const std::string& errorMessage) { void onDidFailLoadingStyle(const std::string &errorMessage) {
MLNMapSnapshotter *strongSnapshotter = snapshotter; MLNMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotterDidFail:withError:)]) { if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotterDidFail:
withError:)]) {
NSString *description = @(errorMessage.c_str()); NSString *description = @(errorMessage.c_str());
NSDictionary *userInfo = @{ 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"), NSLocalizedDescriptionKey : NSLocalizedStringWithDefaultValue(
NSLocalizedFailureReasonErrorKey: description, @"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]; NSError *error = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeLoadStyleFailed
userInfo:userInfo];
[strongSnapshotter.delegate mapSnapshotterDidFail:snapshotter withError:error]; [strongSnapshotter.delegate mapSnapshotterDidFail:snapshotter withError:error];
} }
} }
void onDidFinishLoadingStyle() { void onDidFinishLoadingStyle() {
MLNMapSnapshotter *strongSnapshotter = snapshotter; MLNMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFinishLoadingStyle:)]) { if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:
[strongSnapshotter.delegate mapSnapshotter:snapshotter didFinishLoadingStyle:snapshotter.style]; didFinishLoadingStyle:)]) {
[strongSnapshotter.delegate mapSnapshotter:snapshotter
didFinishLoadingStyle:snapshotter.style];
} }
} }
void onStyleImageMissing(const std::string& imageName) { void onStyleImageMissing(const std::string &imageName) {
MLNMapSnapshotter *strongSnapshotter = snapshotter; MLNMapSnapshotter *strongSnapshotter = snapshotter;
if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:didFailLoadingImageNamed:)]) { if ([strongSnapshotter.delegate respondsToSelector:@selector(mapSnapshotter:
[strongSnapshotter.delegate mapSnapshotter:snapshotter didFailLoadingImageNamed:@(imageName.c_str())]; didFailLoadingImageNamed:)]) {
[strongSnapshotter.delegate mapSnapshotter:snapshotter
didFailLoadingImageNamed:@(imageName.c_str())];
} }
} }
@@ -83,9 +103,12 @@ private:
__weak MLNMapSnapshotter *snapshotter; __weak MLNMapSnapshotter *snapshotter;
}; };
@interface MLNMapSnapshotOverlay() <MLNMapSnapshotProtocol> @interface MLNMapSnapshotOverlay () <MLNMapSnapshotProtocol>
@property (nonatomic, assign) CGFloat scale; @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 @end
@@ -94,7 +117,10 @@ private:
mbgl::MapSnapshotter::LatLngForFn _latLngForFn; mbgl::MapSnapshotter::LatLngForFn _latLngForFn;
} }
- (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 {
self = [super init]; self = [super init];
if (self) { if (self) {
_context = context; _context = context;
@@ -108,30 +134,26 @@ private:
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
{
mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate));
return CGPointMake(sc.x, sc.y); return CGPointMake(sc.x, sc.y);
} }
- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point - (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point {
{
mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y));
return MLNLocationCoordinate2DFromLatLng(latLng); return MLNLocationCoordinate2DFromLatLng(latLng);
} }
#else #else
- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate - (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
{
mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate));
CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale; CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context)) / self.scale;
return NSMakePoint(sc.x, height - sc.y); return NSMakePoint(sc.x, height - sc.y);
} }
- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point - (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point {
{ CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context)) / self.scale;
CGFloat height = ((CGFloat)CGBitmapContextGetHeight(self.context))/self.scale;
auto screenCoord = mbgl::ScreenCoordinate(point.x, height - point.y); auto screenCoord = mbgl::ScreenCoordinate(point.x, height - point.y);
mbgl::LatLng latLng = _latLngForFn(screenCoord); mbgl::LatLng latLng = _latLngForFn(screenCoord);
return MLNLocationCoordinate2DFromLatLng(latLng); return MLNLocationCoordinate2DFromLatLng(latLng);
@@ -142,14 +164,14 @@ private:
@implementation MLNMapSnapshotOptions @implementation MLNMapSnapshotOptions
- (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL camera:(MLNMapCamera *)camera size:(CGSize)size - (instancetype _Nonnull)initWithStyleURL:(nullable NSURL *)styleURL
{ camera:(MLNMapCamera *)camera
MLNLogDebug(@"Initializing withStyleURL: %@ camera: %@ size: %@", styleURL, camera, MLNStringFromSize(size)); size:(CGSize)size {
MLNLogDebug(@"Initializing withStyleURL: %@ camera: %@ size: %@", styleURL, camera,
MLNStringFromSize(size));
self = [super init]; self = [super init];
if (self) if (self) {
{ if (!styleURL) {
if ( !styleURL)
{
styleURL = [MLNStyle defaultStyleURL]; styleURL = [MLNStyle defaultStyleURL];
} }
_styleURL = styleURL; _styleURL = styleURL;
@@ -167,7 +189,9 @@ private:
} }
- (nonnull id)copyWithZone:(nullable NSZone *)zone { - (nonnull id)copyWithZone:(nullable NSZone *)zone {
__typeof__(self) copy = [[[self class] alloc] initWithStyleURL:_styleURL camera:_camera size:_size]; __typeof__(self) copy = [[[self class] alloc] initWithStyleURL:_styleURL
camera:_camera
size:_size];
copy.zoomLevel = _zoomLevel; copy.zoomLevel = _zoomLevel;
copy.coordinateBounds = _coordinateBounds; copy.coordinateBounds = _coordinateBounds;
copy.scale = _scale; copy.scale = _scale;
@@ -178,8 +202,11 @@ private:
@end @end
@interface MLNMapSnapshot() <MLNMapSnapshotProtocol> @interface MLNMapSnapshot () <MLNMapSnapshotProtocol>
- (instancetype)initWithImage:(nullable MLNImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn; - (instancetype)initWithImage:(nullable MLNImage *)image
scale:(CGFloat)scale
pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn
latLngForFn:(mbgl::MapSnapshotter::LatLngForFn)latLngForFn;
@property (nonatomic) CGFloat scale; @property (nonatomic) CGFloat scale;
@end @end
@@ -189,8 +216,10 @@ private:
mbgl::MapSnapshotter::LatLngForFn _latLngForFn; mbgl::MapSnapshotter::LatLngForFn _latLngForFn;
} }
- (instancetype)initWithImage:(nullable MLNImage *)image scale:(CGFloat)scale pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn latLngForFn:(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]; self = [super init];
if (self) { if (self) {
_pointForFn = pointForFn; _pointForFn = pointForFn;
@@ -203,28 +232,24 @@ private:
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
- (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate - (CGPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
{
mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate));
return CGPointMake(sc.x, sc.y); return CGPointMake(sc.x, sc.y);
} }
- (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point - (CLLocationCoordinate2D)coordinateForPoint:(CGPoint)point {
{
mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y)); mbgl::LatLng latLng = _latLngForFn(mbgl::ScreenCoordinate(point.x, point.y));
return MLNLocationCoordinate2DFromLatLng(latLng); return MLNLocationCoordinate2DFromLatLng(latLng);
} }
#else #else
- (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate - (NSPoint)pointForCoordinate:(CLLocationCoordinate2D)coordinate {
{
mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate)); mbgl::ScreenCoordinate sc = _pointForFn(MLNLatLngFromLocationCoordinate2D(coordinate));
return NSMakePoint(sc.x, self.image.size.height - sc.y); return NSMakePoint(sc.x, self.image.size.height - sc.y);
} }
- (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point - (CLLocationCoordinate2D)coordinateForPoint:(NSPoint)point {
{
auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y); auto screenCoord = mbgl::ScreenCoordinate(point.x, self.image.size.height - point.y);
mbgl::LatLng latLng = _latLngForFn(screenCoord); mbgl::LatLng latLng = _latLngForFn(screenCoord);
return MLNLocationCoordinate2DFromLatLng(latLng); return MLNLocationCoordinate2DFromLatLng(latLng);
@@ -234,7 +259,7 @@ private:
@end @end
@interface MLNMapSnapshotter() <MLNMultiPointDelegate> @interface MLNMapSnapshotter () <MLNMultiPointDelegate>
@property (nonatomic) BOOL loading; @property (nonatomic) BOOL loading;
@property (nonatomic) BOOL terminated; @property (nonatomic) BOOL terminated;
@end @end
@@ -257,33 +282,36 @@ private:
return nil; return nil;
} }
- (instancetype)initWithOptions:(MLNMapSnapshotOptions *)options - (instancetype)initWithOptions:(MLNMapSnapshotOptions *)options {
{
MLNLogDebug(@"Initializing withOptions: %@", options); MLNLogDebug(@"Initializing withOptions: %@", options);
self = [super init]; self = [super init];
if (self) { if (self) {
self.options = options; self.options = options;
_installedImages = [NSMutableSet set]; _installedImages = [NSMutableSet set];
#if TARGET_OS_IPHONE #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 #else
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:NSApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
#endif #endif
} }
return self; return self;
} }
- (void)applicationWillTerminate:(NSNotification *)notification - (void)applicationWillTerminate:(NSNotification *)notification {
{
[self cancel]; [self cancel];
self.terminated = YES; self.terminated = YES;
} }
- (void)addAnnotation:(id<MLNAnnotation>)annotation - (void)addAnnotation:(id<MLNAnnotation>)annotation {
{
MLNLogDebug(@"Adding annotation: %@", annotation); MLNLogDebug(@"Adding annotation: %@", annotation);
if ( ! annotation) return; if (!annotation) return;
return [self addAnnotations:@[annotation]]; return [self addAnnotations:@[ annotation ]];
} }
- (void)addAnnotations:(NSArray<id<MLNAnnotation>> *)annotations { - (void)addAnnotations:(NSArray<id<MLNAnnotation>> *)annotations {
@@ -296,26 +324,24 @@ private:
[_annotationsToInstall addObjectsFromArray:annotations]; [_annotationsToInstall addObjectsFromArray:annotations];
} }
- (void)installAnnotations:(NSArray<id<MLNAnnotation>> *)annotations - (void)installAnnotations:(NSArray<id<MLNAnnotation>> *)annotations {
{
MLNLogDebug(@"install: %lu annotations", annotations.count); MLNLogDebug(@"install: %lu annotations", annotations.count);
if ( ! annotations) return; if (!annotations) return;
for (id <MLNAnnotation> annotation in annotations) for (id<MLNAnnotation> annotation in annotations) {
{ MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)],
MLNAssert([annotation conformsToProtocol:@protocol(MLNAnnotation)], @"annotation should conform to MLNAnnotation"); @"annotation should conform to MLNAnnotation");
if ([annotation isKindOfClass:[MLNMultiPoint class]]) if ([annotation isKindOfClass:[MLNMultiPoint class]]) {
{
MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation; MLNMultiPoint *multiPoint = (MLNMultiPoint *)annotation;
if (!multiPoint.pointCount) { if (!multiPoint.pointCount) {
continue; continue;
} }
_mbglMapSnapshotter->addAnnotation([multiPoint annotationObjectWithDelegate:self]); _mbglMapSnapshotter->addAnnotation([multiPoint annotationObjectWithDelegate:self]);
} else if (! [annotation isKindOfClass:[MLNMultiPolyline class]] } else if (![annotation isKindOfClass:[MLNMultiPolyline class]] &&
&& ![annotation isKindOfClass:[MLNMultiPolygon class]] ![annotation isKindOfClass:[MLNMultiPolygon class]] &&
&& ![annotation isKindOfClass:[MLNShapeCollection class]] ![annotation isKindOfClass:[MLNShapeCollection class]] &&
&& ![annotation isKindOfClass:[MLNPointCollection class]]) { ![annotation isKindOfClass:[MLNPointCollection class]]) {
NSString *symbolName; NSString *symbolName;
NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation]; NSValue *annotationValue = [NSValue valueWithNonretainedObject:annotation];
MLNAnnotationImage *annotationImage; MLNAnnotationImage *annotationImage;
@@ -329,18 +355,16 @@ private:
} }
symbolName = annotationImage.styleIconIdentifier; symbolName = annotationImage.styleIconIdentifier;
if ( ! symbolName) if (!symbolName) {
{ symbolName = [MLNSnapshotterAnnotationSpritePrefix
symbolName = [MLNSnapshotterAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; stringByAppendingString:annotationImage.reuseIdentifier];
annotationImage.styleIconIdentifier = symbolName; annotationImage.styleIconIdentifier = symbolName;
} }
[self installAnnotationImage:annotationImage]; [self installAnnotationImage:annotationImage];
_mbglMapSnapshotter->addAnnotation(mbgl::SymbolAnnotation { _mbglMapSnapshotter->addAnnotation(mbgl::SymbolAnnotation{
MLNPointFromLocationCoordinate2D(annotation.coordinate), MLNPointFromLocationCoordinate2D(annotation.coordinate), symbolName.UTF8String});
symbolName.UTF8String
});
} }
} }
} }
@@ -348,37 +372,45 @@ private:
- (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage { - (void)installAnnotationImage:(MLNAnnotationImage *)annotationImage {
NSString *iconIdentifier = annotationImage.styleIconIdentifier; NSString *iconIdentifier = annotationImage.styleIconIdentifier;
if (![_installedImages containsObject:iconIdentifier]) { if (![_installedImages containsObject:iconIdentifier]) {
_mbglMapSnapshotter->addAnnotationImage([annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]); _mbglMapSnapshotter->addAnnotationImage(
[annotationImage.image mgl_styleImageWithIdentifier:iconIdentifier]);
} }
} }
- (void)startWithCompletionHandler:(MLNMapSnapshotCompletionHandler)completion - (void)startWithCompletionHandler:(MLNMapSnapshotCompletionHandler)completion {
{
MLNLogDebug(@"Starting withCompletionHandler: %@", completion); MLNLogDebug(@"Starting withCompletionHandler: %@", completion);
[self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; [self startWithQueue:dispatch_get_main_queue() completionHandler:completion];
} }
- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MLNMapSnapshotCompletionHandler)completionHandler { - (void)startWithQueue:(dispatch_queue_t)queue
completionHandler:(MLNMapSnapshotCompletionHandler)completionHandler {
[self startWithQueue:queue overlayHandler:nil completionHandler:completionHandler]; [self startWithQueue:queue overlayHandler:nil completionHandler:completionHandler];
} }
- (void)startWithOverlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler completionHandler:(MLNMapSnapshotCompletionHandler)completion { - (void)startWithOverlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler
[self startWithQueue:dispatch_get_main_queue() overlayHandler:overlayHandler completionHandler:completion]; 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 - (void)startWithQueue:(dispatch_queue_t)queue
{ overlayHandler:(MLNMapSnapshotOverlayHandler)overlayHandler
completionHandler:(MLNMapSnapshotCompletionHandler)completion {
if (!completion) { if (!completion) {
return; return;
} }
// Ensure that offline storage has been initialized on the main thread, as MLNMapView and MLNOfflineStorage do when used directly. // 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 // https://github.com/mapbox/mapbox-gl-native-ios/issues/227
if ([NSThread.currentThread isMainThread]) { if ([NSThread.currentThread isMainThread]) {
(void)[MLNOfflineStorage sharedOfflineStorage]; (void)[MLNOfflineStorage sharedOfflineStorage];
} else { } else {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"-[MLNMapSnapshotter startWithQueue:completionHandler:] must be called from the main thread, not %@.", NSThread.currentThread]; format:@"-[MLNMapSnapshotter startWithQueue:completionHandler:] must be called "
@"from the main thread, not %@.",
NSThread.currentThread];
} }
if (self.loading) { if (self.loading) {
@@ -397,15 +429,24 @@ private:
[self configureWithOptions:options]; [self configureWithOptions:options];
MLNLogDebug(@"Starting with options: %@", self.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. // Temporarily capture the snapshotter until the completion handler finishes executing, to keep
// POSTCONDITION: Only refer to this variable in the final result queue. // standalone local usage of the snapshotter from becoming a no-op. POSTCONDITION: Only refer to
// 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. // 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; __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) { _mbglMapSnapshotter->snapshot(^(std::exception_ptr mbglError, mbgl::PremultipliedImage image,
mbgl::MapSnapshotter::Attributions attributions,
mbgl::MapSnapshotter::PointForFn pointForFn,
mbgl::MapSnapshotter::LatLngForFn latLngForFn) {
if (mbglError) { if (mbglError) {
NSString *description = @(mbgl::util::toString(mbglError).c_str()); NSString *description = @(mbgl::util::toString(mbglError).c_str());
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; NSDictionary *userInfo = @{NSLocalizedDescriptionKey : description};
NSError *error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeSnapshotFailed userInfo:userInfo]; NSError *error = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSnapshotFailed
userInfo:userInfo];
// Dispatch to result queue // Dispatch to result queue
dispatch_async(queue, ^{ dispatch_async(queue, ^{
@@ -415,7 +456,8 @@ private:
}); });
} else { } else {
#if TARGET_OS_IPHONE #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 #else
MLNImage *mglImage = [[MLNImage alloc] initWithMLNPremultipliedImage:std::move(image)]; MLNImage *mglImage = [[MLNImage alloc] initWithMLNPremultipliedImage:std::move(image)];
mglImage.size = NSMakeSize(mglImage.size.width / options.scale, mglImage.size = NSMakeSize(mglImage.size.width / options.scale,
@@ -427,7 +469,8 @@ private:
dispatch_async(workQueue, ^{ dispatch_async(workQueue, ^{
// Call a function that cannot accidentally capture self. // Call a function that cannot accidentally capture self.
NSError *error; NSError *error;
MLNMapSnapshot *snapshot = MLNSnapshotWithDecoratedImage(mglImage, options, attributions, pointForFn, latLngForFn, overlayHandler, &error); MLNMapSnapshot *snapshot = MLNSnapshotWithDecoratedImage(
mglImage, options, attributions, pointForFn, latLngForFn, overlayHandler, &error);
// Dispatch result to result queue // Dispatch result to result queue
dispatch_async(queue, ^{ dispatch_async(queue, ^{
@@ -440,35 +483,48 @@ private:
}); });
} }
MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage, MLNMapSnapshotOptions *options, void (^overlayHandler)()) { MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions, MLNImage *mglImage,
MLNMapSnapshotOptions *options, void (^overlayHandler)()) {
NSArray<MLNAttributionInfo *> *attributionInfo = MLNAttributionInfosFromAttributions(attributions); NSArray<MLNAttributionInfo *> *attributionInfo =
MLNAttributionInfosFromAttributions(attributions);
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong;
for (NSUInteger styleValue = MLNAttributionInfoStyleLong; styleValue >= MLNAttributionInfoStyleShort; styleValue--) { for (NSUInteger styleValue = MLNAttributionInfoStyleLong;
styleValue >= MLNAttributionInfoStyleShort; styleValue--) {
attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; attributionInfoStyle = (MLNAttributionInfoStyle)styleValue;
CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo]; CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle
sourceAttributionStyle:attributionInfoStyle
attributionInfo:attributionInfo];
if (attributionSize.width <= mglImage.size.width) { if (attributionSize.width <= mglImage.size.width) {
break; break;
} }
} }
UIImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; UIImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle];
CGSize attributionBackgroundSize = [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo]; 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); CGRect logoImageRect =
CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, CGRectMake(MLNLogoImagePosition.x,
logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); 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) { if (!logoImage) {
CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size;
logoImageRect = CGRectMake(0, mglImage.size.height - (MLNLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); logoImageRect =
attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); 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, CGRect attributionBackgroundFrame =
attributionOrigin.y, CGRectMake(attributionOrigin.x, attributionOrigin.y, attributionBackgroundSize.width,
attributionBackgroundSize.width,
attributionBackgroundSize.height); attributionBackgroundSize.height);
CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
attributionBackgroundFrame.origin.y - 1); attributionBackgroundFrame.origin.y - 1);
@@ -503,11 +559,14 @@ MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions,
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();
@@ -517,37 +576,43 @@ MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions,
return compositedImage; return compositedImage;
#else #else
NSRect targetFrame = { .origin = NSZeroPoint, .size = options.size }; NSRect targetFrame = {.origin = NSZeroPoint, .size = options.size};
MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong; MLNAttributionInfoStyle attributionInfoStyle = MLNAttributionInfoStyleLong;
for (NSUInteger styleValue = MLNAttributionInfoStyleLong; styleValue >= MLNAttributionInfoStyleShort; styleValue--) { for (NSUInteger styleValue = MLNAttributionInfoStyleLong;
styleValue >= MLNAttributionInfoStyleShort; styleValue--) {
attributionInfoStyle = (MLNAttributionInfoStyle)styleValue; attributionInfoStyle = (MLNAttributionInfoStyle)styleValue;
CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle attributionInfo:attributionInfo]; CGSize attributionSize = [MLNMapSnapshotter attributionSizeWithLogoStyle:attributionInfoStyle
sourceAttributionStyle:attributionInfoStyle
attributionInfo:attributionInfo];
if (attributionSize.width <= mglImage.size.width) { if (attributionSize.width <= mglImage.size.width) {
break; break;
} }
} }
NSImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle]; NSImage *logoImage = [MLNMapSnapshotter logoImageWithStyle:attributionInfoStyle];
CGSize attributionBackgroundSize = [MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle attributionInfo:attributionInfo]; CGSize attributionBackgroundSize =
[MLNMapSnapshotter attributionTextSizeWithStyle:attributionInfoStyle
attributionInfo:attributionInfo];
NSImage *sourceImage = mglImage; NSImage *sourceImage = mglImage;
CGRect logoImageRect = CGRectMake(MLNLogoImagePosition.x, MLNLogoImagePosition.y, logoImage.size.width, logoImage.size.height); CGRect logoImageRect = CGRectMake(MLNLogoImagePosition.x, MLNLogoImagePosition.y,
CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, logoImage.size.width, logoImage.size.height);
MLNLogoImagePosition.y + 1); CGPoint attributionOrigin = CGPointMake(
targetFrame.size.width - 10 - attributionBackgroundSize.width, MLNLogoImagePosition.y + 1);
if (!logoImage) { if (!logoImage) {
CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size; CGSize defaultLogoSize = [MLNMapSnapshotter maplibreLongStyleLogo].size;
logoImageRect = CGRectMake(0, MLNLogoImagePosition.y, 0, defaultLogoSize.height); logoImageRect = CGRectMake(0, MLNLogoImagePosition.y, 0, defaultLogoSize.height);
attributionOrigin = CGPointMake(10, attributionOrigin.y); attributionOrigin = CGPointMake(10, attributionOrigin.y);
} }
CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, CGRect attributionBackgroundFrame =
attributionOrigin.y, CGRectMake(attributionOrigin.x, attributionOrigin.y, attributionBackgroundSize.width,
attributionBackgroundSize.width,
attributionBackgroundSize.height); attributionBackgroundSize.height);
CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, CGPoint attributionTextPosition =
logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); CGPointMake(attributionBackgroundFrame.origin.x + 10,
logoImageRect.origin.y + (logoImageRect.size.height / 2) -
(attributionBackgroundSize.height / 2));
NSImage *compositedImage = nil; NSImage *compositedImage = nil;
NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
@@ -557,7 +622,7 @@ MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions,
[compositedImage lockFocus]; [compositedImage lockFocus];
[sourceImageRep drawInRect: targetFrame]; [sourceImageRep drawInRect:targetFrame];
overlayHandler(); overlayHandler();
@@ -573,15 +638,20 @@ MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions,
} }
if (options.showsAttribution) { if (options.showsAttribution) {
NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; 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];
@@ -590,7 +660,12 @@ MLNImage *MLNAttributedSnapshot(mbgl::MapSnapshotter::Attributions attributions,
#endif #endif
} }
MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshotOptions *options, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn, mbgl::MapSnapshotter::LatLngForFn latLngForFn, MLNMapSnapshotOverlayHandler overlayHandler, NSError * _Nullable *outError) { 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, ^{ MLNImage *compositedImage = MLNAttributedSnapshot(attributions, mglImage, options, ^{
if (!overlayHandler) { if (!overlayHandler) {
return; return;
@@ -600,7 +675,8 @@ MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshot
if (!context) { if (!context) {
return; return;
} }
MLNMapSnapshotOverlay *snapshotOverlay = [[MLNMapSnapshotOverlay alloc] initWithContext:context MLNMapSnapshotOverlay *snapshotOverlay =
[[MLNMapSnapshotOverlay alloc] initWithContext:context
scale:options.scale scale:options.scale
pointForFn:pointForFn pointForFn:pointForFn
latLngForFn:latLngForFn]; latLngForFn:latLngForFn];
@@ -629,7 +705,8 @@ MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshot
latLngForFn:latLngForFn]; latLngForFn:latLngForFn];
} else { } else {
if (outError) { if (outError) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Failed to generate composited snapshot."}; NSDictionary *userInfo =
@{NSLocalizedDescriptionKey : @"Failed to generate composited snapshot."};
*outError = [NSError errorWithDomain:MLNErrorDomain *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSnapshotFailed code:MLNErrorCodeSnapshotFailed
userInfo:userInfo]; userInfo:userInfo];
@@ -638,7 +715,8 @@ MLNMapSnapshot *MLNSnapshotWithDecoratedImage(MLNImage *mglImage, MLNMapSnapshot
} }
} }
NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnapshotter::Attributions attributions) { NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(
mbgl::MapSnapshotter::Attributions attributions) {
NSMutableArray *infos = [NSMutableArray array]; NSMutableArray *infos = [NSMutableArray array];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
@@ -658,8 +736,9 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return infos; return infos;
} }
+ (void)drawAttributionTextWithStyle:(MLNAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin attributionInfo:(NSArray<MLNAttributionInfo *>*)attributionInfo + (void)drawAttributionTextWithStyle:(MLNAttributionInfoStyle)attributionInfoStyle
{ origin:(CGPoint)origin
attributionInfo:(NSArray<MLNAttributionInfo *> *)attributionInfo {
for (MLNAttributionInfo *info in attributionInfo) { for (MLNAttributionInfo *info in attributionInfo) {
if (info.isFeedbackLink) { if (info.isFeedbackLink) {
continue; continue;
@@ -671,12 +750,12 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
} }
} }
+ (MLNImage *)blurredAttributionBackground:(CIImage *)backgroundImage + (MLNImage *)blurredAttributionBackground:(CIImage *)backgroundImage {
{
CGAffineTransform transform = CGAffineTransformIdentity; CGAffineTransform transform = CGAffineTransformIdentity;
CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"]; CIFilter *clamp = [CIFilter filterWithName:@"CIAffineClamp"];
[clamp setValue:backgroundImage forKey:kCIInputImageKey]; [clamp setValue:backgroundImage forKey:kCIInputImageKey];
[clamp setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)] forKey:@"inputTransform"]; [clamp setValue:[NSValue valueWithBytes:&transform objCType:@encode(CGAffineTransform)]
forKey:@"inputTransform"];
CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[attributionBlurFilter setValue:[clamp outputImage] forKey:kCIInputImageKey]; [attributionBlurFilter setValue:[clamp outputImage] forKey:kCIInputImageKey];
@@ -702,8 +781,7 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return image; return image;
} }
+ (MLNImage *)logoImageWithStyle:(MLNAttributionInfoStyle)style + (MLNImage *)logoImageWithStyle:(MLNAttributionInfoStyle)style {
{
MLNImage *logoImage; MLNImage *logoImage;
switch (style) { switch (style) {
case MLNAttributionInfoStyleLong: case MLNAttributionInfoStyleLong:
@@ -711,9 +789,13 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
break; break;
case MLNAttributionInfoStyleMedium: case MLNAttributionInfoStyleMedium:
#if TARGET_OS_IPHONE #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 #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 #endif
break; break;
case MLNAttributionInfoStyleShort: case MLNAttributionInfoStyleShort:
@@ -723,22 +805,28 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return logoImage; return logoImage;
} }
+ (MLNImage *)maplibreLongStyleLogo + (MLNImage *)maplibreLongStyleLogo {
{
MLNImage *logoImage; MLNImage *logoImage;
#if TARGET_OS_IPHONE #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 #else
logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; logoImage = [[NSImage alloc]
initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox"
ofType:@"pdf"]];
#endif #endif
return logoImage; return logoImage;
} }
+ (CGSize)attributionSizeWithLogoStyle:(MLNAttributionInfoStyle)logoStyle sourceAttributionStyle:(MLNAttributionInfoStyle)attributionStyle attributionInfo:(NSArray<MLNAttributionInfo *>*)attributionInfo + (CGSize)attributionSizeWithLogoStyle:(MLNAttributionInfoStyle)logoStyle
{ sourceAttributionStyle:(MLNAttributionInfoStyle)attributionStyle
attributionInfo:(NSArray<MLNAttributionInfo *> *)attributionInfo {
MLNImage *logoImage = [self logoImageWithStyle:logoStyle]; 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;
@@ -751,8 +839,8 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return attributionSize; return attributionSize;
} }
+ (CGSize)attributionTextSizeWithStyle:(MLNAttributionInfoStyle)attributionStyle attributionInfo:(NSArray<MLNAttributionInfo *>*)attributionInfo + (CGSize)attributionTextSizeWithStyle:(MLNAttributionInfoStyle)attributionStyle
{ attributionInfo:(NSArray<MLNAttributionInfo *> *)attributionInfo {
CGSize attributionBackgroundSize = CGSizeMake(10, 0); CGSize attributionBackgroundSize = CGSizeMake(10, 0);
for (MLNAttributionInfo *info in attributionInfo) { for (MLNAttributionInfo *info in attributionInfo) {
if (info.isFeedbackLink) { if (info.isFeedbackLink) {
@@ -760,14 +848,14 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
} }
CGSize attributionSize = [info titleWithStyle:attributionStyle].size; CGSize attributionSize = [info titleWithStyle:attributionStyle].size;
attributionBackgroundSize.width += attributionSize.width + 10; attributionBackgroundSize.width += attributionSize.width + 10;
attributionBackgroundSize.height = MAX(attributionSize.height, attributionBackgroundSize.height); attributionBackgroundSize.height =
MAX(attributionSize.height, attributionBackgroundSize.height);
} }
return attributionBackgroundSize; return attributionBackgroundSize;
} }
- (void)cancel - (void)cancel {
{
MLNLogInfo(@"Cancelling snapshotter."); MLNLogInfo(@"Cancelling snapshotter.");
if (_mbglMapSnapshotter) { if (_mbglMapSnapshotter) {
@@ -784,8 +872,7 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
// For non retina screens the ratio is 1:1 MLNSnapshotterMinimumPixelSize // For non retina screens the ratio is 1:1 MLNSnapshotterMinimumPixelSize
mbgl::Size size = { mbgl::Size size = {
static_cast<uint32_t>(MAX(options.size.width, MLNSnapshotterMinimumPixelSize)), static_cast<uint32_t>(MAX(options.size.width, MLNSnapshotterMinimumPixelSize)),
static_cast<uint32_t>(MAX(options.size.height, MLNSnapshotterMinimumPixelSize)) static_cast<uint32_t>(MAX(options.size.height, MLNSnapshotterMinimumPixelSize))};
};
float pixelRatio = MAX(options.scale, 1); float pixelRatio = MAX(options.scale, 1);
@@ -794,8 +881,7 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal];
mbgl::ResourceOptions resourceOptions; mbgl::ResourceOptions resourceOptions;
resourceOptions resourceOptions.withTileServerOptions(*tileServerOptions)
.withTileServerOptions(*tileServerOptions)
.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) .withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
.withAssetPath(NSBundle.mainBundle.resourceURL.path.UTF8String); .withAssetPath(NSBundle.mainBundle.resourceURL.path.UTF8String);
mbgl::ClientOptions clientOptions; mbgl::ClientOptions clientOptions;
@@ -806,7 +892,8 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
} }
// Create the snapshotter // Create the snapshotter
auto localFontFamilyName = config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr; auto localFontFamilyName =
config.localFontFamilyName ? std::string(config.localFontFamilyName.UTF8String) : nullptr;
_delegateHost = std::make_unique<MLNMapSnapshotterDelegateHost>(self); _delegateHost = std::make_unique<MLNMapSnapshotterDelegateHost>(self);
_mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>( _mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(
size, pixelRatio, resourceOptions, clientOptions, *_delegateHost, localFontFamilyName); size, pixelRatio, resourceOptions, clientOptions, *_delegateHost, localFontFamilyName);
@@ -852,16 +939,14 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return [[MLNStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self]; return [[MLNStyle alloc] initWithRawStyle:&_mbglMapSnapshotter->getStyle() stylable:self];
} }
- (double)alphaForShapeAnnotation:(MLNShape *)annotation - (double)alphaForShapeAnnotation:(MLNShape *)annotation {
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:alphaForShapeAnnotation:)]) { if ([self.delegate respondsToSelector:@selector(mapSnapshotter:alphaForShapeAnnotation:)]) {
return [self.delegate mapSnapshotter:self alphaForShapeAnnotation:annotation]; return [self.delegate mapSnapshotter:self alphaForShapeAnnotation:annotation];
} }
return 1.0; return 1.0;
} }
- (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation - (mbgl::Color)strokeColorForShapeAnnotation:(MLNShape *)annotation {
{
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:strokeColorForShapeAnnotation:)]) { if ([self.delegate respondsToSelector:@selector(mapSnapshotter:strokeColorForShapeAnnotation:)]) {
MLNColor *color = [self.delegate mapSnapshotter:self strokeColorForShapeAnnotation:annotation]; MLNColor *color = [self.delegate mapSnapshotter:self strokeColorForShapeAnnotation:annotation];
return color.mgl_color; return color.mgl_color;
@@ -870,18 +955,17 @@ NSArray<MLNAttributionInfo *> *MLNAttributionInfosFromAttributions(mbgl::MapSnap
return mbgl::Color::white(); return mbgl::Color::white();
} }
- (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation - (mbgl::Color)fillColorForPolygonAnnotation:(MLNPolygon *)annotation {
{ if ([self.delegate respondsToSelector:@selector(mapSnapshotter:fillColorForPolygonAnnotation:)]) {
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:fillColorForPolygonAnnotation:)]){
MLNColor *color = [self.delegate mapSnapshotter:self fillColorForPolygonAnnotation:annotation]; MLNColor *color = [self.delegate mapSnapshotter:self fillColorForPolygonAnnotation:annotation];
return color.mgl_color; return color.mgl_color;
} }
return mbgl::Color::white(); return mbgl::Color::white();
} }
- (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation - (CGFloat)lineWidthForPolylineAnnotation:(MLNPolyline *)annotation {
{ if ([self.delegate respondsToSelector:@selector(mapSnapshotter:
if ([self.delegate respondsToSelector:@selector(mapSnapshotter:lineWidthForPolylineAnnotation:)]) { lineWidthForPolylineAnnotation:)]) {
return [self.delegate mapSnapshotter:self lineWidthForPolylineAnnotation:annotation]; return [self.delegate mapSnapshotter:self lineWidthForPolylineAnnotation:annotation];
} }

View File

@@ -1,28 +1,25 @@
#import "MLNMultiPoint_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNShape_Private.h"
#import "NSCoder+MLNAdditions.h"
#import "MLNTypes.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNShape_Private.h"
#import "MLNTypes.h"
#import "NSCoder+MLNAdditions.h"
@implementation MLNMultiPoint @implementation MLNMultiPoint {
{
std::optional<mbgl::LatLngBounds> _bounds; std::optional<mbgl::LatLngBounds> _bounds;
std::vector<CLLocationCoordinate2D> _coordinates; std::vector<CLLocationCoordinate2D> _coordinates;
} }
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
{ MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count);
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long) count);
self = [super init]; self = [super init];
if (self) if (self) {
{
if (!count) { if (!count) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"A multipoint must have at least one vertex."]; format:@"A multipoint must have at least one vertex."];
} }
_coordinates = { coords, coords + count }; _coordinates = {coords, coords + count};
} }
return self; return self;
@@ -32,8 +29,7 @@
return YES; return YES;
} }
- (instancetype)initWithCoder:(NSCoder *)decoder - (instancetype)initWithCoder:(NSCoder *)decoder {
{
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) { if (self = [super initWithCoder:decoder]) {
_coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"]; _coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"];
@@ -41,62 +37,54 @@
return self; return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"]; [coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"];
} }
- (BOOL)isEqual:(id)other - (BOOL)isEqual:(id)other {
{
if (self == other) return YES; if (self == other) return YES;
if (![other isKindOfClass:[MLNMultiPoint class]]) return NO; if (![other isKindOfClass:[MLNMultiPoint class]]) return NO;
MLNMultiPoint *otherMultipoint = other; MLNMultiPoint *otherMultipoint = other;
return ([super isEqual:otherMultipoint] return ([super isEqual:otherMultipoint] && _coordinates == otherMultipoint->_coordinates);
&& _coordinates == otherMultipoint->_coordinates);
} }
- (NSUInteger)hash - (NSUInteger)hash {
{
NSUInteger hash = [super hash]; NSUInteger hash = [super hash];
for (auto coord : _coordinates) { for (auto coord : _coordinates) {
hash += @(coord.latitude+coord.longitude).hash; hash += @(coord.latitude + coord.longitude).hash;
} }
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate - (CLLocationCoordinate2D)coordinate {
{
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
return _coordinates.at(0); return _coordinates.at(0);
} }
- (NSUInteger)pointCount - (NSUInteger)pointCount {
{
return _coordinates.size(); return _coordinates.size();
} }
+ (NSSet<NSString *> *)keyPathsForValuesAffectingPointCount + (NSSet<NSString *> *)keyPathsForValuesAffectingPointCount {
{
return [NSSet setWithObjects:@"coordinates", nil]; return [NSSet setWithObjects:@"coordinates", nil];
} }
- (CLLocationCoordinate2D *)coordinates - (CLLocationCoordinate2D *)coordinates {
{
return _coordinates.data(); return _coordinates.data();
} }
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range - (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range {
{ if (range.location + range.length > [self pointCount]) {
if (range.location + range.length > [self pointCount]) [NSException
{ raise:NSRangeException
[NSException raise:NSRangeException
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
NSStringFromRange(range), (unsigned long)[self pointCount]]; 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 { - (void)setCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count {
@@ -107,21 +95,24 @@
} }
[self willChangeValueForKey:@"coordinates"]; [self willChangeValueForKey:@"coordinates"];
_coordinates = { coords, coords + count }; _coordinates = {coords, coords + count};
_bounds = {}; _bounds = {};
[self didChangeValueForKey:@"coordinates"]; [self didChangeValueForKey:@"coordinates"];
} }
- (void)insertCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count atIndex:(NSUInteger)index { - (void)insertCoordinates:(const CLLocationCoordinate2D *)coords
MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count, (unsigned long)index); count:(NSUInteger)count
atIndex:(NSUInteger)index {
MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count,
(unsigned long)index);
if (!count) { if (!count) {
return; return;
} }
if (index > _coordinates.size()) { if (index > _coordinates.size()) {
[NSException raise:NSRangeException [NSException raise:NSRangeException
format:@"Invalid index %lu for existing coordinate count %ld", format:@"Invalid index %lu for existing coordinate count %ld", (unsigned long)index,
(unsigned long)index, (unsigned long)[self pointCount]]; (unsigned long)[self pointCount]];
} }
[self willChangeValueForKey:@"coordinates"]; [self willChangeValueForKey:@"coordinates"];
@@ -130,20 +121,22 @@
[self didChangeValueForKey:@"coordinates"]; [self didChangeValueForKey:@"coordinates"];
} }
- (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count - (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
{
MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count); MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count);
[self insertCoordinates:coords count:count atIndex:_coordinates.size()]; [self insertCoordinates:coords count:count atIndex:_coordinates.size()];
} }
- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords - (void)replaceCoordinatesInRange:(NSRange)range
{ withCoordinates:(const CLLocationCoordinate2D *)coords {
MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range)); MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range));
[self replaceCoordinatesInRange:range withCoordinates:coords count:range.length]; [self replaceCoordinatesInRange:range withCoordinates:coords count:range.length];
} }
- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { - (void)replaceCoordinatesInRange:(NSRange)range
MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count, NSStringFromRange(range)); withCoordinates:(const CLLocationCoordinate2D *)coords
count:(NSUInteger)count {
MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count,
NSStringFromRange(range));
if (!count && !range.length) { if (!count && !range.length) {
return; return;
} }
@@ -157,9 +150,11 @@
[self willChangeValueForKey:@"coordinates"]; [self willChangeValueForKey:@"coordinates"];
std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location); std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location);
if (count >= range.length) { if (count >= range.length) {
_coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords, coords + count - range.length); _coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords,
coords + count - range.length);
} else { } else {
_coordinates.erase(_coordinates.begin() + range.location + count, _coordinates.begin() + NSMaxRange(range)); _coordinates.erase(_coordinates.begin() + range.location + count,
_coordinates.begin() + NSMaxRange(range));
} }
_bounds = {}; _bounds = {};
[self didChangeValueForKey:@"coordinates"]; [self didChangeValueForKey:@"coordinates"];
@@ -171,8 +166,7 @@
[self replaceCoordinatesInRange:range withCoordinates:&coords count:0]; [self replaceCoordinatesInRange:range withCoordinates:&coords count:0];
} }
- (MLNCoordinateBounds)overlayBounds - (MLNCoordinateBounds)overlayBounds {
{
if (!_bounds) { if (!_bounds) {
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (auto coordinate : _coordinates) { for (auto coordinate : _coordinates) {
@@ -187,21 +181,20 @@
return MLNCoordinateBoundsFromLatLngBounds(*_bounds); return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
} }
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
{
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
} }
- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id <MLNMultiPointDelegate>)delegate - (mbgl::Annotation)annotationObjectWithDelegate:(__unused id<MLNMultiPointDelegate>)delegate {
{ MLNAssert(NO, @"Cannot add an annotation from an instance of %@",
MLNAssert(NO, @"Cannot add an annotation from an instance of %@", NSStringFromClass([self class])); NSStringFromClass([self class]));
return mbgl::SymbolAnnotation(mbgl::Point<double>()); return mbgl::SymbolAnnotation(mbgl::Point<double>());
} }
- (NSString *)description - (NSString *)description {
{
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], NSStringFromClass([self class]), (void *)self,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }

View File

@@ -1,25 +1,24 @@
#import "MLNNetworkConfiguration_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNNetworkConfiguration_Private.h"
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "MLNSettings_Private.h" #import "MLNSettings_Private.h"
#endif #endif
#import "MLNReachability.h" #import "MLNReachability.h"
static NSString * const MLNStartTime = @"start_time"; static NSString *const MLNStartTime = @"start_time";
static NSString * const MLNResourceType = @"resource_type"; static NSString *const MLNResourceType = @"resource_type";
NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace"; NSString *const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
@interface MLNNetworkConfiguration () <MLNNativeNetworkDelegate> @interface MLNNetworkConfiguration () <MLNNativeNetworkDelegate>
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary*> *events; @property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *events;
@property (nonatomic, weak) id<MLNNetworkConfigurationMetricsDelegate> metricsDelegate; @property (nonatomic, weak) id<MLNNetworkConfigurationMetricsDelegate> metricsDelegate;
@property (nonatomic) dispatch_queue_t eventsQueue; @property (nonatomic) dispatch_queue_t eventsQueue;
@end @end
@implementation MLNNetworkConfiguration @implementation MLNNetworkConfiguration {
{
NSURLSessionConfiguration *_sessionConfig; NSURLSessionConfiguration *_sessionConfig;
} }
@@ -27,7 +26,8 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
if (self = [super init]) { if (self = [super init]) {
self.sessionConfiguration = nil; self.sessionConfiguration = nil;
_events = [NSMutableDictionary dictionary]; _events = [NSMutableDictionary dictionary];
_eventsQueue = dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT); _eventsQueue =
dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT);
} }
return self; return self;
@@ -54,7 +54,8 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
} }
+ (NSURLSessionConfiguration *)defaultSessionConfiguration { + (NSURLSessionConfiguration *)defaultSessionConfiguration {
NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfiguration =
[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.timeoutIntervalForResource = 30; sessionConfiguration.timeoutIntervalForResource = 30;
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8; sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
@@ -88,48 +89,42 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
} }
- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request { - (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request {
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) { if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
return [self.delegate willSendRequest:request]; return [self.delegate willSendRequest:request];
} }
return request; return request;
} }
- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response { - (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response {
if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) { if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data
urlResponse:response.response urlResponse:response.response
error:response.error]; error:response.error];
if (tempResponse) { if (tempResponse) {
MLNInternalNetworkResponse *internalResponse = [MLNInternalNetworkResponse responseWithData:tempResponse.data MLNInternalNetworkResponse *internalResponse =
[MLNInternalNetworkResponse responseWithData:tempResponse.data
urlResponse:tempResponse.response urlResponse:tempResponse.response
error:tempResponse.error]; error:tempResponse.error];
return internalResponse; return internalResponse;
} else { } else {
return nil; return nil;
} }
} }
return response; return response;
} }
- (NSURLSessionConfiguration *)sessionConfiguration { - (NSURLSessionConfiguration *)sessionConfiguration {
NSURLSessionConfiguration *sessionConfig = nil; NSURLSessionConfiguration *sessionConfig = nil;
@synchronized (self) { @synchronized(self) {
sessionConfig = _sessionConfig; sessionConfig = _sessionConfig;
} }
return sessionConfig; return sessionConfig;
} }
- (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration { - (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
@synchronized (self) { @synchronized(self) {
if (sessionConfiguration == nil) { if (sessionConfiguration == nil) {
_sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration]; _sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration];
} else { } else {
@@ -141,7 +136,7 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
- (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType { - (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType {
if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) { if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) {
NSDate *startDate = [NSDate date]; NSDate *startDate = [NSDate date];
[self setEventDictionary:@{ MLNStartTime: startDate, MLNResourceType: resourceType } [self setEventDictionary:@{MLNStartTime : startDate, MLNResourceType : resourceType}
forKey:urlString]; forKey:urlString];
} }
} }
@@ -164,8 +159,7 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
// MARK: - Event management // MARK: - Event management
- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action - (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action {
{
if ([response isKindOfClass:[NSURLResponse class]]) { if ([response isKindOfClass:[NSURLResponse class]]) {
NSString *urlString = response.URL.relativePath; NSString *urlString = response.URL.relativePath;
if (urlString && [self eventDictionaryForKey:urlString]) { if (urlString && [self eventDictionaryForKey:urlString]) {
@@ -179,31 +173,31 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
} }
} }
- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action - (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action {
{
NSString *urlString = response.URL.relativePath; NSString *urlString = response.URL.relativePath;
NSDictionary *parameters = [self eventDictionaryForKey:urlString]; NSDictionary *parameters = [self eventDictionaryForKey:urlString];
NSDate *startDate = [parameters objectForKey:MLNStartTime]; NSDate *startDate = [parameters objectForKey:MLNStartTime];
NSDate *endDate = [NSDate date]; NSDate *endDate = [NSDate date];
NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate]; NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate];
NSDateFormatter* iso8601Formatter = [[NSDateFormatter alloc] init]; NSDateFormatter *iso8601Formatter = [[NSDateFormatter alloc] init];
iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ"; iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]]; NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]];
NSMutableArray *attributes = [NSMutableArray array]; NSMutableArray *attributes = [NSMutableArray array];
[attributes addObject:@{ @"name" : @"requestUrl" , @"value" : urlString }]; [attributes addObject:@{@"name" : @"requestUrl", @"value" : urlString}];
[attributes addObject:@{ @"name" : MLNResourceType , @"value" : [parameters objectForKey:MLNResourceType] }]; [attributes
addObject:@{@"name" : MLNResourceType, @"value" : [parameters objectForKey:MLNResourceType]}];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode]; NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
[attributes addObject:@{ @"name" : @"responseCode", @"value" : @(responseCode)}]; [attributes addObject:@{@"name" : @"responseCode", @"value" : @(responseCode)}];
} }
BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi]; BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi];
[attributes addObject:@{ @"name" : @"wifiOn", @"value" : @(isWIFIOn)}]; [attributes addObject:@{@"name" : @"wifiOn", @"value" : @(isWIFIOn)}];
if (action) { if (action) {
[attributes addObject:@{ @"name" : @"action" , @"value" : action }]; [attributes addObject:@{@"name" : @"action", @"value" : action}];
} }
double elapsedTimeInMS = elapsedTime * 1000.0; double elapsedTimeInMS = elapsedTime * 1000.0;
@@ -212,14 +206,14 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
@"event" : kMLNDownloadPerformanceEvent, @"event" : kMLNDownloadPerformanceEvent,
@"created" : createdDate, @"created" : createdDate,
@"sessionId" : [NSUUID UUID].UUIDString, @"sessionId" : [NSUUID UUID].UUIDString,
@"counters" : @[ @{ @"name" : @"elapsedMS" , @"value" : @(elapsedTimeInMS) } ], @"counters" : @[ @{@"name" : @"elapsedMS", @"value" : @(elapsedTimeInMS)} ],
@"attributes" : attributes @"attributes" : attributes
}; };
} }
// MARK: - Events dictionary access // MARK: - Events dictionary access
- (nullable NSDictionary*)eventDictionaryForKey:(nonnull NSString*)key { - (nullable NSDictionary *)eventDictionaryForKey:(nonnull NSString *)key {
__block NSDictionary *dictionary; __block NSDictionary *dictionary;
dispatch_sync(self.eventsQueue, ^{ dispatch_sync(self.eventsQueue, ^{
@@ -229,13 +223,13 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
return dictionary; return dictionary;
} }
- (void)setEventDictionary:(nonnull NSDictionary*)dictionary forKey:(nonnull NSString*)key { - (void)setEventDictionary:(nonnull NSDictionary *)dictionary forKey:(nonnull NSString *)key {
dispatch_barrier_async(self.eventsQueue, ^{ dispatch_barrier_async(self.eventsQueue, ^{
[self.events setObject:dictionary forKey:key]; [self.events setObject:dictionary forKey:key];
}); });
} }
- (void)removeEventDictionaryForKey:(nonnull NSString*)key { - (void)removeEventDictionaryForKey:(nonnull NSString *)key {
dispatch_barrier_async(self.eventsQueue, ^{ dispatch_barrier_async(self.eventsQueue, ^{
[self.events removeObjectForKey:key]; [self.events removeObjectForKey:key];
}); });

View File

@@ -1,12 +1,11 @@
#import <Foundation/Foundation.h>
#import "MLNNetworkResponse.h" #import "MLNNetworkResponse.h"
#import <Foundation/Foundation.h>
@implementation MLNNetworkResponse @implementation MLNNetworkResponse
+(MLNNetworkResponse *)responseWithData:(NSData *)data + (MLNNetworkResponse *)responseWithData:(NSData *)data
urlResponse:(NSURLResponse *)response urlResponse:(NSURLResponse *)response
error:(NSError *)error { error:(NSError *)error {
MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init]; MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init];
tempResult.data = data; tempResult.data = data;
tempResult.response = response; tempResult.response = response;

View File

@@ -1,12 +1,12 @@
#import "MLNOfflinePack_Private.h" #import "MLNOfflinePack_Private.h"
#import "MLNOfflineStorage_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNOfflineRegion_Private.h" #import "MLNOfflineRegion_Private.h"
#import "MLNTilePyramidOfflineRegion.h" #import "MLNOfflineStorage_Private.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
#import "MLNShapeOfflineRegion.h" #import "MLNShapeOfflineRegion.h"
#import "MLNShapeOfflineRegion_Private.h" #import "MLNShapeOfflineRegion_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNTilePyramidOfflineRegion.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
#import "NSValue+MLNAdditions.h" #import "NSValue+MLNAdditions.h"
@@ -25,15 +25,16 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
#define MLNAssertOfflinePackIsValid() \ #define MLNAssertOfflinePackIsValid() \
do { \ do { \
if (_state == MLNOfflinePackStateInvalid) { \ if (_state == MLNOfflinePackStateInvalid) { \
[NSException raise:MLNInvalidOfflinePackException \ [NSException \
format: \ raise:MLNInvalidOfflinePackException \
@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \ format:@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \
@"on this instance of MLNOfflinePack, rendering it invalid. It is an " \ @"on this instance of MLNOfflinePack, rendering it invalid. It is an " \
@"error to send any message to this pack."]; \ @"error to send any message to this pack."]; \
} \ } \
} while (NO); } while (NO);
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private, MLNTilePyramidOfflineRegion_Private> @interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private,
MLNTilePyramidOfflineRegion_Private>
@end @end
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private> @interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
@@ -67,7 +68,9 @@ private:
MLNLogInfo(@"Calling this initializer is not allowed."); MLNLogInfo(@"Calling this initializer is not allowed.");
if (self = [super init]) { if (self = [super init]) {
_state = MLNOfflinePackStateInvalid; _state = MLNOfflinePackStateInvalid;
NSLog(@"%s called; did you mean to call +[MLNOfflineStorage addPackForRegion:withContext:completionHandler:] instead?", __PRETTY_FUNCTION__); NSLog(@"%s called; did you mean to call +[MLNOfflineStorage "
@"addPackForRegion:withContext:completionHandler:] instead?",
__PRETTY_FUNCTION__);
} }
return self; return self;
} }
@@ -78,30 +81,36 @@ private:
_state = MLNOfflinePackStateUnknown; _state = MLNOfflinePackStateUnknown;
_mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource]; _mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource];
_mbglDatabaseFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self)); _mbglDatabaseFileSource->setOfflineRegionObserver(
*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self));
} }
return self; return self;
} }
- (void)dealloc { - (void)dealloc {
MLNAssert(_state == MLNOfflinePackStateInvalid, @"MLNOfflinePack was not invalided prior to deallocation."); MLNAssert(_state == MLNOfflinePackStateInvalid,
@"MLNOfflinePack was not invalided prior to deallocation.");
} }
- (id <MLNOfflineRegion>)region { - (id<MLNOfflineRegion>)region {
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
const mbgl::OfflineRegionDefinition &regionDefinition = _mbglOfflineRegion->getDefinition(); const mbgl::OfflineRegionDefinition &regionDefinition = _mbglOfflineRegion->getDefinition();
MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private."); MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)],
MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNShapeOfflineRegion should conform to 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{ return std::visit(
[&] (const mbgl::OfflineTilePyramidRegionDefinition def){ mbgl::overloaded{[&](const mbgl::OfflineTilePyramidRegionDefinition def) {
return (id <MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc] initWithOfflineRegionDefinition:def]; return (id<MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc]
initWithOfflineRegionDefinition:def];
}, },
[&] (const mbgl::OfflineGeometryRegionDefinition& def){ [&](const mbgl::OfflineGeometryRegionDefinition &def) {
return (id <MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc] initWithOfflineRegionDefinition:def]; return (id<MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc]
} initWithOfflineRegionDefinition:def];
}, regionDefinition); }},
regionDefinition);
} }
- (NSData *)context { - (NSData *)context {
@@ -111,7 +120,8 @@ private:
return [NSData dataWithBytes:&metadata[0] length:metadata.size()]; return [NSData dataWithBytes:&metadata[0] length:metadata.size()];
} }
- (void)setContext:(NSData *)context completionHandler:(void (^_Nullable)(NSError * _Nullable error))completion { - (void)setContext:(NSData *)context
completionHandler:(void (^_Nullable)(NSError *_Nullable error))completion {
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
mbgl::OfflineRegionMetadata metadata(context.length); mbgl::OfflineRegionMetadata metadata(context.length);
@@ -119,16 +129,23 @@ private:
[self willChangeValueForKey:@"context"]; [self willChangeValueForKey:@"context"];
__weak MLNOfflinePack *weakSelf = self; __weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->updateOfflineMetadata(_mbglOfflineRegion->getID(), metadata, [&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr> mbglOfflineRegionMetadata) { _mbglDatabaseFileSource->updateOfflineMetadata(
_mbglOfflineRegion->getID(), metadata,
[&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr>
mbglOfflineRegionMetadata) {
NSError *error; NSError *error;
if (!mbglOfflineRegionMetadata) { if (!mbglOfflineRegionMetadata) {
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str()); NSString *errorDescription =
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{ @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str());
NSLocalizedDescriptionKey: errorDescription, error = [NSError errorWithDomain:MLNErrorDomain
} : nil]; code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:errorDescription ? @{
NSLocalizedDescriptionKey : errorDescription,
}
: nil];
} }
dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) { dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) {
[weakSelf reloadWithCompletionHandler:^(NSError * _Nullable reloadingError) { [weakSelf reloadWithCompletionHandler:^(NSError *_Nullable reloadingError) {
MLNOfflinePack *strongSelf = weakSelf; MLNOfflinePack *strongSelf = weakSelf;
[strongSelf didChangeValueForKey:@"context"]; [strongSelf didChangeValueForKey:@"context"];
if (completion) { if (completion) {
@@ -139,11 +156,12 @@ private:
}); });
} }
- (void)reloadWithCompletionHandler:(void (^)(NSError * _Nullable error))completion { - (void)reloadWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
auto regionID = _mbglOfflineRegion->getID(); auto regionID = _mbglOfflineRegion->getID();
MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage]; MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage];
__weak MLNOfflinePack *weakSelf = self; __weak MLNOfflinePack *weakSelf = self;
[sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs, __unused NSError * _Nullable error) { [sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs,
__unused NSError *_Nullable error) {
MLNOfflinePack *strongSelf = weakSelf; MLNOfflinePack *strongSelf = weakSelf;
for (MLNOfflinePack *pack in packs) { for (MLNOfflinePack *pack in packs) {
if (pack.mbglOfflineRegion->getID() == regionID) { if (pack.mbglOfflineRegion->getID() == regionID) {
@@ -161,7 +179,8 @@ private:
self.state = MLNOfflinePackStateActive; self.state = MLNOfflinePackStateActive;
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active); _mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion,
mbgl::OfflineRegionDownloadState::Active);
} }
- (void)suspend { - (void)suspend {
@@ -173,15 +192,17 @@ private:
_isSuspending = YES; _isSuspending = YES;
} }
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive); _mbglDatabaseFileSource->setOfflineRegionDownloadState(
*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
} }
- (void)invalidate { - (void)invalidate {
MLNLogInfo(@"Invalidating pack."); MLNLogInfo(@"Invalidating pack.");
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack."); MLNAssert(_state != MLNOfflinePackStateInvalid,
@"Cannot invalidate an already invalid offline pack.");
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region"); MLNAssert(self.mbglOfflineRegion, @"Should have a valid region");
@synchronized (self) { @synchronized(self) {
self.state = MLNOfflinePackStateInvalid; self.state = MLNOfflinePackStateInvalid;
if (self.mbglOfflineRegion) { if (self.mbglOfflineRegion) {
_mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr); _mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
@@ -196,11 +217,13 @@ private:
// A progress update has arrived after the call to // A progress update has arrived after the call to
// -[MLNOfflineStorage removePack:withCompletionHandler:] but before the // -[MLNOfflineStorage removePack:withCompletionHandler:] but before the
// removal is complete and the completion handler is called. // removal is complete and the completion handler is called.
MLNAssert(_state == MLNOfflinePackStateInvalid, @"A valid MLNOfflinePack has no mbgl::OfflineRegion."); MLNAssert(_state == MLNOfflinePackStateInvalid,
@"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
return; 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) { if (!_isSuspending || state != MLNOfflinePackStateActive) {
_isSuspending = NO; _isSuspending = NO;
@@ -213,7 +236,9 @@ private:
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
__weak MLNOfflinePack *weakSelf = self; __weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->getOfflineRegionStatus(*_mbglOfflineRegion, [&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) { _mbglDatabaseFileSource->getOfflineRegionStatus(
*_mbglOfflineRegion,
[&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) {
if (status) { if (status) {
mbgl::OfflineRegionStatus checkedStatus = *status; mbgl::OfflineRegionStatus checkedStatus = *status;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -225,7 +250,8 @@ private:
} }
- (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status { - (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) { switch (status.downloadState) {
case mbgl::OfflineRegionDownloadState::Inactive: case mbgl::OfflineRegionDownloadState::Inactive:
@@ -247,11 +273,14 @@ private:
progress.countOfTilesCompleted = status.completedTileCount; progress.countOfTilesCompleted = status.completedTileCount;
progress.countOfTileBytesCompleted = status.completedTileSize; progress.countOfTileBytesCompleted = status.completedTileSize;
progress.countOfResourcesExpected = status.requiredResourceCount; progress.countOfResourcesExpected = status.requiredResourceCount;
progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX; progress.maximumResourcesExpected =
status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
self.progress = progress; self.progress = progress;
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyState: @(self.state), NSDictionary *userInfo = @{
MLNOfflinePackUserInfoKeyProgress: [NSValue valueWithMLNOfflinePackProgress:progress]}; MLNOfflinePackUserInfoKeyState : @(self.state),
MLNOfflinePackUserInfoKeyProgress : [NSValue valueWithMLNOfflinePackProgress:progress]
};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification [noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification
@@ -263,16 +292,14 @@ private:
MLNLogError(@"Error: %@", error.localizedDescription); MLNLogError(@"Error: %@", error.localizedDescription);
MLNLogInfo(@"Notifying about pack error."); MLNLogInfo(@"Notifying about pack error.");
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyError: error }; NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyError : error};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackErrorNotification [noteCenter postNotificationName:MLNOfflinePackErrorNotification object:self userInfo:userInfo];
object:self
userInfo:userInfo];
} }
- (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit { - (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit {
MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit); MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit);
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyMaximumCount: @(limit) }; NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyMaximumCount : @(limit)};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter]; NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification [noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification
object:self object:self
@@ -297,8 +324,10 @@ NSError *MLNErrorFromResponseError(mbgl::Response::Error error) {
default: default:
break; break;
} }
return [NSError errorWithDomain:MLNErrorDomain code:errorCode userInfo:@{ return [NSError errorWithDomain:MLNErrorDomain
NSLocalizedFailureReasonErrorKey: @(error.message.c_str()) code:errorCode
userInfo:@{
NSLocalizedFailureReasonErrorKey : @(error.message.c_str())
}]; }];
} }

View File

@@ -1,17 +1,17 @@
#import "MLNOfflineStorage_Private.h" #import "MLNOfflineStorage_Private.h"
#import "MLNFoundation_Private.h" #import "MLNFoundation_Private.h"
#import "MLNSettings_Private.h"
#import "MLNGeometry_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 "MLNLoggingConfiguration_Private.h"
#import "MLNNetworkConfiguration_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 <mbgl/actor/actor.hpp> #include <mbgl/actor/actor.hpp>
#include <mbgl/actor/scheduler.hpp> #include <mbgl/actor/scheduler.hpp>
@@ -25,13 +25,16 @@
#include <memory> #include <memory>
static NSString * const MLNOfflineStorageDatabasePathInfoDictionaryKey = @"MLNOfflineStorageDatabasePath"; static NSString *const MLNOfflineStorageDatabasePathInfoDictionaryKey =
static NSString * const MLNOfflineStorageFileName = @"cache.db"; @"MLNOfflineStorageDatabasePath";
static NSString * const MLNOfflineStorageFileName3_2_0_beta_1 = @"offline.db"; 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 MLNOfflinePackErrorNotification = @"MLNOfflinePackError";
const NSNotificationName MLNOfflinePackMaximumMapboxTilesReachedNotification = @"MLNOfflinePackMaximumMapboxTilesReached"; const NSNotificationName MLNOfflinePackMaximumMapboxTilesReachedNotification =
@"MLNOfflinePackMaximumMapboxTilesReached";
const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyState = @"State"; const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyState = @"State";
const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyProgress = @"Progress"; const MLNOfflinePackUserInfoKey MLNOfflinePackUserInfoKeyProgress = @"Progress";
@@ -60,8 +63,14 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
sharedOfflineStorage = [[self alloc] init]; sharedOfflineStorage = [[self alloc] init];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
[[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(unpauseFileSource:) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage
[[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage selector:@selector(pauseFileSource:) name:UIApplicationDidEnterBackgroundNotification object:nil]; selector:@selector(unpauseFileSource:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:sharedOfflineStorage
selector:@selector(pauseFileSource:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif #endif
[sharedOfflineStorage reloadPacks]; [sharedOfflineStorage reloadPacks];
}); });
@@ -102,8 +111,12 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
MLNLogDebug(@"Setting delegate: %@", newValue); MLNLogDebug(@"Setting delegate: %@", newValue);
_delegate = newValue; _delegate = newValue;
if ([self.delegate respondsToSelector:@selector(offlineStorage:URLForResourceOfKind:withURL:)]) { if ([self.delegate respondsToSelector:@selector(offlineStorage:URLForResourceOfKind:withURL:)]) {
_mbglResourceTransform = std::make_unique<mbgl::Actor<mbgl::ResourceTransform::TransformCallback>>(*mbgl::Scheduler::GetCurrent(), [offlineStorage = self](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb) { _mbglResourceTransform =
NSURL* url = std::make_unique<mbgl::Actor<mbgl::ResourceTransform::TransformCallback>>(
*mbgl::Scheduler::GetCurrent(),
[offlineStorage = self](auto kind_, const std::string &url_,
mbgl::ResourceTransform::FinishedCallback cb) {
NSURL *url =
[NSURL URLWithString:[[NSString alloc] initWithBytes:url_.data() [NSURL URLWithString:[[NSString alloc] initWithBytes:url_.data()
length:url_.length() length:url_.length()
encoding:NSUTF8StringEncoding]]; encoding:NSUTF8StringEncoding]];
@@ -133,7 +146,6 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
case mbgl::Resource::Kind::Unknown: case mbgl::Resource::Kind::Unknown:
kind = MLNResourceKindUnknown; kind = MLNResourceKindUnknown;
break; break;
} }
url = [offlineStorage.delegate offlineStorage:offlineStorage url = [offlineStorage.delegate offlineStorage:offlineStorage
URLForResourceOfKind:kind URLForResourceOfKind:kind
@@ -141,8 +153,11 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
cb(url.absoluteString.UTF8String); cb(url.absoluteString.UTF8String);
}); });
_mbglOnlineFileSource->setResourceTransform({[actorRef = _mbglResourceTransform->self()](auto kind_, const std::string& url_, mbgl::ResourceTransform::FinishedCallback cb_){ _mbglOnlineFileSource->setResourceTransform(
actorRef.invoke(&mbgl::ResourceTransform::TransformCallback::operator(), kind_, url_, std::move(cb_)); {[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 { } else {
_mbglResourceTransform.reset(); _mbglResourceTransform.reset();
@@ -159,28 +174,33 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
MLNInitializeRunLoop(); MLNInitializeRunLoop();
if (self = [super init]) { if (self = [super init]) {
mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; mbgl::TileServerOptions *tileServerOptions =
[[MLNSettings sharedSettings] tileServerOptionsInternal];
mbgl::ResourceOptions resourceOptions; mbgl::ResourceOptions resourceOptions;
resourceOptions.withCachePath(self.databasePath.UTF8String) resourceOptions.withCachePath(self.databasePath.UTF8String)
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
.withTileServerOptions(*tileServerOptions); .withTileServerOptions(*tileServerOptions);
mbgl::ClientOptions clientOptions; mbgl::ClientOptions clientOptions;
_mbglFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::ResourceLoader, resourceOptions, clientOptions); _mbglFileSource = mbgl::FileSourceManager::get()->getFileSource(
_mbglOnlineFileSource = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Network, resourceOptions, clientOptions); mbgl::FileSourceType::ResourceLoader, resourceOptions, clientOptions);
_mbglDatabaseFileSource = std::static_pointer_cast<mbgl::DatabaseFileSource>(std::shared_ptr<mbgl::FileSource>(mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Database, resourceOptions, clientOptions))); _mbglOnlineFileSource = mbgl::FileSourceManager::get()->getFileSource(
mbgl::FileSourceType::Network, resourceOptions, clientOptions);
_mbglDatabaseFileSource = std::static_pointer_cast<mbgl::DatabaseFileSource>(
std::shared_ptr<mbgl::FileSource>(mbgl::FileSourceManager::get()->getFileSource(
mbgl::FileSourceType::Database, resourceOptions, clientOptions)));
// Observe for changes to the tile server options (and find out the current one). // Observe for changes to the tile server options (and find out the current one).
[[MLNSettings sharedSettings] addObserver:self [[MLNSettings sharedSettings]
addObserver:self
forKeyPath:@"tileServerOptionsChangeToken" forKeyPath:@"tileServerOptionsChangeToken"
options:(NSKeyValueObservingOptionInitial | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)
NSKeyValueObservingOptionNew)
context:NULL]; context:NULL];
// Observe for changes to the global access token (and find out the current one). // Observe for changes to the global access token (and find out the current one).
[[MLNSettings sharedSettings] addObserver:self [[MLNSettings sharedSettings]
addObserver:self
forKeyPath:@"apiKey" forKeyPath:@"apiKey"
options:(NSKeyValueObservingOptionInitial | options:(NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew)
NSKeyValueObservingOptionNew)
context:NULL]; context:NULL];
} }
return self; return self;
@@ -197,18 +217,23 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
} }
} }
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context { - (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *, id> *)change
context:(void *)context {
// Synchronize the file source’s access token with the global one in MLNSettings. // Synchronize the file source’s access token with the global one in MLNSettings.
if ([keyPath isEqualToString:@"apiKey"] && object == [MLNSettings sharedSettings]) { if ([keyPath isEqualToString:@"apiKey"] && object == [MLNSettings sharedSettings]) {
NSString *apiKey = change[NSKeyValueChangeNewKey]; NSString *apiKey = change[NSKeyValueChangeNewKey];
if (![apiKey isKindOfClass:[NSNull class]]) { if (![apiKey isKindOfClass:[NSNull class]]) {
_mbglOnlineFileSource->setProperty(mbgl::API_KEY_KEY, apiKey.UTF8String); _mbglOnlineFileSource->setProperty(mbgl::API_KEY_KEY, apiKey.UTF8String);
} }
} else if ([keyPath isEqualToString:@"tileServerOptionsChangeToken"] && object == [MLNSettings sharedSettings]) { } else if ([keyPath isEqualToString:@"tileServerOptionsChangeToken"] &&
object == [MLNSettings sharedSettings]) {
auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; auto tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal];
auto apiBaseURL = tileServerOptions->baseURL(); auto apiBaseURL = tileServerOptions->baseURL();
auto resourceOptions = _mbglOnlineFileSource->getResourceOptions().clone(); auto resourceOptions = _mbglOnlineFileSource->getResourceOptions().clone();
_mbglOnlineFileSource->setResourceOptions(resourceOptions.withTileServerOptions(*tileServerOptions).clone()); _mbglOnlineFileSource->setResourceOptions(
resourceOptions.withTileServerOptions(*tileServerOptions).clone());
} else { } else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
} }
@@ -222,9 +247,12 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
- (NSURL *)databaseURL { - (NSURL *)databaseURL {
if (!_databaseURL) { if (!_databaseURL) {
NSString *customPath = [NSBundle.mainBundle objectForInfoDictionaryKey:MLNOfflineStorageDatabasePathInfoDictionaryKey]; NSString *customPath = [NSBundle.mainBundle
objectForInfoDictionaryKey:MLNOfflineStorageDatabasePathInfoDictionaryKey];
if ([customPath isKindOfClass:[NSString class]]) { if ([customPath isKindOfClass:[NSString class]]) {
_databaseURL = [NSURL fileURLWithPath:customPath.stringByStandardizingPath isDirectory:NO relativeToURL:NSBundle.mainBundle.resourceURL]; _databaseURL = [NSURL fileURLWithPath:customPath.stringByStandardizingPath
isDirectory:NO
relativeToURL:NSBundle.mainBundle.resourceURL];
NSURL *directoryURL = _databaseURL.URLByDeletingLastPathComponent; NSURL *directoryURL = _databaseURL.URLByDeletingLastPathComponent;
[NSFileManager.defaultManager createDirectoryAtURL:directoryURL [NSFileManager.defaultManager createDirectoryAtURL:directoryURL
@@ -235,20 +263,26 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
_databaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:YES]; _databaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:YES];
} }
NSString *databasePath = self.databasePath; NSString *databasePath = self.databasePath;
NSAssert(databasePath, @"Offline pack database URL “%@” is not a valid file URL.", _databaseURL); 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 // Move the offline database from v3.2.0-beta.1 to a location that can
// also be used for ambient caching. // also be used for ambient caching.
if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) {
NSString *legacyDatabasePath = [[self class] legacyDatabasePath]; NSString *legacyDatabasePath = [[self class] legacyDatabasePath];
[[NSFileManager defaultManager] moveItemAtPath:legacyDatabasePath toPath:databasePath error:NULL]; [[NSFileManager defaultManager] moveItemAtPath:legacyDatabasePath
toPath:databasePath
error:NULL];
} }
// Move the offline database from v3.2.x path to a subdirectory that can // Move the offline database from v3.2.x path to a subdirectory that can
// be reliably excluded from backups. // be reliably excluded from backups.
if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) { if (![[NSFileManager defaultManager] fileExistsAtPath:databasePath]) {
NSURL *subdirectorylessDatabaseURL = [[self class] defaultDatabaseURLIncludingSubdirectory:NO]; NSURL *subdirectorylessDatabaseURL =
[[NSFileManager defaultManager] moveItemAtPath:subdirectorylessDatabaseURL.path toPath:databasePath error:NULL]; [[self class] defaultDatabaseURLIncludingSubdirectory:NO];
[[NSFileManager defaultManager] moveItemAtPath:subdirectorylessDatabaseURL.path
toPath:databasePath
error:NULL];
} }
} }
return _databaseURL; return _databaseURL;
@@ -269,7 +303,8 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
~/Library/Application Support/tld.app.bundle.id/cache.db ~/Library/Application Support/tld.app.bundle.id/cache.db
*/ */
+ (NSURL *)defaultDatabaseURLIncludingSubdirectory:(BOOL)useSubdirectory { + (NSURL *)defaultDatabaseURLIncludingSubdirectory:(BOOL)useSubdirectory {
NSURL *databaseDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory NSURL *databaseDirectoryURL =
[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask inDomain:NSUserDomainMask
appropriateForURL:nil appropriateForURL:nil
create:YES create:YES
@@ -304,44 +339,54 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
+ (NSString *)legacyDatabasePath { + (NSString *)legacyDatabasePath {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
// ~/Documents/offline.db // ~/Documents/offline.db
NSArray *legacyPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSArray *legacyPaths =
NSString *legacyDatabasePath = [legacyPaths.firstObject stringByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *legacyDatabasePath = [legacyPaths.firstObject
stringByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1];
#elif TARGET_OS_MAC #elif TARGET_OS_MAC
// ~/Library/Caches/tld.app.bundle.id/offline.db // ~/Library/Caches/tld.app.bundle.id/offline.db
NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier];
NSURL *legacyDatabaseDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory NSURL *legacyDatabaseDirectoryURL =
[[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory
inDomain:NSUserDomainMask inDomain:NSUserDomainMask
appropriateForURL:nil appropriateForURL:nil
create:NO create:NO
error:nil]; error:nil];
legacyDatabaseDirectoryURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; legacyDatabaseDirectoryURL =
NSURL *legacyDatabaseURL = [legacyDatabaseDirectoryURL URLByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1]; [legacyDatabaseDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
NSURL *legacyDatabaseURL = [legacyDatabaseDirectoryURL
URLByAppendingPathComponent:MLNOfflineStorageFileName3_2_0_beta_1];
NSString *legacyDatabasePath = legacyDatabaseURL ? legacyDatabaseURL.path : @""; NSString *legacyDatabasePath = legacyDatabaseURL ? legacyDatabaseURL.path : @"";
#endif #endif
return legacyDatabasePath; return legacyDatabasePath;
} }
- (void)addContentsOfFile:(NSString *)filePath withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { - (void)addContentsOfFile:(NSString *)filePath
withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion {
MLNLogDebug(@"Adding contentsOfFile: %@ completionHandler: %@", filePath, completion); MLNLogDebug(@"Adding contentsOfFile: %@ completionHandler: %@", filePath, completion);
NSURL *fileURL = [NSURL fileURLWithPath:filePath]; NSURL *fileURL = [NSURL fileURLWithPath:filePath];
[self addContentsOfURL:fileURL withCompletionHandler:completion]; [self addContentsOfURL:fileURL withCompletionHandler:completion];
} }
- (void)addContentsOfURL:(NSURL *)fileURL withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion { - (void)addContentsOfURL:(NSURL *)fileURL
withCompletionHandler:(MLNBatchedOfflinePackAdditionCompletionHandler)completion {
MLNLogDebug(@"Adding contentsOfURL: %@ completionHandler: %@", fileURL, completion); MLNLogDebug(@"Adding contentsOfURL: %@ completionHandler: %@", fileURL, completion);
NSFileManager *fileManager = [NSFileManager defaultManager]; NSFileManager *fileManager = [NSFileManager defaultManager];
if (!fileURL.isFileURL) { if (!fileURL.isFileURL) {
[NSException raise:NSInvalidArgumentException format:@"%@ must be a valid file path", fileURL.absoluteString]; [NSException raise:NSInvalidArgumentException
format:@"%@ must be a valid file path", fileURL.absoluteString];
} }
if (![fileManager isWritableFileAtPath:fileURL.path]) { if (![fileManager isWritableFileAtPath:fileURL.path]) {
[NSException raise:NSInvalidArgumentException format:@"The file path: %@ must be writable", fileURL.absoluteString]; [NSException raise:NSInvalidArgumentException
format:@"The file path: %@ must be writable", fileURL.absoluteString];
} }
__weak MLNOfflineStorage *weakSelf = self; __weak MLNOfflineStorage *weakSelf = self;
[self _addContentsOfFile:fileURL.path withCompletionHandler:^(NSArray<MLNOfflinePack *> * _Nullable packs, NSError * _Nullable error) { [self _addContentsOfFile:fileURL.path
withCompletionHandler:^(NSArray<MLNOfflinePack *> *_Nullable packs,
NSError *_Nullable error) {
if (packs) { if (packs) {
NSMutableDictionary *packsByIdentifier = [NSMutableDictionary dictionary]; NSMutableDictionary *packsByIdentifier = [NSMutableDictionary dictionary];
@@ -353,16 +398,17 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
id mutablePacks = [strongSelf mutableArrayValueForKey:@"packs"]; id mutablePacks = [strongSelf mutableArrayValueForKey:@"packs"];
NSMutableIndexSet *replaceIndexSet = [NSMutableIndexSet indexSet]; NSMutableIndexSet *replaceIndexSet = [NSMutableIndexSet indexSet];
NSMutableArray *replacePacksArray = [NSMutableArray array]; NSMutableArray *replacePacksArray = [NSMutableArray array];
[strongSelf.packs enumerateObjectsUsingBlock:^(MLNOfflinePack * _Nonnull pack, NSUInteger idx, BOOL * _Nonnull stop) { [strongSelf.packs enumerateObjectsUsingBlock:^(MLNOfflinePack *_Nonnull pack,
NSUInteger idx, BOOL *_Nonnull stop) {
MLNOfflinePack *newPack = packsByIdentifier[@(pack.mbglOfflineRegion->getID())]; MLNOfflinePack *newPack = packsByIdentifier[@(pack.mbglOfflineRegion->getID())];
if (newPack) { if (newPack) {
MLNOfflinePack *previousPack = [mutablePacks objectAtIndex:idx]; MLNOfflinePack *previousPack = [mutablePacks objectAtIndex:idx];
[previousPack invalidate]; [previousPack invalidate];
[replaceIndexSet addIndex:idx]; [replaceIndexSet addIndex:idx];
[replacePacksArray addObject:[packsByIdentifier objectForKey:@(newPack.mbglOfflineRegion->getID())]]; [replacePacksArray
addObject:[packsByIdentifier objectForKey:@(newPack.mbglOfflineRegion->getID())]];
[packsByIdentifier removeObjectForKey:@(newPack.mbglOfflineRegion->getID())]; [packsByIdentifier removeObjectForKey:@(newPack.mbglOfflineRegion->getID())];
} }
}]; }];
if (replaceIndexSet.count > 0) { if (replaceIndexSet.count > 0) {
@@ -377,39 +423,55 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
}]; }];
} }
- (void)_addContentsOfFile:(NSString *)filePath withCompletionHandler:(void (^)(NSArray<MLNOfflinePack *> * _Nullable packs, NSError * _Nullable error))completion { - (void)_addContentsOfFile:(NSString *)filePath
_mbglDatabaseFileSource->mergeOfflineRegions(std::string(static_cast<const char *>([filePath UTF8String])), [&, completion, filePath](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) { withCompletionHandler:(void (^)(NSArray<MLNOfflinePack *> *_Nullable packs,
NSError *_Nullable error))completion {
_mbglDatabaseFileSource->mergeOfflineRegions(
std::string(static_cast<const char *>([filePath UTF8String])),
[&, completion, filePath](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) {
NSError *error; NSError *error;
NSMutableArray *packs; NSMutableArray *packs;
if (!result) { 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]; NSString *description =
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed [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:@{ userInfo:@{
NSLocalizedDescriptionKey: description, NSLocalizedDescriptionKey : description,
NSLocalizedFailureReasonErrorKey: @(mbgl::util::toString(result.error()).c_str()) NSLocalizedFailureReasonErrorKey :
@(mbgl::util::toString(result.error()).c_str())
}]; }];
} else { } else {
auto& regions = result.value(); auto &regions = result.value();
packs = [NSMutableArray arrayWithCapacity:regions.size()]; packs = [NSMutableArray arrayWithCapacity:regions.size()];
for (auto &region : regions) { for (auto &region : regions) {
MLNOfflinePack *pack = [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; MLNOfflinePack *pack = [[MLNOfflinePack alloc]
initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))];
[packs addObject:pack]; [packs addObject:pack];
} }
} }
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) { dispatch_async(dispatch_get_main_queue(),
completion(packs, error); [&, completion, error, packs](void) { completion(packs, error); });
});
} }
}); });
} }
// MARK: Pack management methods // MARK: Pack management methods
- (void)addPackForRegion:(id <MLNOfflineRegion>)region withContext:(NSData *)context completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { - (void)addPackForRegion:(id<MLNOfflineRegion>)region
MLNLogDebug(@"Adding packForRegion: %@ contextLength: %lu completionHandler: %@", region, (unsigned long)context.length, completion); withContext:(NSData *)context
completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion {
MLNLogDebug(@"Adding packForRegion: %@ contextLength: %lu completionHandler: %@", region,
(unsigned long)context.length, completion);
__weak MLNOfflineStorage *weakSelf = self; __weak MLNOfflineStorage *weakSelf = self;
[self _addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { [self _addPackForRegion:region
withContext:context
completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) {
pack.state = MLNOfflinePackStateInactive; pack.state = MLNOfflinePackStateInactive;
MLNOfflineStorage *strongSelf = weakSelf; MLNOfflineStorage *strongSelf = weakSelf;
[[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack]; [[strongSelf mutableArrayValueForKey:@"packs"] addObject:pack];
@@ -419,44 +481,58 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
}]; }];
} }
- (void)_addPackForRegion:(id <MLNOfflineRegion>)region withContext:(NSData *)context completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion { - (void)_addPackForRegion:(id<MLNOfflineRegion>)region
withContext:(NSData *)context
completionHandler:(MLNOfflinePackAdditionCompletionHandler)completion {
if (![region conformsToProtocol:@protocol(MLNOfflineRegion_Private)]) { if (![region conformsToProtocol:@protocol(MLNOfflineRegion_Private)]) {
[NSException raise:MLNUnsupportedRegionTypeException [NSException raise:MLNUnsupportedRegionTypeException
format:@"Regions of type %@ are unsupported.", NSStringFromClass([region class])]; format:@"Regions of type %@ are unsupported.", NSStringFromClass([region class])];
return; return;
} }
const mbgl::OfflineRegionDefinition regionDefinition = [(id <MLNOfflineRegion_Private>)region offlineRegionDefinition]; const mbgl::OfflineRegionDefinition regionDefinition =
[(id<MLNOfflineRegion_Private>)region offlineRegionDefinition];
mbgl::OfflineRegionMetadata metadata(context.length); mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()]; [context getBytes:&metadata[0] length:metadata.size()];
_mbglDatabaseFileSource->createOfflineRegion(regionDefinition, metadata, [&, completion](mbgl::expected<mbgl::OfflineRegion, std::exception_ptr> mbglOfflineRegion) { _mbglDatabaseFileSource->createOfflineRegion(
regionDefinition, metadata,
[&, completion](mbgl::expected<mbgl::OfflineRegion, std::exception_ptr> mbglOfflineRegion) {
NSError *error; NSError *error;
if (!mbglOfflineRegion) { if (!mbglOfflineRegion) {
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str()); NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegion.error()).c_str());
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{ error = [NSError errorWithDomain:MLNErrorDomain
NSLocalizedDescriptionKey: errorDescription, code:MLNErrorCodeModifyingOfflineStorageFailed
} : nil]; userInfo:errorDescription ? @{
NSLocalizedDescriptionKey : errorDescription,
}
: nil];
} }
if (completion) { if (completion) {
MLNOfflinePack *pack = mbglOfflineRegion ? [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(mbglOfflineRegion.value()))] : nil; MLNOfflinePack *pack =
dispatch_async(dispatch_get_main_queue(), [&, completion, error, pack](void) { mbglOfflineRegion
completion(pack, error); ? [[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 { - (void)removePack:(MLNOfflinePack *)pack
withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion {
MLNLogDebug(@"Removing pack: %@ completionHandler: %@", pack, completion); MLNLogDebug(@"Removing pack: %@ completionHandler: %@", pack, completion);
[[self mutableArrayValueForKey:@"packs"] removeObject:pack]; [[self mutableArrayValueForKey:@"packs"] removeObject:pack];
[self _removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [self _removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
if (completion) { if (completion) {
completion(error); completion(error);
} }
}]; }];
} }
- (void)_removePack:(MLNOfflinePack *)pack withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion { - (void)_removePack:(MLNOfflinePack *)pack
withCompletionHandler:(MLNOfflinePackRemovalCompletionHandler)completion {
mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion; mbgl::OfflineRegion *mbglOfflineRegion = pack.mbglOfflineRegion;
[pack invalidate]; [pack invalidate];
@@ -467,24 +543,27 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
return; return;
} }
_mbglDatabaseFileSource->deleteOfflineRegion(std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) { _mbglDatabaseFileSource->deleteOfflineRegion(
std::move(*mbglOfflineRegion), [&, completion](std::exception_ptr exception) {
NSError *error; NSError *error;
if (exception) { if (exception) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { dispatch_async(dispatch_get_main_queue(),
completion(error); [&, completion, error](void) { completion(error); });
});
} }
}); });
} }
- (void)invalidatePack:(MLNOfflinePack *)pack withCompletionHandler:(void (^)(NSError * _Nullable))completion { - (void)invalidatePack:(MLNOfflinePack *)pack
mbgl::OfflineRegion& region = *pack.mbglOfflineRegion; withCompletionHandler:(void (^)(NSError *_Nullable))completion {
mbgl::OfflineRegion &region = *pack.mbglOfflineRegion;
NSError *error; NSError *error;
if (!pack.mbglOfflineRegion) { if (!pack.mbglOfflineRegion) {
completion(nil); completion(nil);
@@ -493,21 +572,23 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
_mbglDatabaseFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) { _mbglDatabaseFileSource->invalidateOfflineRegion(region, [&](std::exception_ptr exception) {
if (exception) { if (exception) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ error =
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
}); });
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { completion(error); });
completion(error);
});
} }
} }
- (void)reloadPacks { - (void)reloadPacks {
MLNLogInfo(@"Reloading packs."); MLNLogInfo(@"Reloading packs.");
[self getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs, __unused NSError * _Nullable error) { [self getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs,
__unused NSError *_Nullable error) {
for (MLNOfflinePack *pack in self.packs) { for (MLNOfflinePack *pack in self.packs) {
[pack invalidate]; [pack invalidate];
} }
@@ -515,26 +596,31 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
}]; }];
} }
- (void)getPacksWithCompletionHandler:(void (^)(NSArray<MLNOfflinePack *> *packs, NSError * _Nullable error))completion { - (void)getPacksWithCompletionHandler:(void (^)(NSArray<MLNOfflinePack *> *packs,
_mbglDatabaseFileSource->listOfflineRegions([&, completion](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) { NSError *_Nullable error))completion {
_mbglDatabaseFileSource->listOfflineRegions(
[&, completion](mbgl::expected<mbgl::OfflineRegions, std::exception_ptr> result) {
NSError *error; NSError *error;
NSMutableArray *packs; NSMutableArray *packs;
if (!result) { if (!result) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(result.error()).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeUnknown
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(result.error()).c_str()),
}]; }];
} else { } else {
auto& regions = result.value(); auto &regions = result.value();
packs = [NSMutableArray arrayWithCapacity:regions.size()]; packs = [NSMutableArray arrayWithCapacity:regions.size()];
for (auto &region : regions) { for (auto &region : regions) {
MLNOfflinePack *pack = [[MLNOfflinePack alloc] initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))]; MLNOfflinePack *pack = [[MLNOfflinePack alloc]
initWithMBGLRegion:new mbgl::OfflineRegion(std::move(region))];
[packs addObject:pack]; [packs addObject:pack];
} }
} }
if (completion) { if (completion) {
dispatch_async(dispatch_get_main_queue(), [&, completion, error, packs](void) { dispatch_async(dispatch_get_main_queue(),
completion(packs, error); [&, completion, error, packs](void) { completion(packs, error); });
});
} }
}); });
} }
@@ -546,16 +632,21 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
// MARK: - Ambient cache management // MARK: - Ambient cache management
- (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize withCompletionHandler:(void (^)(NSError * _Nullable))completion { - (void)setMaximumAmbientCacheSize:(NSUInteger)cacheSize
_mbglDatabaseFileSource->setMaximumAmbientCacheSize(cacheSize, [&, completion](std::exception_ptr exception) { withCompletionHandler:(void (^)(NSError *_Nullable))completion {
_mbglDatabaseFileSource->setMaximumAmbientCacheSize(
cacheSize, [&, completion](std::exception_ptr exception) {
NSError *error; NSError *error;
if (completion) { if (completion) {
if (exception) { if (exception) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
dispatch_sync(dispatch_get_main_queue(), ^ { dispatch_sync(dispatch_get_main_queue(), ^{
completion(error); completion(error);
}); });
} }
@@ -563,16 +654,19 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
} }
- (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable))completion { - (void)invalidateAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable))completion {
_mbglDatabaseFileSource->invalidateAmbientCache([&, completion](std::exception_ptr exception){ _mbglDatabaseFileSource->invalidateAmbientCache([&, completion](std::exception_ptr exception) {
NSError *error; NSError *error;
if (completion) { if (completion) {
if (exception) { if (exception) {
// Convert std::exception_ptr to an NSError. // Convert std::exception_ptr to an NSError.
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
dispatch_async(dispatch_get_main_queue(), ^ { dispatch_async(dispatch_get_main_queue(), ^{
completion(error); completion(error);
}); });
} }
@@ -580,17 +674,19 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
} }
- (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion { - (void)clearAmbientCacheWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
_mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception){ _mbglDatabaseFileSource->clearAmbientCache([&, completion](std::exception_ptr exception) {
NSError *error; NSError *error;
if (completion) { if (completion) {
if (exception) { if (exception) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
dispatch_async(dispatch_get_main_queue(), [&, completion, error](void) { dispatch_async(dispatch_get_main_queue(),
completion(error); [&, completion, error](void) { completion(error); });
});
} }
}); });
} }
@@ -600,8 +696,11 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
NSError *error; NSError *error;
if (completion) { if (completion) {
if (exception) { if (exception) {
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ error = [NSError
NSLocalizedDescriptionKey: @(mbgl::util::toString(exception).c_str()), errorWithDomain:MLNErrorDomain
code:MLNErrorCodeUnknown
userInfo:@{
NSLocalizedDescriptionKey : @(mbgl::util::toString(exception).c_str()),
}]; }];
} }
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -613,19 +712,36 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
// MARK: - // MARK: -
- (unsigned long long)countOfBytesCompleted { - (unsigned long long)countOfBytesCompleted {
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.databasePath error:NULL]; NSDictionary *attributes =
[[NSFileManager defaultManager] attributesOfItemAtPath:self.databasePath error:NULL];
return attributes.fileSize; 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 { - (void)preloadData:(NSData *)data
[self preloadData:data forURL:url modificationDate:modified expirationDate:expires eTag:eTag mustRevalidate:mustRevalidate completionHandler:nil]; 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 { completionHandler:(nullable MLNOfflinePreloadDataCompletionHandler)completion {
mbgl::Resource resource(mbgl::Resource::Kind::Unknown, url.absoluteString.UTF8String); mbgl::Resource resource(mbgl::Resource::Kind::Unknown, url.absoluteString.UTF8String);
mbgl::Response response; mbgl::Response response;
response.data = std::make_shared<std::string>(static_cast<const char*>(data.bytes), data.length); response.data = std::make_shared<std::string>(static_cast<const char *>(data.bytes), data.length);
response.mustRevalidate = mustRevalidate; response.mustRevalidate = mustRevalidate;
if (eTag) { if (eTag) {
@@ -633,11 +749,15 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
} }
if (modified) { if (modified) {
response.modified = mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(MLNDurationFromTimeInterval(modified.timeIntervalSince1970)); response.modified =
mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(
MLNDurationFromTimeInterval(modified.timeIntervalSince1970));
} }
if (expires) { if (expires) {
response.expires = mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(MLNDurationFromTimeInterval(expires.timeIntervalSince1970)); response.expires =
mbgl::Timestamp() + std::chrono::duration_cast<mbgl::Seconds>(
MLNDurationFromTimeInterval(expires.timeIntervalSince1970));
} }
std::function<void()> callback; std::function<void()> callback;
@@ -651,8 +771,18 @@ const MLNExceptionName MLNUnsupportedRegionTypeException = @"MLNUnsupportedRegio
_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 { - (void)putResourceWithUrl:(NSURL *)url
[self preloadData:data forURL:url modificationDate:modified expirationDate:expires eTag:etag mustRevalidate:mustRevalidate]; 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 @end

View File

@@ -2,7 +2,7 @@
@implementation MLNPluginLayerProperty @implementation MLNPluginLayerProperty
+(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName + (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
propertyType:(MLNPluginLayerPropertyType)propertyType propertyType:(MLNPluginLayerPropertyType)propertyType
defaultValue:(id)defaultValue { defaultValue:(id)defaultValue {
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init]; MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
@@ -26,8 +26,7 @@
return tempResult; return tempResult;
} }
- (id)init {
-(id)init {
// Base class implemenation // Base class implemenation
if (self = [super init]) { if (self = [super init]) {
// Default setup // Default setup
@@ -45,12 +44,10 @@
@implementation MLNPluginLayer @implementation MLNPluginLayer
// This is the layer type in the style that is used // This is the layer type in the style that is used
+(MLNPluginLayerCapabilities *)layerCapabilities { + (MLNPluginLayerCapabilities *)layerCapabilities {
// Base class returns the class name just to return something // 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 // TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
return nil; return nil;
} }
- (void)onRenderLayer:(MLNMapView *)mapView - (void)onRenderLayer:(MLNMapView *)mapView
@@ -62,7 +59,7 @@
// Base class does nothing // Base class does nothing
} }
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// Base class does nothing // Base class does nothing
} }
@@ -71,10 +68,7 @@
// Base class does nothing // Base class does nothing
} }
@end @end
@implementation MLNPluginLayerCapabilities @implementation MLNPluginLayerCapabilities
@end @end

View File

@@ -1,20 +1,17 @@
#import "MLNPluginStyleLayer.h" #import "MLNPluginStyleLayer.h"
#import "MLNPluginStyleLayer_Private.h"
#import <mbgl/plugin/plugin_layer.hpp> #import <mbgl/plugin/plugin_layer.hpp>
#import <mbgl/plugin/plugin_layer_impl.hpp> #import <mbgl/plugin/plugin_layer_impl.hpp>
#import "MLNPluginLayer.h" #import "MLNPluginLayer.h"
#import "MLNPluginStyleLayer_Private.h"
@implementation MLNPluginStyleLayer @implementation MLNPluginStyleLayer
-(void)getStats { - (void)getStats {
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
auto pl = l->impl(); auto pl = l->impl();
} }
-(MLNPluginLayer *)pluginLayer { - (MLNPluginLayer *)pluginLayer {
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
if (l->_platformReference) { if (l->_platformReference) {
MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference; MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference;
@@ -22,14 +19,10 @@
} }
return nil; return nil;
} }
@end @end
MLNStyleLayer *mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) {
MLNStyleLayer* mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) {
return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer]; return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer];
} }

View File

@@ -1,23 +1,20 @@
#import "MLNPointAnnotation.h" #import "MLNPointAnnotation.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNShape_Private.h" #import "MLNShape_Private.h"
#import "NSCoder+MLNAdditions.h" #import "NSCoder+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#import <mbgl/util/geometry.hpp> #import <mbgl/util/geometry.hpp>
@implementation MLNPointAnnotation @implementation MLNPointAnnotation
@synthesize coordinate; @synthesize coordinate;
+ (BOOL)supportsSecureCoding + (BOOL)supportsSecureCoding {
{
return YES; return YES;
} }
- (instancetype)initWithCoder:(NSCoder *)coder - (instancetype)initWithCoder:(NSCoder *)coder {
{
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:coder]) { if (self = [super initWithCoder:coder]) {
self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"]; self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"];
@@ -25,46 +22,44 @@
return self; return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder encodeMLNCoordinate:coordinate forKey:@"coordinate"]; [coder encodeMLNCoordinate:coordinate forKey:@"coordinate"];
} }
- (BOOL)isEqual:(id)other - (BOOL)isEqual:(id)other {
{
if (other == self) return YES; if (other == self) return YES;
if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO; if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO;
MLNPointAnnotation *otherAnnotation = other; MLNPointAnnotation *otherAnnotation = other;
return ([super isEqual:other] return ([super isEqual:other] &&
&& self.coordinate.latitude == otherAnnotation.coordinate.latitude self.coordinate.latitude == otherAnnotation.coordinate.latitude &&
&& self.coordinate.longitude == otherAnnotation.coordinate.longitude); self.coordinate.longitude == otherAnnotation.coordinate.longitude);
} }
- (NSUInteger)hash - (NSUInteger)hash {
{
return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash; return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash;
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>", stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
: self.subtitle,
coordinate.latitude, coordinate.longitude]; coordinate.latitude, coordinate.longitude];
} }
- (NSDictionary *)geoJSONDictionary - (NSDictionary *)geoJSONDictionary {
{ return @{
return @{@"type": @"Point", @"type" : @"Point",
@"coordinates": @[@(self.coordinate.longitude), @(self.coordinate.latitude)]}; @"coordinates" : @[ @(self.coordinate.longitude), @(self.coordinate.latitude) ]
};
} }
- (mbgl::Geometry<double>)geometryObject - (mbgl::Geometry<double>)geometryObject {
{ mbgl::Point<double> point = {self.coordinate.longitude, self.coordinate.latitude};
mbgl::Point<double> point = { self.coordinate.longitude, self.coordinate.latitude };
return point; return point;
} }

View File

@@ -1,15 +1,14 @@
#import "MLNPointCollection_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "NSArray+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNPointCollection_Private.h"
#import "NSArray+MLNAdditions.h"
#import <mbgl/util/geojson.hpp> #import <mbgl/util/geojson.hpp>
#import <mbgl/util/geometry.hpp> #import <mbgl/util/geometry.hpp>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@implementation MLNPointCollection @implementation MLNPointCollection {
{
std::optional<mbgl::LatLngBounds> _bounds; std::optional<mbgl::LatLngBounds> _bounds;
std::vector<CLLocationCoordinate2D> _coordinates; std::vector<CLLocationCoordinate2D> _coordinates;
} }
@@ -18,18 +17,16 @@ NS_ASSUME_NONNULL_BEGIN
return YES; return YES;
} }
+ (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count + (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords
{ count:(NSUInteger)count {
return [[self alloc] initWithCoordinates:coords count:count]; return [[self alloc] initWithCoordinates:coords count:count];
} }
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
{
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count); MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count);
self = [super init]; self = [super init];
if (self) if (self) {
{ _coordinates = {coords, coords + count};
_coordinates = { coords, coords + count };
} }
return self; return self;
} }
@@ -37,7 +34,8 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable instancetype)initWithCoder:(NSCoder *)decoder { - (nullable instancetype)initWithCoder:(NSCoder *)decoder {
MLNLogInfo(@"Initializing with coder.O"); MLNLogInfo(@"Initializing with coder.O");
if (self = [super initWithCoder:decoder]) { if (self = [super initWithCoder:decoder]) {
NSSet<Class> *coordinatesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class]]]; NSSet<Class> *coordinatesClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class] ]];
NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"]; NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"];
_coordinates = [coordinates mgl_coordinates]; _coordinates = [coordinates mgl_coordinates];
} }
@@ -54,8 +52,9 @@ NS_ASSUME_NONNULL_BEGIN
if (![other isKindOfClass:[MLNPointCollection class]]) return NO; if (![other isKindOfClass:[MLNPointCollection class]]) return NO;
MLNPointCollection *otherCollection = (MLNPointCollection *)other; MLNPointCollection *otherCollection = (MLNPointCollection *)other;
return ([super isEqual:other] return ([super isEqual:other] &&
&& ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) || [[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]])); ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) ||
[[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]]));
} }
- (MLNCoordinateBounds)overlayBounds { - (MLNCoordinateBounds)overlayBounds {
@@ -73,66 +72,59 @@ NS_ASSUME_NONNULL_BEGIN
return MLNCoordinateBoundsFromLatLngBounds(*_bounds); return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
} }
- (NSUInteger)pointCount - (NSUInteger)pointCount {
{
return _coordinates.size(); return _coordinates.size();
} }
- (CLLocationCoordinate2D *)coordinates - (CLLocationCoordinate2D *)coordinates {
{
return _coordinates.data(); return _coordinates.data();
} }
- (CLLocationCoordinate2D)coordinate - (CLLocationCoordinate2D)coordinate {
{
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates"); MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
return _coordinates.at(0); return _coordinates.at(0);
} }
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range - (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range {
{ if (range.location + range.length > [self pointCount]) {
if (range.location + range.length > [self pointCount]) [NSException
{ raise:NSRangeException
[NSException raise:NSRangeException
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld", format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
NSStringFromRange(range), (unsigned long)[self pointCount]]; 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 - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
{
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds); return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
} }
- (mbgl::Geometry<double>)geometryObject - (mbgl::Geometry<double>)geometryObject {
{
mbgl::MultiPoint<double> multiPoint; mbgl::MultiPoint<double> multiPoint;
multiPoint.reserve(self.pointCount); multiPoint.reserve(self.pointCount);
for (NSUInteger i = 0; i < self.pointCount; i++) for (NSUInteger i = 0; i < self.pointCount; i++) {
{ multiPoint.push_back(
multiPoint.push_back(mbgl::Point<double>(self.coordinates[i].longitude, self.coordinates[i].latitude)); mbgl::Point<double>(self.coordinates[i].longitude, self.coordinates[i].latitude));
} }
return multiPoint; return multiPoint;
} }
- (NSDictionary *)geoJSONDictionary - (NSDictionary *)geoJSONDictionary {
{
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
for (NSUInteger index = 0; index < self.pointCount; index++) { for (NSUInteger index = 0; index < self.pointCount; index++) {
CLLocationCoordinate2D coordinate = self.coordinates[index]; CLLocationCoordinate2D coordinate = self.coordinates[index];
[coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; [coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
} }
return @{@"type": @"MultiPoint", return @{@"type" : @"MultiPoint", @"coordinates" : coordinates};
@"coordinates": coordinates};
} }
- (NSString *)description - (NSString *)description {
{
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>", return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], NSStringFromClass([self class]), (void *)self,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }

View File

@@ -1,13 +1,13 @@
#import "MLNPolygon_Private.h" #import "MLNPolygon_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNFeature.h" #import "MLNFeature.h"
#import <mbgl/util/geojson.hpp>
#import <mapbox/polylabel.hpp> #import <mapbox/polylabel.hpp>
#import <mbgl/util/geojson.hpp>
@implementation MLNPolygon @implementation MLNPolygon
@@ -17,16 +17,22 @@
return YES; return YES;
} }
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { + (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords
count:(NSUInteger)count {
return [self polygonWithCoordinates:coords count:count interiorPolygons:nil]; return [self polygonWithCoordinates:coords count:count interiorPolygons:nil];
} }
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons { + (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords
count:(NSUInteger)count
interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons]; return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons];
} }
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons { - (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords
MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count, (unsigned long)interiorPolygons); count:(NSUInteger)count
interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count,
(unsigned long)interiorPolygons);
if (self = [super initWithCoordinates:coords count:count]) { if (self = [super initWithCoordinates:coords count:count]) {
if (interiorPolygons.count) { if (interiorPolygons.count) {
_interiorPolygons = interiorPolygons; _interiorPolygons = interiorPolygons;
@@ -39,7 +45,8 @@
MLNLogInfo(@"Initializng with coder."); MLNLogInfo(@"Initializng with coder.");
self = [super initWithCoder:decoder]; self = [super initWithCoder:decoder];
if (self) { if (self) {
NSSet<Class> *polygonClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]]; NSSet<Class> *polygonClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
_interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"]; _interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"];
} }
return self; return self;
@@ -95,19 +102,17 @@
return [self polygon]; return [self polygon];
} }
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate { - (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
mbgl::FillAnnotation annotation{[self polygon]};
mbgl::FillAnnotation annotation { [self polygon] }; annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) }; annotation.outlineColor = {[delegate strokeColorForShapeAnnotation:self]};
annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] }; annotation.color = {[delegate fillColorForPolygonAnnotation:self]};
annotation.color = { [delegate fillColorForPolygonAnnotation:self] };
return annotation; return annotation;
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return @{@"type": @"Polygon", return @{@"type" : @"Polygon", @"coordinates" : self.mgl_coordinates};
@"coordinates": self.mgl_coordinates};
} }
- (NSArray<id> *)mgl_coordinates { - (NSArray<id> *)mgl_coordinates {
@@ -116,7 +121,7 @@
NSMutableArray *exteriorRing = [NSMutableArray array]; NSMutableArray *exteriorRing = [NSMutableArray array];
for (NSUInteger index = 0; index < self.pointCount; index++) { for (NSUInteger index = 0; index < self.pointCount; index++) {
CLLocationCoordinate2D coordinate = self.coordinates[index]; CLLocationCoordinate2D coordinate = self.coordinates[index];
[exteriorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; [exteriorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
} }
[coordinates addObject:exteriorRing]; [coordinates addObject:exteriorRing];
@@ -124,7 +129,7 @@
NSMutableArray *interiorRing = [NSMutableArray array]; NSMutableArray *interiorRing = [NSMutableArray array];
for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) { for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) {
CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index]; CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index];
[interiorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; [interiorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
} }
[coordinates addObject:interiorRing]; [coordinates addObject:interiorRing];
} }
@@ -172,7 +177,8 @@
- (instancetype)initWithCoder:(NSCoder *)decoder { - (instancetype)initWithCoder:(NSCoder *)decoder {
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) { if (self = [super initWithCoder:decoder]) {
NSSet<Class> *polygonsClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]]; NSSet<Class> *polygonsClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
_polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"]; _polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"];
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
@@ -195,8 +201,7 @@
if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO; if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO;
MLNMultiPolygon *otherMultiPolygon = other; MLNMultiPolygon *otherMultiPolygon = other;
return [super isEqual:other] return [super isEqual:other] && [self.polygons isEqualToArray:otherMultiPolygon.polygons];
&& [self.polygons isEqualToArray:otherMultiPolygon.polygons];
} }
- (NSUInteger)hash { - (NSUInteger)hash {
@@ -238,18 +243,18 @@
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count]; NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count];
for (MLNPolygonFeature *feature in self.polygons) { for (MLNPolygonFeature *feature in self.polygons) {
[coordinates addObject: feature.mgl_coordinates]; [coordinates addObject:feature.mgl_coordinates];
} }
return @{@"type": @"MultiPolygon", return @{@"type" : @"MultiPolygon", @"coordinates" : coordinates};
@"coordinates": coordinates};
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
: self.subtitle,
(unsigned long)self.polygons.count, (unsigned long)self.polygons.count,
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }

View File

@@ -1,26 +1,24 @@
#import "MLNPolyline_Private.h" #import "MLNPolyline_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNFeature.h" #import "MLNFeature.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import <mbgl/util/geojson.hpp>
#import <mapbox/polylabel.hpp> #import <mapbox/polylabel.hpp>
#import <mbgl/util/geojson.hpp>
@implementation MLNPolyline @implementation MLNPolyline
@dynamic overlayBounds; @dynamic overlayBounds;
+ (BOOL)supportsSecureCoding + (BOOL)supportsSecureCoding {
{
return YES; return YES;
} }
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords + (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords
count:(NSUInteger)count count:(NSUInteger)count {
{
return [[self alloc] initWithCoordinates:coords count:count]; return [[self alloc] initWithCoordinates:coords count:count];
} }
@@ -37,11 +35,11 @@
return geometry; return geometry;
} }
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate { - (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
mbgl::LineAnnotation annotation { [self lineString] }; mbgl::LineAnnotation annotation{[self lineString]};
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) }; annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
annotation.color = { [delegate strokeColorForShapeAnnotation:self] }; annotation.color = {[delegate strokeColorForShapeAnnotation:self]};
annotation.width = { static_cast<float>([delegate lineWidthForPolylineAnnotation:self]) }; annotation.width = {static_cast<float>([delegate lineWidthForPolylineAnnotation:self])};
return annotation; return annotation;
} }
@@ -51,15 +49,14 @@
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return @{@"type": @"LineString", return @{@"type" : @"LineString", @"coordinates" : self.mgl_coordinates};
@"coordinates": self.mgl_coordinates};
} }
- (NSArray<id> *)mgl_coordinates { - (NSArray<id> *)mgl_coordinates {
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount]; NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
for (NSUInteger index = 0; index < self.pointCount; index++) { for (NSUInteger index = 0; index < self.pointCount; index++) {
CLLocationCoordinate2D coordinate = self.coordinates[index]; CLLocationCoordinate2D coordinate = self.coordinates[index];
[coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]]; [coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
} }
return [coordinates copy]; return [coordinates copy];
} }
@@ -78,12 +75,12 @@
if (count > 1 || middle > traveled) { if (count > 1 || middle > traveled) {
for (NSUInteger i = 0; i < count; i++) { for (NSUInteger i = 0; i < count; i++) {
// Avoid a heap buffer overflow when there are only two coordinates. // Avoid a heap buffer overflow when there are only two coordinates.
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1; NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]); MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
MLNRadianCoordinate2D to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]); MLNRadianCoordinate2D to =
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
if (traveled >= middle) { if (traveled >= middle) {
double overshoot = middle - traveled; double overshoot = middle - traveled;
@@ -92,9 +89,8 @@
} }
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]); to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180; CLLocationDirection direction = [self direction:from to:to] - 180;
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(from, MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(
overshoot/mbgl::util::EARTH_RADIUS_M, from, overshoot / mbgl::util::EARTH_RADIUS_M, MLNRadiansFromDegrees(direction));
MLNRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude), return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
MLNDegreesFromRadians(otherCoordinate.longitude)); MLNDegreesFromRadians(otherCoordinate.longitude));
} }
@@ -106,22 +102,23 @@
return coordinates[count - 1]; return coordinates[count - 1];
} }
- (CLLocationDistance)length - (CLLocationDistance)length {
{
CLLocationDistance length = 0.0; CLLocationDistance length = 0.0;
NSUInteger count = self.pointCount; NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates; CLLocationCoordinate2D *coordinates = self.coordinates;
for (NSUInteger i = 0; i < count - 1; i++) { for (NSUInteger i = 0; i < count - 1; i++) {
length += (MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinateFromLocationCoordinate(coordinates[i]), MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * mbgl::util::EARTH_RADIUS_M); 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 - (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to {
{
return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to)); return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to));
} }
@@ -165,7 +162,8 @@
- (instancetype)initWithCoder:(NSCoder *)decoder { - (instancetype)initWithCoder:(NSCoder *)decoder {
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) { if (self = [super initWithCoder:decoder]) {
NSSet<Class> *polylinesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolyline class]]]; NSSet<Class> *polylinesClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolyline class] ]];
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"]; _polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
@@ -183,14 +181,13 @@
[coder encodeObject:_polylines forKey:@"polylines"]; [coder encodeObject:_polylines forKey:@"polylines"];
} }
- (BOOL)isEqual:(id)other - (BOOL)isEqual:(id)other {
{
if (self == other) return YES; if (self == other) return YES;
if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO; if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO;
MLNMultiPolyline *otherMultipoline = other; MLNMultiPolyline *otherMultipoline = other;
return ([super isEqual:otherMultipoline] return ([super isEqual:otherMultipoline] &&
&& [self.polylines isEqualToArray:otherMultipoline.polylines]); [self.polylines isEqualToArray:otherMultipoline.polylines]);
} }
- (NSUInteger)hash { - (NSUInteger)hash {
@@ -226,18 +223,18 @@
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
NSMutableArray *coordinates = [NSMutableArray array]; NSMutableArray *coordinates = [NSMutableArray array];
for (MLNPolylineFeature *feature in self.polylines) { for (MLNPolylineFeature *feature in self.polylines) {
[coordinates addObject: feature.mgl_coordinates]; [coordinates addObject:feature.mgl_coordinates];
} }
return @{@"type": @"MultiLineString", return @{@"type" : @"MultiLineString", @"coordinates" : coordinates};
@"coordinates": coordinates};
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self, NSStringFromClass([self class]), (void *)self,
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
: self.subtitle,
(unsigned long)self.polylines.count, (unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }

View File

@@ -9,10 +9,12 @@ const MLNTileSourceOption MLNTileSourceOptionDEMEncoding = @"MLNTileSourceOption
@implementation MLNRasterDEMSource @implementation MLNRasterDEMSource
- (std::unique_ptr<mbgl::style::RasterSource>)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset tileSize:(uint16_t)tileSize { - (std::unique_ptr<mbgl::style::RasterSource>)
auto source = std::make_unique<mbgl::style::RasterDEMSource>(identifier.UTF8String, pendingSourceWithIdentifier:(NSString *)identifier
urlOrTileset, urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset
tileSize); tileSize:(uint16_t)tileSize {
auto source =
std::make_unique<mbgl::style::RasterDEMSource>(identifier.UTF8String, urlOrTileset, tileSize);
return source; return source;
} }
@end @end

View File

@@ -22,31 +22,42 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512;
@implementation MLNRasterTileSource @implementation MLNRasterTileSource
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL { - (instancetype)initWithIdentifier:(NSString *)identifier
configurationURL:(NSURL *)configurationURL {
// The style specification default is 512, but 256 is the expected value for // 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 set that would be accessed through a mapbox: URL and therefore
// any tile URL that this option currently affects. // any tile URL that this option currently affects.
BOOL isMapboxURL = ([configurationURL.scheme isEqualToString:@"mapbox"] BOOL isMapboxURL =
&& [configurationURL.host containsString:@"."] ([configurationURL.scheme isEqualToString:@"mapbox"] &&
&& (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"])); [configurationURL.host containsString:@"."] &&
CGFloat tileSize = isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize; (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"]));
CGFloat tileSize =
isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize;
return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize]; return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL tileSize:(CGFloat)tileSize { - (instancetype)initWithIdentifier:(NSString *)identifier
configurationURL:(NSURL *)configurationURL
tileSize:(CGFloat)tileSize {
NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString; NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString;
auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:configurationURLString.UTF8String tileSize:uint16_t(round(tileSize))]; auto source = [self pendingSourceWithIdentifier:identifier
urlOrTileset:configurationURLString.UTF8String
tileSize:uint16_t(round(tileSize))];
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
} }
- (std::unique_ptr<mbgl::style::RasterSource>)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset tileSize:(uint16_t)tileSize { - (std::unique_ptr<mbgl::style::RasterSource>)
auto source = std::make_unique<mbgl::style::RasterSource>(identifier.UTF8String, pendingSourceWithIdentifier:(NSString *)identifier
urlOrTileset, urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset
tileSize); tileSize:(uint16_t)tileSize {
auto source =
std::make_unique<mbgl::style::RasterSource>(identifier.UTF8String, urlOrTileset, tileSize);
return source; return source;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates options:(nullable NSDictionary<MLNTileSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
uint16_t tileSize = MLNRasterTileSourceRetinaTileSize; uint16_t tileSize = MLNRasterTileSourceRetinaTileSize;
@@ -58,7 +69,9 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512;
tileSize = static_cast<uint16_t>(round(tileSizeNumber.doubleValue)); tileSize = static_cast<uint16_t>(round(tileSizeNumber.doubleValue));
} }
auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:tileSet tileSize:tileSize]; auto source = [self pendingSourceWithIdentifier:identifier
urlOrTileset:tileSet
tileSize:tileSize];
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
} }
@@ -74,7 +87,8 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512;
- (NSString *)attributionHTMLString { - (NSString *)attributionHTMLString {
if (!self.rawSource) { if (!self.rawSource) {
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
self.identifier);
return nil; return nil;
} }

View File

@@ -10,7 +10,7 @@
dependent way. dependent way.
*/ */
class MLNRenderFrontend : public mbgl::RendererFrontend { class MLNRenderFrontend : public mbgl::RendererFrontend {
public: public:
MLNRenderFrontend(std::unique_ptr<mbgl::Renderer> renderer_, MLNMapView* nativeView_, MLNRenderFrontend(std::unique_ptr<mbgl::Renderer> renderer_, MLNMapView* nativeView_,
mbgl::gfx::RendererBackend& mbglBackend_, bool async = false) mbgl::gfx::RendererBackend& mbglBackend_, bool async = false)
: renderer(std::move(renderer_)), nativeView(nativeView_), mbglBackend(mbglBackend_) { : renderer(std::move(renderer_)), nativeView(nativeView_), mbglBackend(mbglBackend_) {
@@ -73,7 +73,7 @@ class MLNRenderFrontend : public mbgl::RendererFrontend {
renderer->reduceMemoryUse(); renderer->reduceMemoryUse();
} }
private: private:
std::unique_ptr<mbgl::Renderer> renderer; std::unique_ptr<mbgl::Renderer> renderer;
__weak MLNMapView* nativeView = nullptr; __weak MLNMapView* nativeView = nullptr;
mbgl::gfx::RendererBackend& mbglBackend; mbgl::gfx::RendererBackend& mbglBackend;

View File

@@ -34,5 +34,4 @@
_stencilUpdates = stats.stencilUpdates; _stencilUpdates = stats.stencilUpdates;
} }
@end @end

View File

@@ -19,7 +19,8 @@
- (instancetype)init { - (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
self.tileServerOptionsInternal = new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration()); self.tileServerOptionsInternal =
new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration());
} }
return self; return self;
} }
@@ -65,8 +66,8 @@
} }
+ (void)setApiKey:(NSString *)apiKey { + (void)setApiKey:(NSString *)apiKey {
apiKey = [apiKey stringByTrimmingCharactersInSet: apiKey =
[NSCharacterSet whitespaceAndNewlineCharacterSet]]; [apiKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (!apiKey.length) { if (!apiKey.length) {
return; return;
} }
@@ -88,7 +89,7 @@
+ (NSURL *)apiBaseURL { + (NSURL *)apiBaseURL {
auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL(); auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL();
NSString* baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()]; NSString *baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()];
NSURL *url = [NSURL URLWithString:baseUrlNSStr]; NSURL *url = [NSURL URLWithString:baseUrlNSStr];
return url; return url;
} }
@@ -109,7 +110,7 @@
} }
+ (void)useWellKnownTileServer:(MLNWellKnownTileServer)tileServer { + (void)useWellKnownTileServer:(MLNWellKnownTileServer)tileServer {
switch(tileServer){ switch (tileServer) {
case MLNMapTiler: case MLNMapTiler:
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()]; [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()];
break; break;
@@ -124,46 +125,48 @@
} }
} }
+ (void)setTileServerOptions:(MLNTileServerOptions*)options { + (void)setTileServerOptions:(MLNTileServerOptions *)options {
auto opts = mbgl::TileServerOptions() auto opts = mbgl::TileServerOptions()
.withBaseURL(std::string([options.baseURL UTF8String])) .withBaseURL(std::string([options.baseURL UTF8String]))
.withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String])) .withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String]))
.withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String])); .withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String]));
opts.withSourceTemplate( opts.withSourceTemplate(std::string([options.sourceTemplate UTF8String]),
std::string([options.sourceTemplate UTF8String]),
std::string([options.sourceDomainName UTF8String]), std::string([options.sourceDomainName UTF8String]),
options.sourceVersionPrefix ? std::string([options.sourceVersionPrefix UTF8String]) : std::optional<std::string>{}); options.sourceVersionPrefix
? std::string([options.sourceVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withStyleTemplate( opts.withStyleTemplate(std::string([options.styleTemplate UTF8String]),
std::string([options.styleTemplate UTF8String]),
std::string([options.styleDomainName UTF8String]), std::string([options.styleDomainName UTF8String]),
options.styleVersionPrefix ? std::string([options.styleVersionPrefix UTF8String]) : std::optional<std::string>{}); options.styleVersionPrefix
? std::string([options.styleVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withSpritesTemplate( opts.withSpritesTemplate(std::string([options.spritesTemplate UTF8String]),
std::string([options.spritesTemplate UTF8String]),
std::string([options.spritesDomainName UTF8String]), std::string([options.spritesDomainName UTF8String]),
options.spritesVersionPrefix ? std::string([options.spritesVersionPrefix UTF8String]) : std::optional<std::string>{}); options.spritesVersionPrefix
? std::string([options.spritesVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withGlyphsTemplate( opts.withGlyphsTemplate(std::string([options.glyphsTemplate UTF8String]),
std::string([options.glyphsTemplate UTF8String]),
std::string([options.glyphsDomainName UTF8String]), std::string([options.glyphsDomainName UTF8String]),
options.glyphsVersionPrefix ? std::string([options.glyphsVersionPrefix UTF8String]) : std::optional<std::string>{}); options.glyphsVersionPrefix
? std::string([options.glyphsVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withTileTemplate( opts.withTileTemplate(std::string([options.tileTemplate UTF8String]),
std::string([options.tileTemplate UTF8String]),
std::string([options.tileDomainName UTF8String]), std::string([options.tileDomainName UTF8String]),
options.tileVersionPrefix ? std::string([options.tileVersionPrefix UTF8String]) : std::optional<std::string>{}); options.tileVersionPrefix
? std::string([options.tileVersionPrefix UTF8String])
: std::optional<std::string>{});
std::vector<mbgl::util::DefaultStyle> defaultStyles; std::vector<mbgl::util::DefaultStyle> defaultStyles;
if (options.defaultStyles) { if (options.defaultStyles) {
for (MLNDefaultStyle* objCStyle in options.defaultStyles) { for (MLNDefaultStyle *objCStyle in options.defaultStyles) {
mbgl::util::DefaultStyle cppStyle( mbgl::util::DefaultStyle cppStyle(std::string([objCStyle.url.absoluteString UTF8String]),
std::string([objCStyle.url.absoluteString UTF8String]),
std::string([objCStyle.name UTF8String]), std::string([objCStyle.name UTF8String]),
objCStyle.version objCStyle.version);
);
defaultStyles.push_back(cppStyle); defaultStyles.push_back(cppStyle);
} }
} }
@@ -173,43 +176,57 @@
self.tileServerOptionsInternal = opts; self.tileServerOptionsInternal = opts;
} }
+ (MLNTileServerOptions*)tileServerOptions { + (MLNTileServerOptions *)tileServerOptions {
auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal; auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal;
if (cppOpts) { if (cppOpts) {
MLNTileServerOptions *retVal = [[MLNTileServerOptions alloc] init];
MLNTileServerOptions* retVal = [[MLNTileServerOptions alloc] init];
retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()]; retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()];
retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()]; retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()];
retVal.apiKeyParameterName = [NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()]; retVal.apiKeyParameterName =
[NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()];
retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()]; retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()];
retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().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.sourceVersionPrefix =
cppOpts->sourceVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()]
: nil;
retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()]; retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()];
retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().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.styleVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()]
: nil;
retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()]; retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()];
retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().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.spritesVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()]
: nil;
retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()]; retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()];
retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().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.glyphsVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()]
: nil;
retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()]; retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()];
retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().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.tileVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()]
: nil;
std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles(); std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles();
NSMutableArray<MLNDefaultStyle *> *mglStyles = [[NSMutableArray<MLNDefaultStyle *> alloc] init];
NSMutableArray<MLNDefaultStyle*>* mglStyles = [[NSMutableArray<MLNDefaultStyle*> alloc] init];
for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) { for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) {
NSString* url = [NSString stringWithUTF8String:it->getUrl().c_str()]; NSString *url = [NSString stringWithUTF8String:it->getUrl().c_str()];
auto* mglDefaultStyle = [[MLNDefaultStyle alloc] init]; auto *mglDefaultStyle = [[MLNDefaultStyle alloc] init];
mglDefaultStyle.url = [NSURL URLWithString:url]; mglDefaultStyle.url = [NSURL URLWithString:url];
mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()]; mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()];
mglDefaultStyle.version = it->getCurrentVersion(); mglDefaultStyle.version = it->getCurrentVersion();
@@ -226,7 +243,6 @@
} }
return nil; return nil;
} }
@end @end

View File

@@ -2,8 +2,8 @@
#import "MLNFeature_Private.h" #import "MLNFeature_Private.h"
#import "NSString+MLNAdditions.h"
#import "MLNTypes.h" #import "MLNTypes.h"
#import "NSString+MLNAdditions.h"
#import <mbgl/util/geo.hpp> #import <mbgl/util/geo.hpp>
@@ -13,7 +13,9 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
@implementation MLNShape @implementation MLNShape
+ (nullable MLNShape *)shapeWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError * _Nullable *)outError { + (nullable MLNShape *)shapeWithData:(NSData *)data
encoding:(NSStringEncoding)encoding
error:(NSError *_Nullable *)outError {
NSString *string = [[NSString alloc] initWithData:data encoding:encoding]; NSString *string = [[NSString alloc] initWithData:data encoding:encoding];
if (!string) { if (!string) {
if (outError) { if (outError) {
@@ -27,8 +29,10 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
return MLNShapeFromGeoJSON(geojson); return MLNShapeFromGeoJSON(geojson);
} catch (std::runtime_error &err) { } catch (std::runtime_error &err) {
if (outError) { if (outError) {
*outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{ *outError = [NSError errorWithDomain:MLNErrorDomain
NSLocalizedFailureReasonErrorKey: @(err.what()), code:MLNErrorCodeUnknown
userInfo:@{
NSLocalizedFailureReasonErrorKey : @(err.what()),
}]; }];
} }
return nil; return nil;
@@ -40,8 +44,7 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"];
format:@"MLNShape is an abstract class"];
return mbgl::Point<double>(); return mbgl::Point<double>();
} }
@@ -51,13 +54,11 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
return [string dataUsingEncoding:NSUTF8StringEncoding]; return [string dataUsingEncoding:NSUTF8StringEncoding];
} }
+ (BOOL)supportsSecureCoding + (BOOL)supportsSecureCoding {
{
return YES; return YES;
} }
- (instancetype)initWithCoder:(NSCoder *)coder - (instancetype)initWithCoder:(NSCoder *)coder {
{
if (self = [super init]) { if (self = [super init]) {
_title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"]; _title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"];
_subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"]; _subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
@@ -68,8 +69,7 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
return self; return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[coder encodeObject:_title forKey:@"title"]; [coder encodeObject:_title forKey:@"title"];
[coder encodeObject:_subtitle forKey:@"subtitle"]; [coder encodeObject:_subtitle forKey:@"subtitle"];
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
@@ -77,23 +77,25 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
#endif #endif
} }
- (BOOL)isEqual:(id)other - (BOOL)isEqual:(id)other {
{ if (other == self) {
if (other == self) { return YES; } return YES;
id <MLNAnnotation> annotation = other; }
id<MLNAnnotation> annotation = other;
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) &&
&& ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]); ((!_subtitle && ![annotation subtitle]) ||
[_subtitle isEqualToString:[annotation subtitle]]);
#else #else
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) &&
&& ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]) ((!_subtitle && ![annotation subtitle]) ||
&& ((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]); [_subtitle isEqualToString:[annotation subtitle]]) &&
((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]);
#endif #endif
} }
- (NSUInteger)hash - (NSUInteger)hash {
{
NSUInteger hash = _title.hash + _subtitle.hash; NSUInteger hash = _title.hash + _subtitle.hash;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
hash += _toolTip.hash; hash += _toolTip.hash;
@@ -101,10 +103,8 @@ bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D r
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate - (CLLocationCoordinate2D)coordinate {
{ [NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"];
[NSException raise:MLNAbstractClassException
format:@"MLNShape is an abstract class"];
return kCLLocationCoordinate2DInvalid; return kCLLocationCoordinate2DInvalid;
} }

View File

@@ -1,8 +1,8 @@
#import "MLNShapeCollection.h" #import "MLNShapeCollection.h"
#import "MLNShape_Private.h"
#import "MLNFeature.h" #import "MLNFeature.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNShape_Private.h"
#import <mbgl/style/conversion/geojson.hpp> #import <mbgl/style/conversion/geojson.hpp>
@@ -27,7 +27,8 @@
- (instancetype)initWithCoder:(NSCoder *)decoder { - (instancetype)initWithCoder:(NSCoder *)decoder {
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) { if (self = [super initWithCoder:decoder]) {
NSSet<Class> *shapeClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNShape class]]]; NSSet<Class> *shapeClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNShape class] ]];
_shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"]; _shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"];
} }
return self; return self;
@@ -43,8 +44,8 @@
if (![other isKindOfClass:[MLNShapeCollection class]]) return NO; if (![other isKindOfClass:[MLNShapeCollection class]]) return NO;
MLNShapeCollection *otherShapeCollection = other; MLNShapeCollection *otherShapeCollection = other;
return [super isEqual:otherShapeCollection] return
&& [_shapes isEqualToArray:otherShapeCollection.shapes]; [super isEqual:otherShapeCollection] && [_shapes isEqualToArray:otherShapeCollection.shapes];
} }
- (NSUInteger)hash { - (NSUInteger)hash {
@@ -60,8 +61,7 @@
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return @{@"type": @"GeometryCollection", return @{@"type" : @"GeometryCollection", @"geometries" : [self geometryCollection]};
@"geometries": [self geometryCollection]};
} }
- (NSArray *)geometryCollection { - (NSArray *)geometryCollection {

View File

@@ -1,17 +1,17 @@
#import "MLNShapeOfflineRegion.h" #import "MLNShapeOfflineRegion.h"
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#else #else
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#endif #endif
#import "MLNFeature_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNOfflineRegion_Private.h" #import "MLNOfflineRegion_Private.h"
#import "MLNShapeOfflineRegion_Private.h" #import "MLNShapeOfflineRegion_Private.h"
#import "MLNFeature_Private.h"
#import "MLNShape_Private.h" #import "MLNShape_Private.h"
#import "MLNStyle.h" #import "MLNStyle.h"
#import "MLNLoggingConfiguration_Private.h"
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private> @interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
@@ -24,7 +24,7 @@
@synthesize styleURL = _styleURL; @synthesize styleURL = _styleURL;
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
-(NSDictionary *)offlineStartEventAttributes { - (NSDictionary *)offlineStartEventAttributes {
return @{}; return @{};
} }
@@ -35,22 +35,25 @@
- (instancetype)init { - (instancetype)init {
MLNLogInfo(@"Calling this initializer is not allowed."); MLNLogInfo(@"Calling this initializer is not allowed.");
[NSException raise:@"Method unavailable" [NSException raise:@"Method unavailable"
format: format:@"-[MLNShapeOfflineRegion init] is unavailable. "
@"-[MLNShapeOfflineRegion init] is unavailable. "
@"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."]; @"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."];
return nil; return nil;
} }
- (instancetype)initWithStyleURL:(NSURL *)styleURL shape:(MLNShape *)shape fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel { - (instancetype)initWithStyleURL:(NSURL *)styleURL
MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, shape, minimumZoomLevel, maximumZoomLevel); 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 (self = [super init]) {
if (!styleURL) { if (!styleURL) {
styleURL = [MLNStyle defaultStyleURL]; styleURL = [MLNStyle defaultStyleURL];
} }
if (!styleURL.scheme) { if (!styleURL.scheme) {
[NSException raise:@"Invalid style URL" format: [NSException raise:@"Invalid style URL"
@"%@ does not support setting a relative file URL as the style URL. " format:@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, " @"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. " @"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.", @"For Mapbox-hosted styles, use the mapbox: scheme.",
@@ -66,40 +69,47 @@
return self; return self;
} }
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineGeometryRegionDefinition &)definition { - (instancetype)initWithOfflineRegionDefinition:
(const mbgl::OfflineGeometryRegionDefinition &)definition {
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry); MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry);
MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL
shape:shape
fromZoomLevel:definition.minZoom
toZoomLevel:definition.maxZoom];
result.includesIdeographicGlyphs = definition.includeIdeographs; result.includesIdeographicGlyphs = definition.includeIdeographs;
return result; return result;
} }
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition { - (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #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 #elif TARGET_OS_MAC
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
#endif #endif
return mbgl::OfflineGeometryRegionDefinition(_styleURL.absoluteString.UTF8String, return mbgl::OfflineGeometryRegionDefinition(
_shape.geometryObject, _styleURL.absoluteString.UTF8String, _shape.geometryObject, _minimumZoomLevel,
_minimumZoomLevel, _maximumZoomLevel, _maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs);
scaleFactor, _includesIdeographicGlyphs);
} }
- (nullable instancetype)initWithCoder:(NSCoder *)coder { - (nullable instancetype)initWithCoder:(NSCoder *)coder {
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
MLNShape * shape = [coder decodeObjectForKey:@"shape"]; MLNShape *shape = [coder decodeObjectForKey:@"shape"];
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel]; MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL
shape:shape
fromZoomLevel:minimumZoomLevel
toZoomLevel:maximumZoomLevel];
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
return result; return result;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[coder encodeObject:_styleURL forKey:@"styleURL"]; [coder encodeObject:_styleURL forKey:@"styleURL"];
[coder encodeObject:_shape forKey:@"shape"]; [coder encodeObject:_shape forKey:@"shape"];
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
@@ -108,7 +118,11 @@
} }
- (id)copyWithZone:(nullable NSZone *)zone { - (id)copyWithZone:(nullable NSZone *)zone {
MLNShapeOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL shape:_shape fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel]; MLNShapeOfflineRegion *result =
[[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
shape:_shape
fromZoomLevel:_minimumZoomLevel
toZoomLevel:_maximumZoomLevel];
result.includesIdeographicGlyphs = _includesIdeographicGlyphs; result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
return result; return result;
} }
@@ -122,18 +136,16 @@
} }
MLNShapeOfflineRegion *otherRegion = other; MLNShapeOfflineRegion *otherRegion = other;
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel return (_minimumZoomLevel == otherRegion->_minimumZoomLevel &&
&& _maximumZoomLevel == otherRegion->_maximumZoomLevel _maximumZoomLevel == otherRegion->_maximumZoomLevel &&
&& _shape.geometryObject == otherRegion->_shape.geometryObject _shape.geometryObject == otherRegion->_shape.geometryObject &&
&& [_styleURL isEqual:otherRegion->_styleURL] [_styleURL isEqual:otherRegion->_styleURL] &&
&& _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
} }
- (NSUInteger)hash { - (NSUInteger)hash {
return (_styleURL.hash return (_styleURL.hash + _shape.hash + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash +
+ _shape.hash @(_includesIdeographicGlyphs).hash);
+ @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash
+ @(_includesIdeographicGlyphs).hash);
} }
@end @end

View File

@@ -1,35 +1,44 @@
#import "MLNFoundation_Private.h" #import "MLNFoundation_Private.h"
#import "MLNShapeSource_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 "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 "NSPredicate+MLNPrivateAdditions.h"
#import "NSURL+MLNAdditions.h" #import "NSURL+MLNAdditions.h"
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/renderer/renderer.hpp> #include <mbgl/renderer/renderer.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer"; const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer";
const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius"; const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius";
const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints = @"MLNShapeSourceOptionClusterMinPoints"; const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints =
@"MLNShapeSourceOptionClusterMinPoints";
const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered"; const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered";
const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties = @"MLNShapeSourceOptionClusterProperties"; const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties =
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel = @"MLNShapeSourceOptionMaximumZoomLevel"; @"MLNShapeSourceOptionClusterProperties";
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = @"MLNShapeSourceOptionMaximumZoomLevelForClustering"; const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel =
const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = @"MLNShapeSourceOptionMinimumZoomLevel"; @"MLNShapeSourceOptionMaximumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = @"MLNShapeSourceOptionSimplificationTolerance"; const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering =
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = @"MLNShapeSourceOptionLineDistanceMetrics"; @"MLNShapeSourceOptionMaximumZoomLevelForClustering";
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = @"MLNShapeSourceOptionSynchronousUpdate"; const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel =
@"MLNShapeSourceOptionMinimumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance =
@"MLNShapeSourceOptionSimplificationTolerance";
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics =
@"MLNShapeSourceOptionLineDistanceMetrics";
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate =
@"MLNShapeSourceOptionSynchronousUpdate";
mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) { mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(
NSDictionary<MLNShapeSourceOption, id> *options) {
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>(); auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
@@ -98,8 +107,10 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) { if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) { if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
[NSException raise:NSInvalidArgumentException [NSException
format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."]; 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]; NSEnumerator *stringEnumerator = [value keyEnumerator];
@@ -109,23 +120,28 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
NSArray *expressionsArray = value[key]; NSArray *expressionsArray = value[key];
if (![expressionsArray isKindOfClass:[NSArray class]]) { if (![expressionsArray isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."]; 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. // 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) { if ([expressionsArray count] != 2) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value requires array of two objects."]; format:@"MLNShapeSourceOptionClusterProperties member value requires array of "
@"two objects."];
} }
// reduceExpression should be a valid NSExpression // reduceExpression should be a valid NSExpression
NSExpression *reduceExpression = expressionsArray[0]; NSExpression *reduceExpression = expressionsArray[0];
if (![reduceExpression isKindOfClass:[NSExpression class]]) { if (![reduceExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties array value requires two expression objects."]; format:@"MLNShapeSourceOptionClusterProperties array value requires two "
@"expression objects."];
} }
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression); auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
if (!reduce) { if (!reduce) {
[NSException raise:NSInvalidArgumentException [NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."]; format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
} }
@@ -133,17 +149,20 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
NSExpression *mapExpression = expressionsArray[1]; NSExpression *mapExpression = expressionsArray[1];
if (![mapExpression isKindOfClass:[NSExpression class]]) { if (![mapExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value must contain a valid NSExpression."]; format:@"MLNShapeSourceOptionClusterProperties member value must contain a "
@"valid NSExpression."];
} }
auto map = MLNClusterPropertyFromNSExpression(mapExpression); auto map = MLNClusterPropertyFromNSExpression(mapExpression);
if (!map) { if (!map) {
[NSException raise:NSInvalidArgumentException [NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."]; format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
} }
std::string keyString = std::string([key UTF8String]); std::string keyString = std::string([key UTF8String]);
geoJSONOptions->clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce))); geoJSONOptions->clusterProperties.emplace(keyString,
std::make_pair(std::move(map), std::move(reduce)));
} }
} }
@@ -175,18 +194,24 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
@implementation MLNShapeSource @implementation MLNShapeSource
- (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
URL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions)); auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) { if (self = [super initWithPendingSource:std::move(source)]) {
self.URL = url; self.URL = url;
} }
return self; return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MLNShape *)shape options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
shape:(nullable MLNShape *)shape
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions)); auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) { if (self = [super initWithPendingSource:std::move(source)]) {
if ([shape isMemberOfClass:[MLNShapeCollection class]]) { if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
@@ -201,17 +226,25 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
return self; return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NSArray<MLNShape<MLNFeature> *> *)features options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
for (id <MLNFeature> feature in features) { features:(NSArray<MLNShape<MLNFeature> *> *)features
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
for (id<MLNFeature> feature in features) {
if (![feature conformsToProtocol:@protocol(MLNFeature)]) { if (![feature conformsToProtocol:@protocol(MLNFeature)]) {
[NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MLNFeature protocol.", feature]; [NSException raise:NSInvalidArgumentException
format:@"The object %@ included in the features argument does not conform to the "
@"MLNFeature protocol.",
feature];
} }
} }
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:features]; MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:features];
return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options]; return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NSArray<MLNShape *> *)shapes options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
shapes:(NSArray<MLNShape *> *)shapes
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes]; MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
return [self initWithIdentifier:identifier shape:shapeCollection options:options]; return [self initWithIdentifier:identifier shape:shapeCollection options:options];
} }
@@ -238,22 +271,23 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
- (void)setShape:(MLNShape *)shape { - (void)setShape:(MLNShape *)shape {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
self.rawSource->setGeoJSON({ shape.geoJSONObject }); self.rawSource->setGeoJSON({shape.geoJSONObject});
_shape = shape; _shape = shape;
} }
- (NSString *)description { - (NSString *)description {
if (self.rawSource) { if (self.rawSource) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>", return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.URL, self.shape]; NSStringFromClass([self class]), (void *)self,
} self.identifier, self.URL, self.shape];
else { } else {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>", return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.shape]; NSStringFromClass([self class]), (void *)self,
self.identifier, self.shape];
} }
} }
- (NSArray<id <MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate { - (NSArray<id<MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
std::optional<mbgl::style::Filter> optionalFilter; std::optional<mbgl::style::Filter> optionalFilter;
if (predicate) { if (predicate) {
@@ -263,14 +297,17 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
std::vector<mbgl::Feature> features; std::vector<mbgl::Feature> features;
if ([self.stylable isKindOfClass:[MLNMapView class]]) { if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable; MLNMapView *mapView = (MLNMapView *)self.stylable;
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter }); features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), {{}, optionalFilter});
} }
return MLNFeaturesFromMBGLFeatures(features); return MLNFeaturesFromMBGLFeatures(features);
} }
// MARK: - MLNCluster management // MARK: - MLNCluster management
- (std::optional<mbgl::FeatureExtensionValue>)featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster extension:(std::string)extension options:(const std::map<std::string, mbgl::Value>)options { - (std::optional<mbgl::FeatureExtensionValue>)
featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster
extension:(std::string)extension
options:(const std::map<std::string, mbgl::Value>)options {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
std::optional<mbgl::FeatureExtensionValue> extensionValue; std::optional<mbgl::FeatureExtensionValue> extensionValue;
@@ -290,22 +327,21 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
if ([self.stylable isKindOfClass:[MLNMapView class]]) { if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable; MLNMapView *mapView = (MLNMapView *)self.stylable;
extensionValue = mapView.renderer->queryFeatureExtensions(self.rawSource->getID(), extensionValue = mapView.renderer->queryFeatureExtensions(
clusterFeature, self.rawSource->getID(), clusterFeature, "supercluster", extension, options);
"supercluster",
extension,
options);
} }
return extensionValue; return extensionValue;
} }
- (NSArray<id <MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit { - (NSArray<id<MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster
const std::map<std::string, mbgl::Value> options = { offset:(NSUInteger)offset
{ "limit", static_cast<uint64_t>(limit) }, limit:(NSUInteger)limit {
{ "offset", static_cast<uint64_t>(offset) } const std::map<std::string, mbgl::Value> options = {{"limit", static_cast<uint64_t>(limit)},
}; {"offset", static_cast<uint64_t>(offset)}};
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"leaves" options:options]; auto featureExtension = [self featureExtensionValueOfCluster:cluster
extension:"leaves"
options:options];
if (!featureExtension) { if (!featureExtension) {
return @[]; return @[];
@@ -319,8 +355,10 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
return MLNFeaturesFromMBGLFeatures(leaves); return MLNFeaturesFromMBGLFeatures(leaves);
} }
- (NSArray<id <MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster { - (NSArray<id<MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster {
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"children" options:{}]; auto featureExtension = [self featureExtensionValueOfCluster:cluster
extension:"children"
options:{}];
if (!featureExtension) { if (!featureExtension) {
return @[]; return @[];
@@ -335,7 +373,9 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
} }
- (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)cluster { - (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)cluster {
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"expansion-zoom" options:{}]; auto featureExtension = [self featureExtensionValueOfCluster:cluster
extension:"expansion-zoom"
options:{}];
if (!featureExtension) { if (!featureExtension) {
return -1.0; return -1.0;
@@ -354,11 +394,12 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
return -1.0; return -1.0;
} }
- (void)debugRecursiveLogForFeature:(id <MLNFeature>)feature indent:(NSUInteger)indent { - (void)debugRecursiveLogForFeature:(id<MLNFeature>)feature indent:(NSUInteger)indent {
NSString *description = feature.description; NSString *description = feature.description;
// Want our recursive log on a single line // Want our recursive log on a single line
NSString *log = [description stringByReplacingOccurrencesOfString:@"\\s+" NSString *log =
[description stringByReplacingOccurrencesOfString:@"\\s+"
withString:@" " withString:@" "
options:NSRegularExpressionSearch options:NSRegularExpressionSearch
range:NSMakeRange(0, description.length)]; range:NSMakeRange(0, description.length)];
@@ -368,7 +409,7 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster); MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
if (cluster) { if (cluster) {
for (id <MLNFeature> child in [self childrenOfCluster:cluster]) { for (id<MLNFeature> child in [self childrenOfCluster:cluster]) {
[self debugRecursiveLogForFeature:child indent:indent + 4]; [self debugRecursiveLogForFeature:child indent:indent + 4];
} }
} }

View File

@@ -1,11 +1,11 @@
#import "MLNMapView_Private.h"
#import "MLNSource_Private.h" #import "MLNSource_Private.h"
#import "MLNStyle_Private.h" #import "MLNStyle_Private.h"
#import "MLNMapView_Private.h"
#import "NSBundle+MLNAdditions.h" #import "NSBundle+MLNAdditions.h"
#include <mbgl/style/style.hpp>
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/style/source.hpp> #include <mbgl/style/source.hpp>
#include <mbgl/style/style.hpp>
const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException"; const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException";
@@ -15,7 +15,7 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
// special internal source types like mbgl::AnnotationSource. // special internal source types like mbgl::AnnotationSource.
@property (nonatomic, readonly) mbgl::style::Source *rawSource; @property (nonatomic, readonly) mbgl::style::Source *rawSource;
@property (nonatomic, readonly, weak) id <MLNStylable> stylable; @property (nonatomic, readonly, weak) id<MLNStylable> stylable;
@end @end
@@ -24,27 +24,25 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
mapbox::base::WeakPtr<mbgl::style::Source> _weakSource; mapbox::base::WeakPtr<mbgl::style::Source> _weakSource;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier {
- (instancetype)initWithIdentifier:(NSString *)identifier
{
if (self = [super init]) { if (self = [super init]) {
_identifier = [identifier copy]; _identifier = [identifier copy];
} }
return self; return self;
} }
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource stylable:(id <MLNStylable>)stylable { - (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource
stylable:(id<MLNStylable>)stylable {
NSString *identifier = @(rawSource->getID().c_str()); NSString *identifier = @(rawSource->getID().c_str());
if (self = [self initWithIdentifier:identifier]) { if (self = [self initWithIdentifier:identifier]) {
_weakSource = rawSource->makeWeakPtr(); _weakSource = rawSource->makeWeakPtr();
rawSource->peer = SourceWrapper { self }; rawSource->peer = SourceWrapper{self};
_stylable = stylable; _stylable = stylable;
} }
return self; return self;
} }
- (mbgl::style::Source *)rawSource - (mbgl::style::Source *)rawSource {
{
return _weakSource.get(); return _weakSource.get();
} }
@@ -55,23 +53,24 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
return self; return self;
} }
- (void)addToStylable:(id <MLNStylable>)stylable { - (void)addToStylable:(id<MLNStylable>)stylable {
if (_pendingSource == nullptr) { if (_pendingSource == nullptr) {
[NSException raise:MLNRedundantSourceException [NSException raise:MLNRedundantSourceException
format:@"This instance %@ was already added to %@. Adding the same source instance " \ format:@"This instance %@ was already added to %@. Adding the same source instance "
"to the style more than once is invalid.", self, stylable.style]; "to the style more than once is invalid.",
self, stylable.style];
} }
_stylable = stylable; _stylable = stylable;
_stylable.style.rawStyle->addSource(std::move(_pendingSource)); _stylable.style.rawStyle->addSource(std::move(_pendingSource));
} }
- (BOOL)removeFromStylable:(id <MLNStylable>)mapView error:(NSError * __nullable * __nullable)outError { - (BOOL)removeFromStylable:(id<MLNStylable>)mapView
error:(NSError *__nullable *__nullable)outError {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
BOOL removed = NO; 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) { if (removedSource) {
@@ -79,31 +78,39 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
_pendingSource = std::move(removedSource); _pendingSource = std::move(removedSource);
_stylable = nil; _stylable = nil;
} else if (outError) { } else if (outError) {
NSString *localizedDescription = [NSString stringWithFormat: NSString *localizedDescription = [NSString
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"), 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]; self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIsInUseCannotRemove code:MLNErrorCodeSourceIsInUseCannotRemove
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
} }
} else if (outError) { } else if (outError) {
// TODO: Consider raising an exception here // TODO: Consider raising an exception here
NSString *localizedDescription = [NSString stringWithFormat: NSString *localizedDescription =
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"), [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]; self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIdentifierMismatch code:MLNErrorCodeSourceIdentifierMismatch
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
} }
return removed; return removed;
} }
- (NSString *)description { - (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", NSStringFromClass([self class]),
NSStringFromClass([self class]), (void *)self, self.identifier]; (void *)self, self.identifier];
} }
@end @end

View File

@@ -1,72 +1,75 @@
#import "MLNStyle_Private.h" #import "MLNStyle_Private.h"
#import "MLNMapView_Private.h" #import "MLNBackgroundStyleLayer.h"
#import "MLNStyleLayer.h"
#import "MLNStyleLayer_Private.h"
#import "MLNFillStyleLayer.h"
#import "MLNFillExtrusionStyleLayer.h"
#import "MLNLineStyleLayer.h"
#import "MLNCircleStyleLayer.h" #import "MLNCircleStyleLayer.h"
#import "MLNSymbolStyleLayer.h" #import "MLNFillExtrusionStyleLayer.h"
#import "MLNFillStyleLayer.h"
#import "MLNHeatmapStyleLayer.h" #import "MLNHeatmapStyleLayer.h"
#import "MLNHillshadeStyleLayer.h" #import "MLNHillshadeStyleLayer.h"
#import "MLNLineStyleLayer.h"
#import "MLNMapView_Private.h"
#import "MLNRasterStyleLayer.h" #import "MLNRasterStyleLayer.h"
#import "MLNBackgroundStyleLayer.h" #import "MLNStyleLayer.h"
#import "MLNStyleLayerManager.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.h"
#import "MLNSource_Private.h" #import "MLNSource_Private.h"
#import "MLNLight_Private.h"
#import "MLNTileSource_Private.h" #import "MLNTileSource_Private.h"
#import "MLNVectorTileSource_Private.h" #import "MLNVectorTileSource_Private.h"
#import "MLNRasterTileSource.h"
#import "MLNRasterDEMSource.h"
#import "MLNShapeSource.h"
#import "MLNImageSource.h"
#import "MLNAttributionInfo_Private.h" #import "MLNAttributionInfo_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/image.hpp> #include <mbgl/style/image.hpp>
#include <mbgl/style/light.hpp> #include <mbgl/style/light.hpp>
#include <mbgl/style/sources/geojson_source.hpp> #include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/sources/raster_source.hpp>
#include <mbgl/style/sources/raster_dem_source.hpp>
#include <mbgl/style/sources/image_source.hpp> #include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/sources/raster_dem_source.hpp>
#include <mbgl/style/sources/raster_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/style.hpp>
#import "NSDate+MLNAdditions.h" #import "NSDate+MLNAdditions.h"
#import "MLNCustomStyleLayer.h" #import "MLNCustomStyleLayer.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h" #import "UIImage+MLNAdditions.h"
#else #else
#import "NSImage+MLNAdditions.h" #import "NSImage+MLNAdditions.h"
#endif #endif
const MLNExceptionName MLNInvalidStyleURLException = @"MLNInvalidStyleURLException"; const MLNExceptionName MLNInvalidStyleURLException = @"MLNInvalidStyleURLException";
const MLNExceptionName MLNRedundantLayerException = @"MLNRedundantLayerException"; const MLNExceptionName MLNRedundantLayerException = @"MLNRedundantLayerException";
const MLNExceptionName MLNRedundantLayerIdentifierException = @"MLNRedundantLayerIdentifierException"; const MLNExceptionName MLNRedundantLayerIdentifierException =
@"MLNRedundantLayerIdentifierException";
const MLNExceptionName MLNRedundantSourceException = @"MLNRedundantSourceException"; const MLNExceptionName MLNRedundantSourceException = @"MLNRedundantSourceException";
const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSourceIdentifierException"; const MLNExceptionName MLNRedundantSourceIdentifierException =
@"MLNRedundantSourceIdentifierException";
/** /**
Model class for localization changes. Model class for localization changes.
*/ */
@interface MLNTextLanguage: NSObject @interface MLNTextLanguage : NSObject
@property (strong, nonatomic) NSString *originalTextField; @property (strong, nonatomic) NSString *originalTextField;
@property (strong, nonatomic) NSString *updatedTextField; @property (strong, nonatomic) NSString *updatedTextField;
- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField; - (instancetype)initWithTextLanguage:(NSString *)originalTextField
updatedTextField:(NSString *)updatedTextField;
@end @end
@implementation MLNTextLanguage @implementation MLNTextLanguage
- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField - (instancetype)initWithTextLanguage:(NSString *)originalTextField
{ updatedTextField:(NSString *)updatedTextField {
if (self = [super init]) { if (self = [super init]) {
_originalTextField = originalTextField; _originalTextField = originalTextField;
_updatedTextField = updatedTextField; _updatedTextField = updatedTextField;
@@ -75,13 +78,15 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
@end @end
@interface MLNStyle() @interface MLNStyle ()
@property (nonatomic, readonly, weak) id <MLNStylable> stylable; @property (nonatomic, readonly, weak) id<MLNStylable> stylable;
@property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (nonatomic, readonly) mbgl::style::Style *rawStyle;
@property (readonly, copy, nullable) NSURL *URL; @property (readonly, copy, nullable) NSURL *URL;
@property (nonatomic, readwrite, strong) NSMutableDictionary<NSString *, MLNCustomStyleLayer *> *customLayers; @property (nonatomic, readwrite, strong)
@property (nonatomic) NSMutableDictionary<NSString *, NSDictionary<NSObject *, MLNTextLanguage *> *> *localizedLayersByIdentifier; NSMutableDictionary<NSString *, MLNCustomStyleLayer *> *customLayers;
@property (nonatomic) NSMutableDictionary<NSString *, NSDictionary<NSObject *, MLNTextLanguage *> *>
*localizedLayersByIdentifier;
@end @end
@@ -89,26 +94,26 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: Predefined style URLs // MARK: Predefined style URLs
+ (NSArray<MLNDefaultStyle*>*) predefinedStyles { + (NSArray<MLNDefaultStyle *> *)predefinedStyles {
return MLNSettings.tileServerOptions.defaultStyles; return MLNSettings.tileServerOptions.defaultStyles;
} }
+ (MLNDefaultStyle*) defaultStyle { + (MLNDefaultStyle *)defaultStyle {
MLNTileServerOptions* opts = MLNSettings.tileServerOptions; MLNTileServerOptions *opts = MLNSettings.tileServerOptions;
return opts.defaultStyle; return opts.defaultStyle;
} }
+ (NSURL*) defaultStyleURL { + (NSURL *)defaultStyleURL {
MLNDefaultStyle* styleDefinition = [MLNStyle defaultStyle]; MLNDefaultStyle *styleDefinition = [MLNStyle defaultStyle];
if (styleDefinition != nil){ if (styleDefinition != nil) {
return styleDefinition.url; return styleDefinition.url;
} }
return nil; return nil;
} }
+ (MLNDefaultStyle*) predefinedStyle:(NSString*)withStyleName { + (MLNDefaultStyle *)predefinedStyle:(NSString *)withStyleName {
for (MLNDefaultStyle* style in MLNSettings.tileServerOptions.defaultStyles) { for (MLNDefaultStyle *style in MLNSettings.tileServerOptions.defaultStyles) {
if ([style.name isEqualToString:withStyleName]) { if ([style.name isEqualToString:withStyleName]) {
return style; return style;
} }
@@ -118,7 +123,7 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: - // MARK: -
- (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle stylable:(id <MLNStylable>)stylable { - (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle stylable:(id<MLNStylable>)stylable {
MLNLogInfo(@"Initializing %@ with stylable: %@", NSStringFromClass([self class]), stylable); MLNLogInfo(@"Initializing %@ with stylable: %@", NSStringFromClass([self class]), stylable);
if (self = [super init]) { if (self = [super init]) {
_stylable = stylable; _stylable = stylable;
@@ -179,8 +184,7 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
return [self sourceWithIdentifier:object.identifier]; return [self sourceWithIdentifier:object.identifier];
} }
- (MLNSource *)sourceWithIdentifier:(NSString *)identifier - (MLNSource *)sourceWithIdentifier:(NSString *)identifier {
{
MLNLogDebug(@"Querying source with identifier: %@", identifier); MLNLogDebug(@"Querying source with identifier: %@", identifier);
auto rawSource = self.rawStyle->getSource(identifier.UTF8String); auto rawSource = self.rawStyle->getSource(identifier.UTF8String);
@@ -188,7 +192,8 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
- (MLNSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { - (MLNSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource {
if (MLNSource *source = rawSource->peer.has_value() ? rawSource->peer.get<SourceWrapper>().source : nil) { if (MLNSource *source =
rawSource->peer.has_value() ? rawSource->peer.get<SourceWrapper>().source : nil) {
return source; return source;
} }
@@ -209,33 +214,33 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
} }
- (void)addSource:(MLNSource *)source - (void)addSource:(MLNSource *)source {
{
MLNLogDebug(@"Adding source: %@", source); MLNLogDebug(@"Adding source: %@", source);
if (!source.rawSource) { if (!source.rawSource) {
[NSException raise:NSInvalidArgumentException format: [NSException
@"The source %@ cannot be added to the style. " 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.", @"Make sure the source was created as a member of a concrete subclass of MLNSource.",
source]; source];
} }
try { try {
[source addToStylable:self.stylable]; [source addToStylable:self.stylable];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantSourceIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantSourceIdentifierException format:@"%s", err.what()];
} }
} }
- (void)removeSource:(MLNSource *)source - (void)removeSource:(MLNSource *)source {
{
[self removeSource:source error:nil]; [self removeSource:source error:nil];
} }
- (BOOL)removeSource:(MLNSource *)source error:(NSError * __nullable * __nullable)outError { - (BOOL)removeSource:(MLNSource *)source error:(NSError *__nullable *__nullable)outError {
MLNLogDebug(@"Removing source: %@", source); MLNLogDebug(@"Removing source: %@", source);
if (!source.rawSource) { if (!source.rawSource) {
NSString *errorMessage = [NSString stringWithFormat: NSString *errorMessage = [NSString
stringWithFormat:
@"The source %@ cannot be removed from the style. " @"The source %@ cannot be removed from the style. "
@"Make sure the source was created as a member of a concrete subclass of MLNSource." @"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.", @"Automatic re-addition of sources after style changes is not currently supported.",
@@ -244,10 +249,9 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
if (outError) { if (outError) {
*outError = [NSError errorWithDomain:MLNErrorDomain *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceCannotBeRemovedFromStyle code:MLNErrorCodeSourceCannotBeRemovedFromStyle
userInfo:@{ NSLocalizedDescriptionKey : errorMessage }]; userInfo:@{NSLocalizedDescriptionKey : errorMessage}];
return NO; return NO;
} } else {
else {
[NSException raise:NSInvalidArgumentException format:@"%@", errorMessage]; [NSException raise:NSInvalidArgumentException format:@"%@", errorMessage];
} }
} }
@@ -255,8 +259,9 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
return [source removeFromStylable:self.stylable error:outError]; return [source removeFromStylable:self.stylable error:outError];
} }
- (nullable NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize
- (nullable NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { linkColor:
(nullable MLNColor *)linkColor {
// It’d be incredibly convenient to use -sources here, but this operation // It’d be incredibly convenient to use -sources here, but this operation
// depends on the sources being sorted in ascending order by creation, as // depends on the sources being sorted in ascending order by creation, as
// with the std::vector used in mbgl. // with the std::vector used in mbgl.
@@ -276,10 +281,10 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: Style layers // MARK: Style layers
- (NSArray<__kindof MLNStyleLayer *> *)layers - (NSArray<__kindof MLNStyleLayer *> *)layers {
{
auto layers = self.rawStyle->getLayers(); auto layers = self.rawStyle->getLayers();
NSMutableArray<__kindof MLNStyleLayer *> *styleLayers = [NSMutableArray arrayWithCapacity:layers.size()]; NSMutableArray<__kindof MLNStyleLayer *> *styleLayers =
[NSMutableArray arrayWithCapacity:layers.size()];
for (auto layer : layers) { for (auto layer : layers) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer]; MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
[styleLayers addObject:styleLayer]; [styleLayers addObject:styleLayer];
@@ -297,13 +302,11 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
} }
- (NSUInteger)countOfLayers - (NSUInteger)countOfLayers {
{
return self.rawStyle->getLayers().size(); return self.rawStyle->getLayers().size();
} }
- (MLNStyleLayer *)objectInLayersAtIndex:(NSUInteger)index - (MLNStyleLayer *)objectInLayersAtIndex:(NSUInteger)index {
{
auto layers = self.rawStyle->getLayers(); auto layers = self.rawStyle->getLayers();
if (index >= layers.size()) { if (index >= layers.size()) {
[NSException raise:NSRangeException [NSException raise:NSRangeException
@@ -314,8 +317,7 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
return [self layerFromMBGLLayer:layer]; return [self layerFromMBGLLayer:layer];
} }
- (void)getLayers:(MLNStyleLayer **)buffer range:(NSRange)inRange - (void)getLayers:(MLNStyleLayer **)buffer range:(NSRange)inRange {
{
auto layers = self.rawStyle->getLayers(); auto layers = self.rawStyle->getLayers();
if (NSMaxRange(inRange) > layers.size()) { if (NSMaxRange(inRange) > layers.size()) {
[NSException raise:NSRangeException [NSException raise:NSRangeException
@@ -328,40 +330,41 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
} }
- (void)insertObject:(MLNStyleLayer *)styleLayer inLayersAtIndex:(NSUInteger)index - (void)insertObject:(MLNStyleLayer *)styleLayer inLayersAtIndex:(NSUInteger)index {
{
if (!styleLayer.rawLayer) { if (!styleLayer.rawLayer) {
[NSException raise:NSInvalidArgumentException format: [NSException raise:NSInvalidArgumentException
@"The style layer %@ cannot be inserted into the style. " 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.", @"Make sure the style layer was created as a member of a concrete subclass "
@"of MLNStyleLayer.",
styleLayer]; styleLayer];
} }
auto layers = self.rawStyle->getLayers(); auto layers = self.rawStyle->getLayers();
if (index > layers.size()) { if (index > layers.size()) {
[NSException raise:NSRangeException [NSException
raise:NSRangeException
format:@"Cannot insert style layer at out-of-bounds index %lu.", (unsigned long)index]; format:@"Cannot insert style layer at out-of-bounds index %lu.", (unsigned long)index];
} else if (index == 0) { } else if (index == 0) {
try { try {
MLNStyleLayer *sibling = layers.size() ? [self layerFromMBGLLayer:layers.at(0)] : nil; MLNStyleLayer *sibling = layers.size() ? [self layerFromMBGLLayer:layers.at(0)] : nil;
[styleLayer addToStyle:self belowLayer:sibling]; [styleLayer addToStyle:self belowLayer:sibling];
} catch (const std::runtime_error & err) { } catch (const std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
} else { } else {
try { try {
MLNStyleLayer *sibling = [self layerFromMBGLLayer:layers.at(index)]; MLNStyleLayer *sibling = [self layerFromMBGLLayer:layers.at(index)];
[styleLayer addToStyle:self belowLayer:sibling]; [styleLayer addToStyle:self belowLayer:sibling];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
} }
} }
- (void)removeObjectFromLayersAtIndex:(NSUInteger)index - (void)removeObjectFromLayersAtIndex:(NSUInteger)index {
{
auto layers = self.rawStyle->getLayers(); auto layers = self.rawStyle->getLayers();
if (index >= layers.size()) { if (index >= layers.size()) {
[NSException raise:NSRangeException [NSException
raise:NSRangeException
format:@"Cannot remove style layer at out-of-bounds index %lu.", (unsigned long)index]; format:@"Cannot remove style layer at out-of-bounds index %lu.", (unsigned long)index];
} }
auto layer = layers.at(index); auto layer = layers.at(index);
@@ -369,31 +372,30 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
[styleLayer removeFromStyle:self]; [styleLayer removeFromStyle:self];
} }
- (MLNStyleLayer *)layerFromMBGLLayer:(mbgl::style::Layer *)rawLayer - (MLNStyleLayer *)layerFromMBGLLayer:(mbgl::style::Layer *)rawLayer {
{
NSParameterAssert(rawLayer); NSParameterAssert(rawLayer);
if (MLNStyleLayer *layer = rawLayer->peer.has_value() ? rawLayer->peer.get<LayerWrapper>().layer : nil) { if (MLNStyleLayer *layer =
rawLayer->peer.has_value() ? rawLayer->peer.get<LayerWrapper>().layer : nil) {
return layer; return layer;
} }
return mbgl::LayerManagerDarwin::get()->createPeer(rawLayer); return mbgl::LayerManagerDarwin::get()->createPeer(rawLayer);
} }
- (MLNStyleLayer *)layerWithIdentifier:(NSString *)identifier - (MLNStyleLayer *)layerWithIdentifier:(NSString *)identifier {
{
MLNLogDebug(@"Querying layerWithIdentifier: %@", identifier); MLNLogDebug(@"Querying layerWithIdentifier: %@", identifier);
auto mbglLayer = self.rawStyle->getLayer(identifier.UTF8String); auto mbglLayer = self.rawStyle->getLayer(identifier.UTF8String);
return mbglLayer ? [self layerFromMBGLLayer:mbglLayer] : nil; return mbglLayer ? [self layerFromMBGLLayer:mbglLayer] : nil;
} }
- (void)removeLayer:(MLNStyleLayer *)layer - (void)removeLayer:(MLNStyleLayer *)layer {
{
MLNLogDebug(@"Removing layer: %@", layer); MLNLogDebug(@"Removing layer: %@", layer);
if (!layer.rawLayer) { if (!layer.rawLayer) {
[NSException raise:NSInvalidArgumentException format: [NSException raise:NSInvalidArgumentException
@"The style layer %@ cannot be removed from the style. " 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.", @"Make sure the style layer was created as a member of a concrete subclass "
@"of MLNStyleLayer.",
layer]; layer];
} }
[self willChangeValueForKey:@"layers"]; [self willChangeValueForKey:@"layers"];
@@ -401,19 +403,19 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
[self didChangeValueForKey:@"layers"]; [self didChangeValueForKey:@"layers"];
} }
- (void)addLayer:(MLNStyleLayer *)layer - (void)addLayer:(MLNStyleLayer *)layer {
{
MLNLogDebug(@"Adding layer: %@", layer); MLNLogDebug(@"Adding layer: %@", layer);
if (!layer.rawLayer) { if (!layer.rawLayer) {
[NSException raise:NSInvalidArgumentException format: [NSException raise:NSInvalidArgumentException
@"The style layer %@ cannot be added to the style. " 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.", @"Make sure the style layer was created as a member of a concrete subclass "
@"of MLNStyleLayer.",
layer]; layer];
} }
[self willChangeValueForKey:@"layers"]; [self willChangeValueForKey:@"layers"];
try { try {
[layer addToStyle:self belowLayer:nil]; [layer addToStyle:self belowLayer:nil];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
[self didChangeValueForKey:@"layers"]; [self didChangeValueForKey:@"layers"];
@@ -423,27 +425,25 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
[self insertObject:layer inLayersAtIndex:index]; [self insertObject:layer inLayersAtIndex:index];
} }
- (void)insertLayer:(MLNStyleLayer *)layer belowLayer:(MLNStyleLayer *)sibling - (void)insertLayer:(MLNStyleLayer *)layer belowLayer:(MLNStyleLayer *)sibling {
{
MLNLogDebug(@"Inseting layer: %@ belowLayer: %@", layer, sibling); MLNLogDebug(@"Inseting layer: %@ belowLayer: %@", layer, sibling);
if (!layer.rawLayer) { if (!layer.rawLayer) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format: format:@"The style layer %@ cannot be added to the style. "
@"The style layer %@ cannot be added to the style. " @"Make sure the style layer was created as a member of a concrete subclass "
@"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", @"of MLNStyleLayer.",
layer]; layer];
} }
if (!sibling.rawLayer) { if (!sibling.rawLayer) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format: format:@"A style layer cannot be placed below %@ in the style. "
@"A style layer cannot be placed below %@ in the style. "
@"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].",
sibling]; sibling];
} }
[self willChangeValueForKey:@"layers"]; [self willChangeValueForKey:@"layers"];
try { try {
[layer addToStyle:self belowLayer:sibling]; [layer addToStyle:self belowLayer:sibling];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
[self didChangeValueForKey:@"layers"]; [self didChangeValueForKey:@"layers"];
@@ -453,15 +453,14 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
MLNLogDebug(@"Inseting layer: %@ aboveLayer: %@", layer, sibling); MLNLogDebug(@"Inseting layer: %@ aboveLayer: %@", layer, sibling);
if (!layer.rawLayer) { if (!layer.rawLayer) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format: format:@"The style layer %@ cannot be added to the style. "
@"The style layer %@ cannot be added to the style. " @"Make sure the style layer was created as a member of a concrete subclass "
@"Make sure the style layer was created as a member of a concrete subclass of MLNStyleLayer.", @"of MLNStyleLayer.",
layer]; layer];
} }
if (!sibling.rawLayer) { if (!sibling.rawLayer) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format: format:@"A style layer cannot be placed above %@ in the style. "
@"A style layer cannot be placed above %@ in the style. "
@"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].",
sibling]; sibling];
} }
@@ -479,21 +478,20 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
[self willChangeValueForKey:@"layers"]; [self willChangeValueForKey:@"layers"];
if (index + 1 > layers.size()) { if (index + 1 > layers.size()) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format: format:@"A style layer cannot be placed above %@ in the style. "
@"A style layer cannot be placed above %@ in the style. "
@"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].", @"Make sure sibling was obtained using -[MLNStyle layerWithIdentifier:].",
sibling]; sibling];
} else if (index + 1 == layers.size()) { } else if (index + 1 == layers.size()) {
try { try {
[layer addToStyle:self belowLayer:nil]; [layer addToStyle:self belowLayer:nil];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
} else { } else {
MLNStyleLayer *nextSibling = [self layerFromMBGLLayer:layers.at(index + 1)]; MLNStyleLayer *nextSibling = [self layerFromMBGLLayer:layers.at(index + 1)];
try { try {
[layer addToStyle:self belowLayer:nextSibling]; [layer addToStyle:self belowLayer:nextSibling];
} catch (std::runtime_error & err) { } catch (std::runtime_error &err) {
[NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()]; [NSException raise:MLNRedundantLayerIdentifierException format:@"%s", err.what()];
} }
} }
@@ -502,12 +500,10 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: Style images // MARK: Style images
- (void)setImage:(MLNImage *)image forName:(NSString *)name - (void)setImage:(MLNImage *)image forName:(NSString *)name {
{
MLNLogDebug(@"Setting image: %@ forName: %@", image, name); MLNLogDebug(@"Setting image: %@ forName: %@", image, name);
if (!image) { if (!image) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException format:@"Cannot assign nil image to “%@”.", name];
format:@"Cannot assign nil image to “%@”.", name];
} }
if (!name) { if (!name) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
@@ -517,23 +513,19 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
self.rawStyle->addImage([image mgl_styleImageWithIdentifier:name]); self.rawStyle->addImage([image mgl_styleImageWithIdentifier:name]);
} }
- (void)removeImageForName:(NSString *)name - (void)removeImageForName:(NSString *)name {
{
MLNLogDebug(@"Removing imageForName: %@", name); MLNLogDebug(@"Removing imageForName: %@", name);
if (!name) { if (!name) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException format:@"Cannot remove image with nil name."];
format:@"Cannot remove image with nil name."];
} }
self.rawStyle->removeImage([name UTF8String]); self.rawStyle->removeImage([name UTF8String]);
} }
- (MLNImage *)imageForName:(NSString *)name - (MLNImage *)imageForName:(NSString *)name {
{
MLNLogDebug(@"Querying imageForName: %@", name); MLNLogDebug(@"Querying imageForName: %@", name);
if (!name) { if (!name) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException format:@"Cannot get image with nil name."];
format:@"Cannot get image with nil name."];
} }
auto styleImage = self.rawStyle->getImage([name UTF8String]); auto styleImage = self.rawStyle->getImage([name UTF8String]);
@@ -542,50 +534,45 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: Style transitions // MARK: Style transitions
- (void)setTransition:(MLNTransition)transition - (void)setTransition:(MLNTransition)transition {
{
self.rawStyle->setTransitionOptions(MLNOptionsFromTransition(transition)); self.rawStyle->setTransitionOptions(MLNOptionsFromTransition(transition));
} }
- (MLNTransition)transition - (MLNTransition)transition {
{
const mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); const mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions();
return MLNTransitionFromOptions(transitionOptions); return MLNTransitionFromOptions(transitionOptions);
} }
- (void)setPerformsPlacementTransitions:(BOOL)performsPlacementTransitions - (void)setPerformsPlacementTransitions:(BOOL)performsPlacementTransitions {
{
mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions();
transitionOptions.enablePlacementTransitions = static_cast<bool>(performsPlacementTransitions); transitionOptions.enablePlacementTransitions = static_cast<bool>(performsPlacementTransitions);
self.rawStyle->setTransitionOptions(transitionOptions); self.rawStyle->setTransitionOptions(transitionOptions);
} }
- (BOOL)performsPlacementTransitions - (BOOL)performsPlacementTransitions {
{
mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions(); mbgl::style::TransitionOptions transitionOptions = self.rawStyle->getTransitionOptions();
return transitionOptions.enablePlacementTransitions; return transitionOptions.enablePlacementTransitions;
} }
// MARK: Style light // MARK: Style light
- (void)setLight:(MLNLight *)light - (void)setLight:(MLNLight *)light {
{ std::unique_ptr<mbgl::style::Light> mbglLight =
std::unique_ptr<mbgl::style::Light> mbglLight = std::make_unique<mbgl::style::Light>([light mbglLight]); std::make_unique<mbgl::style::Light>([light mbglLight]);
self.rawStyle->setLight(std::move(mbglLight)); self.rawStyle->setLight(std::move(mbglLight));
} }
- (MLNLight *)light - (MLNLight *)light {
{
auto mbglLight = self.rawStyle->getLight(); auto mbglLight = self.rawStyle->getLight();
MLNLight *light = [[MLNLight alloc] initWithMBGLLight:mbglLight]; MLNLight *light = [[MLNLight alloc] initWithMBGLLight:mbglLight];
return light; return light;
} }
- (NSString *)description - (NSString *)description {
{ return [NSString
return [NSString stringWithFormat:@"<%@: %p; name = %@, URL = %@>", stringWithFormat:@"<%@: %p; name = %@, URL = %@>", NSStringFromClass([self class]),
NSStringFromClass([self class]), (void *)self, (void *)self,
self.name ? [NSString stringWithFormat:@"\"%@\"", self.name] : self.name, self.name ? [NSString stringWithFormat:@"\"%@\"", self.name] : self.name,
self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL]; self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL];
} }
@@ -593,9 +580,10 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
// MARK: Mapbox Streets source introspection // MARK: Mapbox Streets source introspection
- (void)localizeLabelsIntoLocale:(nullable NSLocale *)locale { - (void)localizeLabelsIntoLocale:(nullable NSLocale *)locale {
NSSet<MLNVectorTileSource *> *streetsSources = NSSet<MLNVectorTileSource *> *streetsSources = [self.sources
[self.sources filteredSetUsingPredicate: filteredSetUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
[NSPredicate predicateWithBlock:^BOOL(MLNVectorTileSource * _Nullable source, NSDictionary<NSString *, id> * _Nullable bindings) { MLNVectorTileSource *_Nullable source,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [source isKindOfClass:[MLNVectorTileSource class]] && [source isMapboxStreets]; return [source isKindOfClass:[MLNVectorTileSource class]] && [source isMapboxStreets];
}]]; }]];
NSSet<NSString *> *streetsSourceIdentifiers = [streetsSources valueForKey:@"identifier"]; NSSet<NSString *> *streetsSourceIdentifiers = [streetsSources valueForKey:@"identifier"];
@@ -617,7 +605,8 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
} }
- (NSSet<MLNVectorTileSource *> *)mapboxStreetsSources { - (NSSet<MLNVectorTileSource *> *)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; return [source isKindOfClass:[MLNVectorTileSource class]] && source.mapboxStreets;
}]; }];
} }
@@ -625,9 +614,16 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
- (NSArray<MLNStyleLayer *> *)placeStyleLayers { - (NSArray<MLNStyleLayer *> *)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]; NSSet *placeSourceLayerIdentifiers =
NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MLNVectorStyleLayer * _Nullable layer, NSDictionary<NSString *, id> * _Nullable bindings) { [NSSet setWithObjects:@"marine_label", @"country_label", @"state_label", @"place_label",
return [layer isKindOfClass:[MLNVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [placeSourceLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; @"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<NSString *, id> *_Nullable bindings) {
return [layer isKindOfClass:[MLNVectorStyleLayer class]] &&
[streetsSourceIdentifiers containsObject:layer.sourceIdentifier] &&
[placeSourceLayerIdentifiers containsObject:layer.sourceLayerIdentifier];
}]; }];
return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; return [self.layers filteredArrayUsingPredicate:isPlacePredicate];
} }
@@ -636,8 +632,12 @@ const MLNExceptionName MLNRedundantSourceIdentifierException = @"MLNRedundantSou
NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"];
NSSet *roadStyleLayerIdentifiers = [NSSet setWithObjects:@"road_label", @"road", nil]; NSSet *roadStyleLayerIdentifiers = [NSSet setWithObjects:@"road_label", @"road", nil];
NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MLNVectorStyleLayer * _Nullable layer, NSDictionary<NSString *, id> * _Nullable bindings) { NSPredicate *isPlacePredicate =
return [layer isKindOfClass:[MLNVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [roadStyleLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; [NSPredicate predicateWithBlock:^BOOL(MLNVectorStyleLayer *_Nullable layer,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [layer isKindOfClass:[MLNVectorStyleLayer class]] &&
[streetsSourceIdentifiers containsObject:layer.sourceIdentifier] &&
[roadStyleLayerIdentifiers containsObject:layer.sourceLayerIdentifier];
}]; }];
return [self.layers filteredArrayUsingPredicate:isPlacePredicate]; return [self.layers filteredArrayUsingPredicate:isPlacePredicate];
} }

View File

@@ -1,8 +1,8 @@
#import "MLNStyleLayer_Private.h" #import "MLNStyleLayer_Private.h"
#import "MLNStyle_Private.h" #import "MLNStyle_Private.h"
#include <mbgl/style/style.hpp>
#include <mbgl/style/layer.hpp> #include <mbgl/style/layer.hpp>
#include <mbgl/style/style.hpp>
const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerException"; const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerException";
@@ -21,7 +21,7 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc
if (self = [super init]) { if (self = [super init]) {
_identifier = @(rawLayer->getID().c_str()); _identifier = @(rawLayer->getID().c_str());
_weakLayer = rawLayer->makeWeakPtr(); _weakLayer = rawLayer->makeWeakPtr();
rawLayer->peer = LayerWrapper { self }; rawLayer->peer = LayerWrapper{self};
} }
return self; return self;
} }
@@ -33,17 +33,16 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc
return self; return self;
} }
- (mbgl::style::Layer *)rawLayer - (mbgl::style::Layer *)rawLayer {
{
return _weakLayer.get(); return _weakLayer.get();
} }
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer - (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
{
if (_pendingLayer == nullptr) { if (_pendingLayer == nullptr) {
[NSException raise:MLNRedundantLayerException [NSException raise:MLNRedundantLayerException
format:@"This instance %@ was already added to %@. Adding the same layer instance " \ format:@"This instance %@ was already added to %@. Adding the same layer instance "
"to the style more than once is invalid.", self, style]; "to the style more than once is invalid.",
self, style];
} }
if (otherLayer) { if (otherLayer) {
@@ -54,68 +53,59 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc
} }
} }
- (void)removeFromStyle:(MLNStyle *)style - (void)removeFromStyle:(MLNStyle *)style {
{
if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) { if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) {
_pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String); _pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String);
} }
} }
- (void)setVisible:(BOOL)visible - (void)setVisible:(BOOL)visible {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
mbgl::style::VisibilityType v = visible mbgl::style::VisibilityType v =
? mbgl::style::VisibilityType::Visible visible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None;
: mbgl::style::VisibilityType::None;
self.rawLayer->setVisibility(v); self.rawLayer->setVisibility(v);
} }
- (BOOL)isVisible - (BOOL)isVisible {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
mbgl::style::VisibilityType v = self.rawLayer->getVisibility(); mbgl::style::VisibilityType v = self.rawLayer->getVisibility();
return (v == mbgl::style::VisibilityType::Visible); return (v == mbgl::style::VisibilityType::Visible);
} }
- (void)setMaximumZoomLevel:(float)maximumZoomLevel - (void)setMaximumZoomLevel:(float)maximumZoomLevel {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
self.rawLayer->setMaxZoom(maximumZoomLevel); self.rawLayer->setMaxZoom(maximumZoomLevel);
} }
- (float)maximumZoomLevel - (float)maximumZoomLevel {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
return self.rawLayer->getMaxZoom(); return self.rawLayer->getMaxZoom();
} }
- (void)setMinimumZoomLevel:(float)minimumZoomLevel - (void)setMinimumZoomLevel:(float)minimumZoomLevel {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
self.rawLayer->setMinZoom(minimumZoomLevel); self.rawLayer->setMinZoom(minimumZoomLevel);
} }
- (float)minimumZoomLevel - (float)minimumZoomLevel {
{
MLNAssertStyleLayerIsValid(); MLNAssertStyleLayerIsValid();
return self.rawLayer->getMinZoom(); return self.rawLayer->getMinZoom();
} }
- (NSString *)description - (NSString *)description {
{
if (self.rawLayer) { if (self.rawLayer) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>", return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, NSStringFromClass([self class]), (void *)self,
self.visible ? @"YES" : @"NO"]; self.identifier, self.visible ? @"YES" : @"NO"];
} } else {
else { return
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>", [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>",
NSStringFromClass([self class]), (void *)self, self.identifier]; NSStringFromClass([self class]), (void *)self, self.identifier];
} }
} }

View File

@@ -2,6 +2,7 @@
#import "MLNBackgroundStyleLayer_Private.h" #import "MLNBackgroundStyleLayer_Private.h"
#import "MLNCircleStyleLayer_Private.h" #import "MLNCircleStyleLayer_Private.h"
#import "MLNCustomStyleLayer_Private.h"
#import "MLNFillExtrusionStyleLayer_Private.h" #import "MLNFillExtrusionStyleLayer_Private.h"
#import "MLNFillStyleLayer_Private.h" #import "MLNFillStyleLayer_Private.h"
#import "MLNHeatmapStyleLayer_Private.h" #import "MLNHeatmapStyleLayer_Private.h"
@@ -9,7 +10,6 @@
#import "MLNLineStyleLayer_Private.h" #import "MLNLineStyleLayer_Private.h"
#import "MLNRasterStyleLayer_Private.h" #import "MLNRasterStyleLayer_Private.h"
#import "MLNSymbolStyleLayer_Private.h" #import "MLNSymbolStyleLayer_Private.h"
#import "MLNCustomStyleLayer_Private.h"
#import "MLNCustomDrawableStyleLayer_Private.h" #import "MLNCustomDrawableStyleLayer_Private.h"
@@ -102,13 +102,14 @@ void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr<LayerFactory> fact
void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) { void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) {
std::string type{factory->getTypeInfo()->type}; std::string type{factory->getTypeInfo()->type};
if (!type.empty()) { if (!type.empty()) {
NSCAssert(typeToFactory.find(type) == typeToFactory.end(), @"A layer type can be registered only once."); NSCAssert(typeToFactory.find(type) == typeToFactory.end(),
@"A layer type can be registered only once.");
typeToFactory.emplace(std::make_pair(std::move(type), factory)); typeToFactory.emplace(std::make_pair(std::move(type), factory));
} }
} }
LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) { LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) {
for (const auto& factory: peerFactories) { for (const auto& factory : peerFactories) {
if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) { if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) {
return factory.get(); return factory.get();
} }
@@ -126,7 +127,7 @@ LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* i
return peerFactory->getCoreLayerFactory(); return peerFactory->getCoreLayerFactory();
} }
for (const auto& factory: coreFactories) { for (const auto& factory : coreFactories) {
if (layerTypeInfoEquals(factory->getTypeInfo(), info)) { if (layerTypeInfoEquals(factory->getTypeInfo(), info)) {
// if (factory->getTypeInfo() == info) { // if (factory->getTypeInfo() == info) {
return factory.get(); return factory.get();
@@ -143,11 +144,10 @@ LayerManagerDarwin* LayerManagerDarwin::get() noexcept {
} }
// static // static
LayerManager* LayerManager::get() noexcept { LayerManager* LayerManager::get() noexcept { return LayerManagerDarwin::get(); }
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; const bool LayerManager::annotationsEnabled = false;
#else #else
const bool LayerManager::annotationsEnabled = true; const bool LayerManager::annotationsEnabled = true;

View File

@@ -87,7 +87,7 @@ struct LayerWrapper {
namespace mbgl { namespace mbgl {
class LayerPeerFactory { class LayerPeerFactory {
public: public:
virtual ~LayerPeerFactory() = default; virtual ~LayerPeerFactory() = default;
/** /**
Get the corresponding core layer factory. Get the corresponding core layer factory.

View File

@@ -2,60 +2,56 @@
#include <mbgl/style/expression/expression.hpp> #include <mbgl/style/expression/expression.hpp>
const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase = @"MLNStyleFunctionOptionInterpolationBase"; const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase =
const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue = @"MLNStyleFunctionOptionDefaultValue"; @"MLNStyleFunctionOptionInterpolationBase";
const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue =
@"MLNStyleFunctionOptionDefaultValue";
id MLNJSONObjectFromMBGLValue(const mbgl::Value &value) { id MLNJSONObjectFromMBGLValue(const mbgl::Value &value) {
return value.match([](const mbgl::NullValue) -> id { return value.match(
return [NSNull null]; [](const mbgl::NullValue) -> id { return [NSNull null]; },
}, [](const bool value) { [](const bool value) { return @(value); }, [](const float value) { return @(value); },
return @(value); [](const int64_t value) { return @(value); }, [](const uint64_t value) { return @(value); },
}, [](const float value) { [](const double value) { return @(value); },
return @(value); [](const std::string &value) { return @(value.c_str()); },
}, [](const int64_t value) { [](const mbgl::Color &value) { return [MLNColor mgl_colorWithColor:value]; },
return @(value); [](const mbgl::style::Position &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<float, 3> spherical = value.getSpherical(); std::array<float, 3> 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]; return [NSValue valueWithMLNSphericalPosition:position];
}, [&](const std::vector<mbgl::Value> &vector) { },
[&](const std::vector<mbgl::Value> &vector) {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()]; NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()];
for (auto value : vector) { for (auto value : vector) {
[array addObject:MLNJSONObjectFromMBGLValue(value)]; [array addObject:MLNJSONObjectFromMBGLValue(value)];
} }
return array; return array;
}, [&](const std::unordered_map<std::string, mbgl::Value> &map) { },
[&](const std::unordered_map<std::string, mbgl::Value> &map) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:map.size()]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:map.size()];
for (auto &item : map) { for (auto &item : map) {
dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second); dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second);
} }
return dictionary; return dictionary;
}, [](const auto &) -> id { },
return nil; [](const auto &) -> id { return nil; });
});
} }
id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) { id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) {
return MLNJSONObjectFromMBGLValue(mbglExpression.serialize()); return MLNJSONObjectFromMBGLValue(mbglExpression.serialize());
} }
std::unique_ptr<mbgl::style::expression::Expression> MLNClusterPropertyFromNSExpression(
std::unique_ptr<mbgl::style::expression::Expression> MLNClusterPropertyFromNSExpression(NSExpression *expression) { NSExpression *expression) {
if (!expression) { if (!expression) {
return nullptr; 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;
} }

View File

@@ -61,7 +61,7 @@ id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mb
template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType, template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType,
typename ObjCEnum = ObjCType> typename ObjCEnum = ObjCType>
class MLNStyleValueTransformer { class MLNStyleValueTransformer {
public: public:
/// Convert an mbgl property value into an mgl style value /// Convert an mbgl property value into an mgl style value
NSExpression *toExpression(const mbgl::style::PropertyValue<MBGLType> &mbglValue) { NSExpression *toExpression(const mbgl::style::PropertyValue<MBGLType> &mbglValue) {
PropertyExpressionEvaluator evaluator; PropertyExpressionEvaluator evaluator;
@@ -139,7 +139,7 @@ class MLNStyleValueTransformer {
return *value; 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 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. 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 // Bool
static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); } static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); }

View File

@@ -1,16 +1,17 @@
#import "MLNTilePyramidOfflineRegion.h" #import "MLNTilePyramidOfflineRegion.h"
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR #if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#endif #endif
#import "MLNOfflineRegion_Private.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNStyle.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNOfflineRegion_Private.h"
#import "MLNStyle.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private, MLNTilePyramidOfflineRegion_Private> @interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private,
MLNTilePyramidOfflineRegion_Private>
@end @end
@@ -21,7 +22,7 @@
@synthesize styleURL = _styleURL; @synthesize styleURL = _styleURL;
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
-(NSDictionary *)offlineStartEventAttributes { - (NSDictionary *)offlineStartEventAttributes {
return @{}; return @{};
} }
@@ -31,22 +32,26 @@
- (instancetype)init { - (instancetype)init {
MLNLogInfo(@"Calling this initializer is not allowed."); MLNLogInfo(@"Calling this initializer is not allowed.");
[NSException raise:NSGenericException format: [NSException raise:NSGenericException
@"-[MLNTilePyramidOfflineRegion init] is unavailable. " format:@"-[MLNTilePyramidOfflineRegion init] is unavailable. "
@"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."]; @"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."];
return nil; return nil;
} }
- (instancetype)initWithStyleURL:(NSURL *)styleURL bounds:(MLNCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel { - (instancetype)initWithStyleURL:(NSURL *)styleURL
MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel); 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 (self = [super init]) {
if (!styleURL) { if (!styleURL) {
styleURL = [MLNStyle defaultStyleURL]; styleURL = [MLNStyle defaultStyleURL];
} }
if (!styleURL.scheme) { if (!styleURL.scheme) {
[NSException raise:MLNInvalidStyleURLException format: [NSException raise:MLNInvalidStyleURLException
@"%@ does not support setting a relative file URL as the style URL. " format:@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, " @"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. " @"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.", @"For Mapbox-hosted styles, use the mapbox: scheme.",
@@ -62,44 +67,53 @@
return self; return self;
} }
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineTilePyramidRegionDefinition &)definition { - (instancetype)initWithOfflineRegionDefinition:
(const mbgl::OfflineTilePyramidRegionDefinition &)definition {
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds); MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds);
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:definition.minZoom
toZoomLevel:definition.maxZoom];
result.includesIdeographicGlyphs = definition.includeIdeographs; result.includesIdeographicGlyphs = definition.includeIdeographs;
return result; return result;
} }
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition { - (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #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 #elif TARGET_OS_MAC
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor; const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
#endif #endif
return mbgl::OfflineTilePyramidRegionDefinition(_styleURL.absoluteString.UTF8String, return mbgl::OfflineTilePyramidRegionDefinition(
MLNLatLngBoundsFromCoordinateBounds(_bounds), _styleURL.absoluteString.UTF8String, MLNLatLngBoundsFromCoordinateBounds(_bounds),
_minimumZoomLevel, _maximumZoomLevel, _minimumZoomLevel, _maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs);
scaleFactor, _includesIdeographicGlyphs);
} }
- (nullable instancetype)initWithCoder:(NSCoder *)coder { - (nullable instancetype)initWithCoder:(NSCoder *)coder {
MLNLogInfo(@"Initializing with coder."); MLNLogInfo(@"Initializing with coder.");
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"]; NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
CLLocationCoordinate2D sw = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"], CLLocationCoordinate2D sw =
CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"],
[coder decodeDoubleForKey:@"southWestLongitude"]); [coder decodeDoubleForKey:@"southWestLongitude"]);
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"], CLLocationCoordinate2D ne =
CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"],
[coder decodeDoubleForKey:@"northEastLongitude"]); [coder decodeDoubleForKey:@"northEastLongitude"]);
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne); MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne);
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel]; MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:minimumZoomLevel
toZoomLevel:maximumZoomLevel];
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
return result; return result;
} }
- (void)encodeWithCoder:(NSCoder *)coder - (void)encodeWithCoder:(NSCoder *)coder {
{
[coder encodeObject:_styleURL forKey:@"styleURL"]; [coder encodeObject:_styleURL forKey:@"styleURL"];
[coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"]; [coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"];
[coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"]; [coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"];
@@ -111,7 +125,11 @@
} }
- (id)copyWithZone:(nullable NSZone *)zone { - (id)copyWithZone:(nullable NSZone *)zone {
MLNTilePyramidOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL bounds:_bounds fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel]; MLNTilePyramidOfflineRegion *result =
[[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
bounds:_bounds
fromZoomLevel:_minimumZoomLevel
toZoomLevel:_maximumZoomLevel];
result.includesIdeographicGlyphs = _includesIdeographicGlyphs; result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
return result; return result;
} }
@@ -125,19 +143,17 @@
} }
MLNTilePyramidOfflineRegion *otherRegion = other; MLNTilePyramidOfflineRegion *otherRegion = other;
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel return (_minimumZoomLevel == otherRegion->_minimumZoomLevel &&
&& _maximumZoomLevel == otherRegion->_maximumZoomLevel _maximumZoomLevel == otherRegion->_maximumZoomLevel &&
&& MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds) MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds) &&
&& [_styleURL isEqual:otherRegion->_styleURL] [_styleURL isEqual:otherRegion->_styleURL] &&
&& _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs); _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
} }
- (NSUInteger)hash { - (NSUInteger)hash {
return (_styleURL.hash return (_styleURL.hash + @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash +
+ @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash + @(_minimumZoomLevel).hash +
+ @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash @(_maximumZoomLevel).hash + @(_includesIdeographicGlyphs).hash);
+ @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash
+ @(_includesIdeographicGlyphs).hash);
} }
@end @end

View File

@@ -8,25 +8,30 @@
#import "NSValue+MLNAdditions.h" #import "NSValue+MLNAdditions.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#else #else
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#endif #endif
#include <mbgl/util/tileset.hpp> #include <mbgl/util/tileset.hpp>
const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel = @"MLNTileSourceOptionMinimumZoomLevel"; const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel =
const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel = @"MLNTileSourceOptionMaximumZoomLevel"; @"MLNTileSourceOptionMinimumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds = @"MLNTileSourceOptionCoordinateBounds"; const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel =
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString = @"MLNTileSourceOptionAttributionHTMLString"; @"MLNTileSourceOptionMaximumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos = @"MLNTileSourceOptionAttributionInfos"; const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds =
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = @"MLNTileSourceOptionTileCoordinateSystem"; @"MLNTileSourceOptionCoordinateBounds";
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString =
@"MLNTileSourceOptionAttributionHTMLString";
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos =
@"MLNTileSourceOptionAttributionInfos";
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem =
@"MLNTileSourceOptionTileCoordinateSystem";
@implementation MLNTileSource @implementation MLNTileSource
- (NSURL *)configurationURL { - (NSURL *)configurationURL {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
format:@"MLNTileSource is an abstract class"];
return nil; return nil;
} }
@@ -34,21 +39,23 @@ const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = @"MLNTileSou
return [self attributionInfosWithFontSize:0 linkColor:nil]; return [self attributionInfosWithFontSize:0 linkColor:nil];
} }
- (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor { - (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize
linkColor:(nullable MLNColor *)linkColor {
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
fontSize:fontSize fontSize:fontSize
linkColor:linkColor]; linkColor:linkColor];
} }
- (NSString *)attributionHTMLString { - (NSString *)attributionHTMLString {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
format:@"MLNTileSource is an abstract class"];
return nil; return nil;
} }
@end @end
mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplates, NSDictionary<MLNTileSourceOption, id> * _Nullable options) { mbgl::Tileset MLNTileSetFromTileURLTemplates(
NSArray<NSString *> *tileURLTemplates,
NSDictionary<MLNTileSourceOption, id> *_Nullable options) {
mbgl::Tileset tileSet; mbgl::Tileset tileSet;
for (NSString *tileURLTemplate in tileURLTemplates) { for (NSString *tileURLTemplate in tileURLTemplates) {
@@ -73,14 +80,16 @@ mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplat
} }
if (tileSet.zoomRange.min > tileSet.zoomRange.max) { if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than MLNTileSourceOptionMaximumZoomLevel."]; format:@"MLNTileSourceOptionMinimumZoomLevel must be less than "
@"MLNTileSourceOptionMaximumZoomLevel."];
} }
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) { if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
if (![coordinateBounds isKindOfClass:[NSValue class]] if (![coordinateBounds isKindOfClass:[NSValue class]] &&
&& strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) { strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue containing an MLNCoordinateBounds."]; format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue "
@"containing an MLNCoordinateBounds."];
} }
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue); tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
} }
@@ -99,24 +108,30 @@ mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplat
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."]; format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
} }
NSAttributedString *attributedString = [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos]; NSAttributedString *attributedString =
[MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
static NSString * const NSExcludedElementsDocumentAttribute = @"ExcludedElements"; static NSString *const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
#endif #endif
NSDictionary *documentAttributes = @{ NSDictionary *documentAttributes = @{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding), NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
// The attribution string is meant to be a simple, inline fragment, not a full-fledged, validating document. // The attribution string is meant to be a simple, inline fragment, not a full-fledged,
NSExcludedElementsDocumentAttribute: @[@"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p"], // validating document.
NSExcludedElementsDocumentAttribute :
@[ @"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p" ],
}; };
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange documentAttributes:documentAttributes error:NULL]; NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange
documentAttributes:documentAttributes
error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
tileSet.attribution = html.UTF8String; tileSet.attribution = html.UTF8String;
} }
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) { if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) { if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException [NSException
raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."]; format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
} }
MLNTileCoordinateSystem tileCoordinateSystem; MLNTileCoordinateSystem tileCoordinateSystem;
@@ -152,7 +167,8 @@ mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplat
encodingNumber = options[MLNVectorTileSourceOptionEncoding]; encodingNumber = options[MLNVectorTileSourceOptionEncoding];
if (encodingNumber) { if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) { if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException [NSException
raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."]; format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
} }
MLNVectorTileSourceEncoding encoding; MLNVectorTileSourceEncoding encoding;

View File

@@ -1,12 +1,13 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <mbgl/util/feature.hpp>
#import <mbgl/util/geometry.hpp> #import <mbgl/util/geometry.hpp>
/** /**
Recursively transforms a C++ type into the corresponding Foundation type. Recursively transforms a C++ type into the corresponding Foundation type.
*/ */
class ValueEvaluator { class ValueEvaluator {
public: public:
id operator()(const mbgl::NullValue &) const { return [NSNull null]; } id operator()(const mbgl::NullValue &) const { return [NSNull null]; }
id operator()(const bool &value) const { return value ? @YES : @NO; } id operator()(const bool &value) const { return value ? @YES : @NO; }

View File

@@ -4,27 +4,25 @@
@implementation MLNVectorStyleLayer @implementation MLNVectorStyleLayer
- (void)setPredicate:(NSPredicate *)predicate { - (void)setPredicate:(NSPredicate *)predicate {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
format:@"MLNVectorStyleLayer is an abstract class"];
} }
- (NSPredicate *)predicate { - (NSPredicate *)predicate {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
format:@"MLNVectorStyleLayer is an abstract class"];
return nil; return nil;
} }
- (NSString *)description { - (NSString *)description {
if (self.rawLayer) { if (self.rawLayer) {
return [NSString stringWithFormat: return
@"<%@: %p; identifier = %@; sourceIdentifier = %@; " [NSString stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; "
@"sourceLayerIdentifier = %@; predicate = %@; visible = %@>", @"sourceLayerIdentifier = %@; predicate = %@; visible = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, NSStringFromClass([self class]), (void *)self, self.identifier,
self.sourceIdentifier, self.sourceLayerIdentifier, self.predicate, self.sourceIdentifier, self.sourceLayerIdentifier,
self.visible ? @"YES" : @"NO"]; self.predicate, self.visible ? @"YES" : @"NO"];
} } else {
else { return [NSString
return [NSString stringWithFormat: stringWithFormat:
@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; " @"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; "
@"sourceLayerIdentifier = <unknown>; predicate = <unknown>; visible = <unknown>>", @"sourceLayerIdentifier = <unknown>; predicate = <unknown>; visible = <unknown>>",
NSStringFromClass([self class]), (void *)self, self.identifier]; NSStringFromClass([self class]), (void *)self, self.identifier];

View File

@@ -2,17 +2,17 @@
#import "MLNFeature_Private.h" #import "MLNFeature_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNSource_Private.h"
#import "MLNTileSource_Private.h"
#import "MLNStyle_Private.h"
#import "MLNMapView_Private.h" #import "MLNMapView_Private.h"
#import "MLNSource_Private.h"
#import "MLNStyle_Private.h"
#import "MLNTileSource_Private.h"
#import "NSPredicate+MLNPrivateAdditions.h" #import "NSPredicate+MLNPrivateAdditions.h"
#import "NSURL+MLNAdditions.h" #import "NSURL+MLNAdditions.h"
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/renderer/renderer.hpp> #include <mbgl/renderer/renderer.hpp>
#include <mbgl/style/sources/vector_source.hpp>
const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSourceOptionEncoding"; const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSourceOptionEncoding";
@@ -24,24 +24,25 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
@implementation MLNVectorTileSource @implementation MLNVectorTileSource
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL { - (instancetype)initWithIdentifier:(NSString *)identifier
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, configurationURL:(NSURL *)configurationURL {
auto source = std::make_unique<mbgl::style::VectorSource>(
identifier.UTF8String,
configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String); configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier - (instancetype)initWithIdentifier:(NSString *)identifier
configurationURLString:(NSString *)configurationURLString configurationURLString:(NSString *)configurationURLString {
{ auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String,
auto source = std::make_unique<mbgl::style::VectorSource>( configurationURLString.UTF8String);
identifier.UTF8String,
configurationURLString.UTF8String
);
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates options:(nullable NSDictionary<MLNTileSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet); auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet);
return self = [super initWithPendingSource:std::move(source)]; return self = [super initWithPendingSource:std::move(source)];
@@ -59,7 +60,8 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
- (NSString *)attributionHTMLString { - (NSString *)attributionHTMLString {
if (!self.rawSource) { if (!self.rawSource) {
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier); MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
self.identifier);
return nil; return nil;
} }
@@ -67,13 +69,16 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
return attribution ? @(attribution->c_str()) : nil; return attribution ? @(attribution->c_str()) : nil;
} }
- (NSArray<id <MLNFeature>> *)featuresInSourceLayersWithIdentifiers:(NSSet<NSString *> *)sourceLayerIdentifiers predicate:(nullable NSPredicate *)predicate { - (NSArray<id<MLNFeature>> *)
featuresInSourceLayersWithIdentifiers:(NSSet<NSString *> *)sourceLayerIdentifiers
predicate:(nullable NSPredicate *)predicate {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
std::optional<std::vector<std::string>> optionalSourceLayerIDs; std::optional<std::vector<std::string>> optionalSourceLayerIDs;
if (sourceLayerIdentifiers) { if (sourceLayerIdentifiers) {
__block std::vector<std::string> layerIDs; __block std::vector<std::string> layerIDs;
layerIDs.reserve(sourceLayerIdentifiers.count); layerIDs.reserve(sourceLayerIdentifiers.count);
[sourceLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { [sourceLayerIdentifiers
enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) {
layerIDs.push_back(identifier.UTF8String); layerIDs.push_back(identifier.UTF8String);
}]; }];
optionalSourceLayerIDs = layerIDs; optionalSourceLayerIDs = layerIDs;
@@ -87,7 +92,8 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
std::vector<mbgl::Feature> features; std::vector<mbgl::Feature> features;
if ([self.stylable isKindOfClass:[MLNMapView class]]) { if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable; MLNMapView *mapView = (MLNMapView *)self.stylable;
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { optionalSourceLayerIDs, optionalFilter }); features = mapView.renderer->querySourceFeatures(self.rawSource->getID(),
{optionalSourceLayerIDs, optionalFilter});
} }
return MLNFeaturesFromMBGLFeatures(features); return MLNFeaturesFromMBGLFeatures(features);
} }
@@ -102,18 +108,38 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
https://www.mapbox.com/vector-tiles/mapbox-streets-v8/ https://www.mapbox.com/vector-tiles/mapbox-streets-v8/
*/ */
static NSArray * const MLNMapboxStreetsLanguages = @[ static NSArray *const MLNMapboxStreetsLanguages = @[
@"ar", @"de", @"en", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi", @"ar",
@"zh-Hans", @"zh-Hant", @"de",
@"en",
@"es",
@"fr",
@"ja",
@"ko",
@"pt",
@"ru",
@"vi",
@"zh-Hans",
@"zh-Hant",
]; ];
/** /**
Like ``MLNMapboxStreetsLanguages``, but deanglicized for use with Like ``MLNMapboxStreetsLanguages``, but deanglicized for use with
`+[NSBundle preferredLocalizationsFromArray:forPreferences:]`. `+[NSBundle preferredLocalizationsFromArray:forPreferences:]`.
*/ */
static NSArray * const MLNMapboxStreetsAlternativeLanguages = @[ static NSArray *const MLNMapboxStreetsAlternativeLanguages = @[
@"mul", @"ar", @"de", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi", @"mul",
@"zh-Hans", @"zh-Hant", @"ar",
@"de",
@"es",
@"fr",
@"ja",
@"ko",
@"pt",
@"ru",
@"vi",
@"zh-Hans",
@"zh-Hant",
]; ];
+ (NSSet<NSString *> *)mapboxStreetsLanguages { + (NSSet<NSString *> *)mapboxStreetsLanguages {
@@ -130,23 +156,29 @@ static NSArray * const MLNMapboxStreetsAlternativeLanguages = @[
} }
+ (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray { + (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray {
BOOL acceptsEnglish = [preferencesArray filteredArrayUsingPredicate: BOOL acceptsEnglish =
[NSPredicate predicateWithBlock:^BOOL(NSString * _Nullable language, NSDictionary<NSString *,id> * _Nullable bindings) { [preferencesArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
NSString *_Nullable language,
NSDictionary<NSString *, id>
*_Nullable bindings) {
NSString *languageCode; NSString *languageCode;
if (@available(iOS 10.0, macOS 10.12.0, *)) { if (@available(iOS 10.0, macOS 10.12.0, *)) {
languageCode = [NSLocale localeWithLocaleIdentifier:language].languageCode; languageCode =
} [NSLocale localeWithLocaleIdentifier:language].languageCode;
else { } else {
languageCode = [[NSLocale localeWithLocaleIdentifier:language] objectForKey:NSLocaleLanguageCode]; languageCode = [[NSLocale localeWithLocaleIdentifier:language]
objectForKey:NSLocaleLanguageCode];
} }
return [languageCode isEqualToString:@"en"]; return [languageCode isEqualToString:@"en"];
}]].count; }]]
.count;
NSArray<NSString *> *availableLanguages = acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages; NSArray<NSString *> *availableLanguages =
NSArray<NSString *> *preferredLanguages = [NSBundle preferredLocalizationsFromArray:availableLanguages acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages;
forPreferences:preferencesArray]; NSArray<NSString *> *preferredLanguages =
[NSBundle preferredLocalizationsFromArray:availableLanguages forPreferences:preferencesArray];
NSString *mostSpecificLanguage; NSString *mostSpecificLanguage;
for (NSString *language in preferredLanguages) { for (NSString *language in preferredLanguages) {
if (language.length > mostSpecificLanguage.length) { if (language.length > mostSpecificLanguage.length) {
@@ -162,7 +194,8 @@ static NSArray * const MLNMapboxStreetsAlternativeLanguages = @[
return NO; return NO;
} }
NSArray *identifiers = [url.host componentsSeparatedByString:@","]; NSArray *identifiers = [url.host componentsSeparatedByString:@","];
return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] || [identifiers containsObject:@"mapbox.mapbox-streets-v7"]; return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] ||
[identifiers containsObject:@"mapbox.mapbox-streets-v7"];
} }
@end @end

View File

@@ -1,8 +1,8 @@
#import "NSArray+MLNAdditions.h" #import "NSArray+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#import "NSDictionary+MLNAdditions.h" #import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h" #import "NSExpression+MLNPrivateAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
@implementation NSArray (MLNAdditions) @implementation NSArray (MLNAdditions)
@@ -29,7 +29,8 @@
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init]; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
BOOL isSubsequentItem = NO; BOOL isSubsequentItem = NO;
for (NSAttributedString *component in self) { for (NSAttributedString *component in self) {
MLNAssert([component isKindOfClass:[NSAttributedString class]], @"Items in array must be attributed strings."); MLNAssert([component isKindOfClass:[NSAttributedString class]],
@"Items in array must be attributed strings.");
if (isSubsequentItem) { if (isSubsequentItem) {
[attributedString appendAttributedString:attributedSeparator]; [attributedString appendAttributedString:attributedSeparator];
} }
@@ -42,22 +43,22 @@
+ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector<CLLocationCoordinate2D>)coords { + (NSArray *)mgl_coordinatesFromCoordinates:(std::vector<CLLocationCoordinate2D>)coords {
NSMutableArray *coordinates = [NSMutableArray array]; NSMutableArray *coordinates = [NSMutableArray array];
for (auto coord : coords) { for (auto coord : coords) {
[coordinates addObject:@{@"latitude": @(coord.latitude), [coordinates addObject:@{@"latitude" : @(coord.latitude), @"longitude" : @(coord.longitude)}];
@"longitude": @(coord.longitude)}];
} }
return coordinates; return coordinates;
} }
- (std::vector<CLLocationCoordinate2D>)mgl_coordinates { - (std::vector<CLLocationCoordinate2D>)mgl_coordinates {
NSUInteger numberOfCoordinates = [self count]; NSUInteger numberOfCoordinates = [self count];
CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D)); CLLocationCoordinate2D *coords =
(CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
for (NSUInteger i = 0; i < numberOfCoordinates; i++) { for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue], coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue],
[self[i][@"longitude"] doubleValue]); [self[i][@"longitude"] doubleValue]);
} }
std::vector<CLLocationCoordinate2D> coordinates = { coords, coords + numberOfCoordinates }; std::vector<CLLocationCoordinate2D> coordinates = {coords, coords + numberOfCoordinates};
free(coords); free(coords);
return coordinates; return coordinates;

View File

@@ -18,7 +18,9 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException
bundle = [self bundleWithPath:bundlePath]; bundle = [self bundleWithPath:bundlePath];
} else { } else {
[NSException raise:MLNBundleNotFoundException [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."]; 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 #endif
@@ -40,8 +42,8 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \ #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)) (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. // Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a
// 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool // unit test bundle. 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool
if (!bundleIdentifier) { if (!bundleIdentifier) {
bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier; bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier;
} }

View File

@@ -5,23 +5,26 @@
@implementation NSCoder (MLNAdditions) @implementation NSCoder (MLNAdditions)
- (void)mgl_encodeLocationCoordinates2D:(std::vector<CLLocationCoordinate2D>)coordinates forKey:(NSString *)key { - (void)mgl_encodeLocationCoordinates2D:(std::vector<CLLocationCoordinate2D>)coordinates
forKey:(NSString *)key {
[self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key]; [self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key];
} }
- (std::vector<CLLocationCoordinate2D>)mgl_decodeLocationCoordinates2DForKey:(NSString *)key { - (std::vector<CLLocationCoordinate2D>)mgl_decodeLocationCoordinates2DForKey:(NSString *)key {
NSSet<Class> *coordinateClasses = [NSSet setWithArray:@[[NSArray class], [NSDictionary class]]]; NSSet<Class> *coordinateClasses = [NSSet setWithArray:@[ [NSArray class], [NSDictionary class] ]];
NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key]; NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key];
return [coordinates mgl_coordinates]; return [coordinates mgl_coordinates];
} }
- (void)encodeMLNCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key { - (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 { - (CLLocationCoordinate2D)decodeMLNCoordinateForKey:(NSString *)key {
NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key]; NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key];
return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue], [coordinate[@"longitude"] doubleValue]); return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue],
[coordinate[@"longitude"] doubleValue]);
} }
@end @end

View File

@@ -2,8 +2,8 @@
#import "MLNStyleValue_Private.h" #import "MLNStyleValue_Private.h"
#import "NSPredicate+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h" #import "NSExpression+MLNPrivateAdditions.h"
#import "NSPredicate+MLNAdditions.h"
@implementation NSComparisonPredicate (MLNAdditions) @implementation NSComparisonPredicate (MLNAdditions)
@@ -19,7 +19,8 @@
} }
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; format:@"Comparison predicate must compare an attribute (as a key path) to a "
@"constant or vice versa."];
return nil; return nil;
} }
@@ -35,7 +36,8 @@
value = leftExpression.mgl_constantMBGLValue; value = leftExpression.mgl_constantMBGLValue;
} else { } else {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; format:@"Comparison predicate must compare an attribute (as a key path) to a "
@"constant or vice versa."];
} }
return value; return value;
} }
@@ -52,7 +54,8 @@
type = leftExpression.mgl_featureType; type = leftExpression.mgl_featureType;
} else { } else {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; format:@"Comparison predicate must compare an attribute (as a key path) to a "
@"constant or vice versa."];
} }
return type; return type;
} }
@@ -69,7 +72,8 @@
identifier = leftExpression.mgl_featureIdentifier; identifier = leftExpression.mgl_featureIdentifier;
} else { } else {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."]; format:@"Comparison predicate must compare an attribute (as a key path) to a "
@"constant or vice versa."];
} }
return identifier; return identifier;
} }
@@ -88,18 +92,23 @@
if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) { if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) {
// https://github.com/mapbox/mapbox-gl-js/issues/9339 // https://github.com/mapbox/mapbox-gl-js/issues/9339
if (self.options) { if (self.options) {
[NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."]; [NSException
raise:NSInvalidArgumentException
format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."];
} }
NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression NSPredicate *directPredicate =
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
rightExpression:self.leftExpression rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType type:NSInPredicateOperatorType
options:self.options]; options:self.options];
NSPredicate *invertedPredicate = [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate]; NSPredicate *invertedPredicate =
[NSCompoundPredicate notPredicateWithSubpredicate:directPredicate];
return invertedPredicate.mgl_jsonExpressionObject; return invertedPredicate.mgl_jsonExpressionObject;
} else { } else {
[NSException raise:NSInvalidArgumentException format:@"“ALL” is only supported for the “!=” operator."]; [NSException raise:NSInvalidArgumentException
format:@"“ALL” is only supported for the “!=” operator."];
} }
case NSAnyPredicateModifier: case NSAnyPredicateModifier:
@@ -107,22 +116,27 @@
if (self.predicateOperatorType == NSEqualToPredicateOperatorType) { if (self.predicateOperatorType == NSEqualToPredicateOperatorType) {
// https://github.com/mapbox/mapbox-gl-js/issues/9339 // https://github.com/mapbox/mapbox-gl-js/issues/9339
if (self.options) { if (self.options) {
[NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."]; [NSException
raise:NSInvalidArgumentException
format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."];
} }
NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression NSPredicate *directPredicate =
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
rightExpression:self.leftExpression rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType type:NSInPredicateOperatorType
options:self.options]; options:self.options];
return directPredicate.mgl_jsonExpressionObject; return directPredicate.mgl_jsonExpressionObject;
} else { } else {
[NSException raise:NSInvalidArgumentException format:@"“ANY” or “SOME” is only supported for the “=” operator."]; [NSException raise:NSInvalidArgumentException
format:@"“ANY” or “SOME” is only supported for the “=” operator."];
} }
default: default:
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"NSComparisonPredicateModifier:%lu is not supported.", (unsigned long)self.comparisonPredicateModifier]; format:@"NSComparisonPredicateModifier:%lu is not supported.",
(unsigned long)self.comparisonPredicateModifier];
} }
NSString *op; NSString *op;
@@ -148,21 +162,25 @@
case NSBetweenPredicateOperatorType: { case NSBetweenPredicateOperatorType: {
op = @"all"; op = @"all";
NSArray *limits = self.rightExpression.constantValue; NSArray *limits = self.rightExpression.constantValue;
NSPredicate *leftHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:limits[0] NSPredicate *leftHandPredicate = [NSComparisonPredicate
predicateWithLeftExpression:limits[0]
rightExpression:self.leftExpression rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType type:NSLessThanOrEqualToPredicateOperatorType
options:0]; options:0];
NSPredicate *rightHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression NSPredicate *rightHandPredicate = [NSComparisonPredicate
predicateWithLeftExpression:self.leftExpression
rightExpression:limits[1] rightExpression:limits[1]
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType type:NSLessThanOrEqualToPredicateOperatorType
options:0]; options:0];
return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject]; return @[
op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject
];
} }
case NSInPredicateOperatorType: { case NSInPredicateOperatorType: {
if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) { if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) {
return @[@"within", self.rightExpression.mgl_jsonExpressionObject]; return @[ @"within", self.rightExpression.mgl_jsonExpressionObject ];
} }
// An “in” expression comparing two string literals is unfortunately // An “in” expression comparing two string literals is unfortunately
// misinterpreted as a legacy “in” filter due to ambiguity. Wrap one // misinterpreted as a legacy “in” filter due to ambiguity. Wrap one
@@ -172,8 +190,10 @@
self.rightExpression.expressionType == NSConstantValueExpressionType && self.rightExpression.expressionType == NSConstantValueExpressionType &&
[self.leftExpression.constantValue isKindOfClass:[NSString class]] && [self.leftExpression.constantValue isKindOfClass:[NSString class]] &&
[self.rightExpression.constantValue isKindOfClass:[NSString class]]) { [self.rightExpression.constantValue isKindOfClass:[NSString class]]) {
NSExpression *rightLiteralExpression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression]; NSExpression *rightLiteralExpression = [NSExpression
NSPredicate *literalPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression];
NSPredicate *literalPredicate =
[NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
rightExpression:rightLiteralExpression rightExpression:rightLiteralExpression
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType type:NSInPredicateOperatorType
@@ -184,7 +204,8 @@
break; break;
} }
case NSContainsPredicateOperatorType: { case NSContainsPredicateOperatorType: {
NSPredicate *inPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression NSPredicate *inPredicate =
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
rightExpression:self.leftExpression rightExpression:self.leftExpression
modifier:self.comparisonPredicateModifier modifier:self.comparisonPredicateModifier
type:NSInPredicateOperatorType type:NSInPredicateOperatorType
@@ -197,18 +218,21 @@
case NSEndsWithPredicateOperatorType: case NSEndsWithPredicateOperatorType:
case NSCustomSelectorPredicateOperatorType: case NSCustomSelectorPredicateOperatorType:
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType]; format:@"NSPredicateOperatorType:%lu is not supported.",
(unsigned long)self.predicateOperatorType];
} }
if (!op) { if (!op) {
return nil; return nil;
} }
NSArray *comparisonArray = @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject]; NSArray *comparisonArray = @[
op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject
];
if (self.options) { if (self.options) {
NSDictionary *collatorObject = @{ NSDictionary *collatorObject = @{
@"case-sensitive": @(!(self.options & NSCaseInsensitivePredicateOption)), @"case-sensitive" : @(!(self.options & NSCaseInsensitivePredicateOption)),
@"diacritic-sensitive": @(!(self.options & NSDiacriticInsensitivePredicateOption)), @"diacritic-sensitive" : @(!(self.options & NSDiacriticInsensitivePredicateOption)),
}; };
return [comparisonArray arrayByAddingObject:@[@"collator", collatorObject]]; return [comparisonArray arrayByAddingObject:@[ @"collator", collatorObject ]];
} }
return comparisonArray; return comparisonArray;
} }

View File

@@ -2,17 +2,16 @@
#import "MLNStyleValue_Private.h" #import "MLNStyleValue_Private.h"
#import "NSPredicate+MLNPrivateAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "NSPredicate+MLNPrivateAdditions.h"
#include <mbgl/style/conversion/property_value.hpp> #include <mbgl/style/conversion/property_value.hpp>
@implementation NSCompoundPredicate (MLNAdditions) @implementation NSCompoundPredicate (MLNAdditions)
- (std::vector<mbgl::style::Filter>)mgl_subfilters - (std::vector<mbgl::style::Filter>)mgl_subfilters {
{ std::vector<mbgl::style::Filter> filters;
std::vector<mbgl::style::Filter>filters;
for (NSPredicate *predicate in self.subpredicates) { for (NSPredicate *predicate in self.subpredicates) {
filters.push_back(predicate.mgl_filter); filters.push_back(predicate.mgl_filter);
} }
@@ -26,24 +25,24 @@
- (id)mgl_jsonExpressionObject { - (id)mgl_jsonExpressionObject {
switch (self.compoundPredicateType) { switch (self.compoundPredicateType) {
case NSNotPredicateType: { case NSNotPredicateType: {
MLNAssert(self.subpredicates.count <= 1, @"NOT predicate cannot have multiple subpredicates."); MLNAssert(self.subpredicates.count <= 1,
@"NOT predicate cannot have multiple subpredicates.");
NSPredicate *subpredicate = self.subpredicates.firstObject; NSPredicate *subpredicate = self.subpredicates.firstObject;
return @[@"!", subpredicate.mgl_jsonExpressionObject]; return @[ @"!", subpredicate.mgl_jsonExpressionObject ];
} }
case NSAndPredicateType: { case NSAndPredicateType: {
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return [@[@"all"] arrayByAddingObjectsFromArray:subarrays]; return [@[ @"all" ] arrayByAddingObjectsFromArray:subarrays];
} }
case NSOrPredicateType: { case NSOrPredicateType: {
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"]; NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return [@[@"any"] arrayByAddingObjectsFromArray:subarrays]; return [@[ @"any" ] arrayByAddingObjectsFromArray:subarrays];
} }
} }
[NSException raise:@"Compound predicate type not handled" [NSException raise:@"Compound predicate type not handled" format:@""];
format:@""];
return nil; return nil;
} }

View File

@@ -1,12 +1,11 @@
#import "NSDate+MLNAdditions.h"
#import <ratio> #import <ratio>
#import "NSDate+MLNAdditions.h"
mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration) mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration) {
{ return std::chrono::duration_cast<mbgl::Duration>(
return std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(duration)); std::chrono::duration<NSTimeInterval>(duration));
} }
NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration) NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration) {
{
return std::chrono::duration<NSTimeInterval, std::ratio<1>>(duration).count(); return std::chrono::duration<NSTimeInterval, std::ratio<1>>(duration).count();
} }

View File

@@ -1,7 +1,7 @@
#import "NSDictionary+MLNAdditions.h" #import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "NSArray+MLNAdditions.h" #import "NSArray+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
@implementation NSDictionary (MLNAdditions) @implementation NSDictionary (MLNAdditions)

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
#import "NSPredicate+MLNPrivateAdditions.h" #import "NSPredicate+MLNPrivateAdditions.h"
#import "MLNValueEvaluator.h"
#import "MLNStyleValue_Private.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNStyleValue_Private.h"
#import "MLNValueEvaluator.h"
#include <mbgl/style/conversion/filter.hpp> #include <mbgl/style/conversion/filter.hpp>
@implementation NSPredicate (MLNPrivateAdditions) @implementation NSPredicate (MLNPrivateAdditions)
- (mbgl::style::Filter)mgl_filter - (mbgl::style::Filter)mgl_filter {
{
mbgl::style::conversion::Error valueError; mbgl::style::conversion::Error valueError;
NSArray *jsonObject = self.mgl_jsonExpressionObject; NSArray *jsonObject = self.mgl_jsonExpressionObject;
auto value = mbgl::style::conversion::convert<mbgl::style::Filter>(mbgl::style::conversion::makeConvertible(jsonObject), valueError); auto value = mbgl::style::conversion::convert<mbgl::style::Filter>(
mbgl::style::conversion::makeConvertible(jsonObject), valueError);
if (!value) { if (!value) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
@@ -24,8 +24,7 @@
return filter; return filter;
} }
+ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter + (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter {
{
if (filter.expression) { if (filter.expression) {
id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression); id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression);
return [NSPredicate predicateWithMLNJSONObject:jsonObject]; return [NSPredicate predicateWithMLNJSONObject:jsonObject];
@@ -47,14 +46,14 @@ NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) {
return subpredicates; return subpredicates;
} }
static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{ static NSDictionary *const MLNPredicateOperatorTypesByJSONOperator = @{
@"==": @(NSEqualToPredicateOperatorType), @"==" : @(NSEqualToPredicateOperatorType),
@"!=": @(NSNotEqualToPredicateOperatorType), @"!=" : @(NSNotEqualToPredicateOperatorType),
@"<": @(NSLessThanPredicateOperatorType), @"<" : @(NSLessThanPredicateOperatorType),
@"<=": @(NSLessThanOrEqualToPredicateOperatorType), @"<=" : @(NSLessThanOrEqualToPredicateOperatorType),
@">": @(NSGreaterThanPredicateOperatorType), @">" : @(NSGreaterThanPredicateOperatorType),
@">=": @(NSGreaterThanOrEqualToPredicateOperatorType), @">=" : @(NSGreaterThanOrEqualToPredicateOperatorType),
@"in": @(NSInPredicateOperatorType), @"in" : @(NSInPredicateOperatorType),
}; };
+ (instancetype)predicateWithMLNJSONObject:(id)object { + (instancetype)predicateWithMLNJSONObject:(id)object {
@@ -65,21 +64,25 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
return [NSPredicate predicateWithValue:NO]; return [NSPredicate predicateWithValue:NO];
} }
MLNAssert([object isKindOfClass:[NSArray class]], @"Condition for case expression should be an expression."); MLNAssert([object isKindOfClass:[NSArray class]],
@"Condition for case expression should be an expression.");
NSArray *objects = (NSArray *)object; NSArray *objects = (NSArray *)object;
NSString *op = objects.firstObject; NSString *op = objects.firstObject;
NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op]; NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op];
if (operatorTypeNumber) { if (operatorTypeNumber) {
NSPredicateOperatorType operatorType = (NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue]; NSPredicateOperatorType operatorType =
(NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue];
NSComparisonPredicateOptions options = 0; NSComparisonPredicateOptions options = 0;
if (objects.count > 3) { if (objects.count > 3) {
NSArray *collatorExpression = objects[3]; NSArray *collatorExpression = objects[3];
MLNCAssert([collatorExpression isKindOfClass:[NSArray class]], @"Collators must be dictionaries."); MLNCAssert([collatorExpression isKindOfClass:[NSArray class]],
@"Collators must be dictionaries.");
MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression"); MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression");
NSDictionary *collator = collatorExpression[1]; NSDictionary *collator = collatorExpression[1];
MLNCAssert([collator isKindOfClass:[NSDictionary class]], @"Malformed collator in collator expression"); MLNCAssert([collator isKindOfClass:[NSDictionary class]],
@"Malformed collator in collator expression");
// Predicate options can’t express specific locales as collators can. // Predicate options can’t express specific locales as collators can.
if (!collator[@"locale"]) { if (!collator[@"locale"]) {
@@ -92,7 +95,8 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
} }
} }
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0] return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
rightExpression:subexpressions[1] rightExpression:subexpressions[1]
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
@@ -101,9 +105,11 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
} }
if ([op isEqualToString:@"!"]) { if ([op isEqualToString:@"!"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count > 1) { if (subpredicates.count > 1) {
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; NSCompoundPredicate *predicate =
[NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate]; return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
} }
if (subpredicates.count) { if (subpredicates.count) {
@@ -112,7 +118,8 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
return [NSPredicate predicateWithValue:YES]; return [NSPredicate predicateWithValue:YES];
} }
if ([op isEqualToString:@"all"]) { if ([op isEqualToString:@"all"]) {
NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count == 2) { if (subpredicates.count == 2) {
// Determine if the expression is of BETWEEN type // Determine if the expression is of BETWEEN type
if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] && if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] &&
@@ -123,41 +130,51 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
NSArray *limits; NSArray *limits;
NSExpression *leftConditionExpression; NSExpression *leftConditionExpression;
if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType && if (leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) { rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
limits = @[leftCondition.rightExpression, rightCondition.rightExpression]; limits = @[ leftCondition.rightExpression, rightCondition.rightExpression ];
leftConditionExpression = leftCondition.leftExpression; leftConditionExpression = leftCondition.leftExpression;
} else if (leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType && } else if (leftCondition.predicateOperatorType ==
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) { NSLessThanOrEqualToPredicateOperatorType &&
limits = @[leftCondition.leftExpression, rightCondition.rightExpression]; rightCondition.predicateOperatorType ==
NSLessThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.leftExpression, rightCondition.rightExpression ];
leftConditionExpression = leftCondition.rightExpression; leftConditionExpression = leftCondition.rightExpression;
} else if(leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType && } else if (leftCondition.predicateOperatorType ==
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) { NSLessThanOrEqualToPredicateOperatorType &&
limits = @[leftCondition.leftExpression, rightCondition.leftExpression]; rightCondition.predicateOperatorType ==
NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.leftExpression, rightCondition.leftExpression ];
leftConditionExpression = leftCondition.rightExpression; leftConditionExpression = leftCondition.rightExpression;
} else if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType && } else if (leftCondition.predicateOperatorType ==
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) { NSGreaterThanOrEqualToPredicateOperatorType &&
limits = @[leftCondition.rightExpression, rightCondition.leftExpression]; rightCondition.predicateOperatorType ==
NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.rightExpression, rightCondition.leftExpression ];
leftConditionExpression = leftCondition.leftExpression; leftConditionExpression = leftCondition.leftExpression;
} }
if (limits && leftConditionExpression) { if (limits && leftConditionExpression) {
return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression, [NSExpression expressionForAggregate:limits]]; return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression,
[NSExpression expressionForAggregate:limits]];
} }
} }
} }
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]; return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
} }
if ([op isEqualToString:@"any"]) { if ([op isEqualToString:@"any"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates]; return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
} }
if ([op isEqualToString:@"within"]) { if ([op isEqualToString:@"within"]) {
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject] [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSComparisonPredicate
predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
rightExpression:subexpressions[0] rightExpression:subexpressions[0]
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType type:NSInPredicateOperatorType
@@ -165,12 +182,12 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
} }
NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object];
return [NSComparisonPredicate predicateWithLeftExpression:expression return [NSComparisonPredicate
predicateWithLeftExpression:expression
rightExpression:[NSExpression expressionForConstantValue:@YES] rightExpression:[NSExpression expressionForConstantValue:@YES]
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType type:NSEqualToPredicateOperatorType
options:0]; options:0];
} }
- (id)mgl_jsonExpressionObject { - (id)mgl_jsonExpressionObject {
@@ -186,8 +203,7 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
format:@"Block-based predicates are not supported."]; format:@"Block-based predicates are not supported."];
} }
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException format:@"Unrecognized predicate type."];
format:@"Unrecognized predicate type."];
return nil; return nil;
} }
@@ -196,7 +212,6 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
@implementation NSPredicate (MLNExpressionAdditions) @implementation NSPredicate (MLNExpressionAdditions)
- (id)mgl_if:(id)firstValue, ... { - (id)mgl_if:(id)firstValue, ... {
if ([self evaluateWithObject:nil]) { if ([self evaluateWithObject:nil]) {
return firstValue; return firstValue;
} }

View File

@@ -1,15 +1,14 @@
#import "NSValue+MLNStyleAttributeAdditions.h" #import "MLNGeometry_Private.h"
#import "MLNLight.h" #import "MLNLight.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNGeometry_Private.h" #import "NSValue+MLNStyleAttributeAdditions.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#endif #endif
@implementation NSValue (MLNStyleAttributeAdditions) @implementation NSValue (MLNStyleAttributeAdditions)
+ (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray + (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray {
{
CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]); CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]);
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
// Style specification assumes an origin at the upper-left corner. // Style specification assumes an origin at the upper-left corner.
@@ -19,8 +18,7 @@
return [NSValue value:&vector withObjCType:@encode(CGVector)]; return [NSValue value:&vector withObjCType:@encode(CGVector)];
} }
+ (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray + (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray {
{
// Style specification defines padding in clockwise order: top, right, bottom, left. // Style specification defines padding in clockwise order: top, right, bottom, left.
// Foundation defines padding in counterclockwise order: top, left, bottom, right. // Foundation defines padding in counterclockwise order: top, left, bottom, right.
MLNEdgeInsets insets = { MLNEdgeInsets insets = {
@@ -32,8 +30,7 @@
return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)]; return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)];
} }
- (std::array<float, 2>)mgl_offsetArrayValue - (std::array<float, 2>)mgl_offsetArrayValue {
{
MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector"); MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector");
CGVector vector; CGVector vector;
[self getValue:&vector]; [self getValue:&vector];
@@ -46,9 +43,9 @@
}; };
} }
- (std::array<float, 4>)mgl_paddingArrayValue - (std::array<float, 4>)mgl_paddingArrayValue {
{ MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0,
MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0, @"Value does not represent an NSEdgeInsets/UIEdgeInsets"); @"Value does not represent an NSEdgeInsets/UIEdgeInsets");
MLNEdgeInsets insets; MLNEdgeInsets insets;
[self getValue:&insets]; [self getValue:&insets];
// Style specification defines padding in clockwise order: top, right, bottom, left. // Style specification defines padding in clockwise order: top, right, bottom, left.
@@ -60,9 +57,9 @@
}; };
} }
- (std::array<float, 3>)mgl_lightPositionArrayValue - (std::array<float, 3>)mgl_lightPositionArrayValue {
{ MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0,
MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, @"Value does not represent an MLNSphericalPosition"); @"Value does not represent an MLNSphericalPosition");
MLNSphericalPosition lightPosition; MLNSphericalPosition lightPosition;
[self getValue:&lightPosition]; [self getValue:&lightPosition];
// Style specification defines padding in clockwise order: top, right, bottom, left. // Style specification defines padding in clockwise order: top, right, bottom, left.

View File

@@ -1,12 +1,12 @@
#include <mbgl/gl/headless_backend.hpp> #include <mbgl/gl/headless_backend.hpp>
#include <mbgl/util/logging.hpp> #include <mbgl/util/logging.hpp>
#include <OpenGL/OpenGL.h>
#include <Foundation/NSString.h>
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <Foundation/NSString.h>
#include <OpenGL/OpenGL.h>
#include <string>
#include <stdexcept> #include <stdexcept>
#include <string>
namespace mbgl { namespace mbgl {
namespace gl { namespace gl {
@@ -16,7 +16,9 @@ namespace gl {
class CGLDisplayConfig { class CGLDisplayConfig {
private: private:
// Key for singleton construction. // Key for singleton construction.
struct Key { explicit Key() = default; }; struct Key {
explicit Key() = default;
};
public: public:
CGLDisplayConfig(Key) { CGLDisplayConfig(Key) {
@@ -24,23 +26,22 @@ public:
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. // If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
CGLPixelFormatAttribute attributes[] = { CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy), kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
// Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when running in VMs. // Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when
kCGLPFAClosestPolicy, // running in VMs.
kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32), kCGLPFAClosestPolicy, kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24), kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24), kCGLPFAAlphaSize,
kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8), static_cast<CGLPixelFormatAttribute>(8), kCGLPFADepthSize,
kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(16), static_cast<CGLPixelFormatAttribute>(16), kCGLPFAStencilSize,
kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8), static_cast<CGLPixelFormatAttribute>(8), kCGLPFASupportsAutomaticGraphicsSwitching,
kCGLPFASupportsAutomaticGraphicsSwitching,
kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
static_cast<CGLPixelFormatAttribute>(0) static_cast<CGLPixelFormatAttribute>(0)};
};
GLint num; GLint num;
// TODO: obtain all configurations and choose the best one. // TODO: obtain all configurations and choose the best one.
const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num); const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
if (error != kCGLNoError) { if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + "\n"); throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) +
"\n");
} }
if (num <= 0) { if (num <= 0) {
throw std::runtime_error("No pixel formats found."); throw std::runtime_error("No pixel formats found.");
@@ -50,7 +51,8 @@ public:
~CGLDisplayConfig() { ~CGLDisplayConfig() {
const CGLError error = CGLDestroyPixelFormat(pixelFormat); const CGLError error = CGLDestroyPixelFormat(pixelFormat);
if (error != kCGLNoError) { if (error != kCGLNoError) {
Log::Error(Event::OpenGL, std::string("Error destroying pixel format:") + CGLErrorString(error)); Log::Error(Event::OpenGL,
std::string("Error destroying pixel format:") + CGLErrorString(error));
} }
} }
@@ -83,9 +85,7 @@ public:
} }
} }
~CGLBackendImpl() final { ~CGLBackendImpl() final { CGLDestroyContext(glContext); }
CGLDestroyContext(glContext);
}
gl::ProcAddress getExtensionFunctionPointer(const char* name) final { gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));

View File

@@ -30,13 +30,9 @@ public:
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
} }
void activateContext() final { void activateContext() final { [EAGLContext setCurrentContext:glContext]; }
[EAGLContext setCurrentContext:glContext];
}
void deactivateContext() final { void deactivateContext() final { [EAGLContext setCurrentContext:nil]; }
[EAGLContext setCurrentContext:nil];
}
private: private:
EAGLContext* glContext = nullptr; EAGLContext* glContext = nullptr;

View File

@@ -1,8 +1,8 @@
#import <Mapbox.h> #import <Mapbox.h>
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
#import "MLNFoundation_Private.h"
#import "MLNCluster.h" #import "MLNCluster.h"
#import "MLNFoundation_Private.h"
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import "MLNUserLocation_Private.h" #import "MLNUserLocation_Private.h"
@@ -35,7 +35,7 @@
pointFeature.title = @"title"; pointFeature.title = @"title";
pointFeature.subtitle = @"subtitle"; pointFeature.subtitle = @"subtitle";
pointFeature.identifier = @(123); pointFeature.identifier = @(123);
pointFeature.attributes = @{@"bbox": @[@1, @2, @3, @4]}; pointFeature.attributes = @{@"bbox" : @[ @1, @2, @3, @4 ]};
NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class]; NSString *filePath = [self temporaryFilePathForClass:MLNPointFeature.class];
[NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath]; [NSKeyedArchiver archiveRootObject:pointFeature toFile:filePath];
@@ -73,16 +73,15 @@
XCTAssert(cluster.clusterPointCount == 2); XCTAssert(cluster.clusterPointCount == 2);
} }
- (void)testPolyline { - (void)testPolyline {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551), CLLocationCoordinate2DMake(0.129631234123, 1.7812739312551),
CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392) CLLocationCoordinate2DMake(2.532083092342, 3.5216418292392)};
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates
count:numberOfCoordinates];
polyline.title = @"title"; polyline.title = @"title";
polyline.subtitle = @"subtitle"; polyline.subtitle = @"subtitle";
@@ -92,9 +91,7 @@
XCTAssertEqualObjects(polyline, unarchivedPolyline); XCTAssertEqualObjects(polyline, unarchivedPolyline);
CLLocationCoordinate2D otherCoordinates[] = { CLLocationCoordinate2D otherCoordinates[] = {CLLocationCoordinate2DMake(-1, -2)};
CLLocationCoordinate2DMake(-1, -2)
};
[unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates]; [unarchivedPolyline replaceCoordinatesInRange:NSMakeRange(0, 1) withCoordinates:otherCoordinates];
@@ -106,8 +103,10 @@
CLLocationCoordinate2DMake(51.000000, 2.000000), CLLocationCoordinate2DMake(51.000000, 2.000000),
}; };
NSUInteger multiLineCoordinatesCount = sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D); NSUInteger multiLineCoordinatesCount =
MLNPolyline *multiLine = [MLNPolyline polylineWithCoordinates:multiLineCoordinates count:multiLineCoordinatesCount]; sizeof(multiLineCoordinates) / sizeof(CLLocationCoordinate2D);
MLNPolyline *multiLine = [MLNPolyline polylineWithCoordinates:multiLineCoordinates
count:multiLineCoordinatesCount];
CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000); CLLocationCoordinate2D multiLineCenter = CLLocationCoordinate2DMake(51.000000, 1.000000);
XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude); XCTAssertEqual([multiLine coordinate].latitude, multiLineCenter.latitude);
@@ -119,7 +118,8 @@
}; };
NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D); NSUInteger segmentCoordinatesCount = sizeof(segmentCoordinates) / sizeof(CLLocationCoordinate2D);
MLNPolyline *segmentLine = [MLNPolyline polylineWithCoordinates:segmentCoordinates count:segmentCoordinatesCount]; MLNPolyline *segmentLine = [MLNPolyline polylineWithCoordinates:segmentCoordinates
count:segmentCoordinatesCount];
CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935); CLLocationCoordinate2D segmentCenter = CLLocationCoordinate2DMake(35.0404006631, -85.2604935);
XCTAssertEqualWithAccuracy([segmentLine coordinate].latitude, segmentCenter.latitude, 0.0001); XCTAssertEqualWithAccuracy([segmentLine coordinate].latitude, segmentCenter.latitude, 0.0001);
@@ -132,22 +132,25 @@
CLLocationCoordinate2DMake(37.862172, -122.262700), CLLocationCoordinate2DMake(37.862172, -122.262700),
}; };
NSUInteger sfToBerkeleyCoordinatesCount = sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D); NSUInteger sfToBerkeleyCoordinatesCount =
MLNPolyline *sfToBerkeleyLine = [MLNPolyline polylineWithCoordinates:sfToBerkeleyCoordinates count:sfToBerkeleyCoordinatesCount]; sizeof(sfToBerkeleyCoordinates) / sizeof(CLLocationCoordinate2D);
CLLocationCoordinate2D sfToBerkeleyCenter = CLLocationCoordinate2DMake(37.8230575118,-122.324867587); MLNPolyline *sfToBerkeleyLine =
[MLNPolyline polylineWithCoordinates:sfToBerkeleyCoordinates
XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].latitude, sfToBerkeleyCenter.latitude, 0.0001); count:sfToBerkeleyCoordinatesCount];
XCTAssertEqualWithAccuracy([sfToBerkeleyLine coordinate].longitude, sfToBerkeleyCenter.longitude, 0.0001); 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 { - (void)testPolygon {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(35.090745, -85.300259),
CLLocationCoordinate2DMake(35.090745, -85.300259),
CLLocationCoordinate2DMake(35.092035, -85.298885), CLLocationCoordinate2DMake(35.092035, -85.298885),
CLLocationCoordinate2DMake(35.090639, -85.297416), CLLocationCoordinate2DMake(35.090639, -85.297416),
CLLocationCoordinate2DMake(35.089112, -85.298928) CLLocationCoordinate2DMake(35.089112, -85.298928)};
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
@@ -171,31 +174,31 @@
}; };
NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D); NSUInteger squareCoordinatesCount = sizeof(squareCoordinates) / sizeof(CLLocationCoordinate2D);
MLNPolygon *squarePolygon = [MLNPolygon polygonWithCoordinates:squareCoordinates count:squareCoordinatesCount]; MLNPolygon *squarePolygon = [MLNPolygon polygonWithCoordinates:squareCoordinates
count:squareCoordinatesCount];
CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5);
XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude); XCTAssertEqual([squarePolygon coordinate].latitude, squareCenter.latitude);
XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude); XCTAssertEqual([squarePolygon coordinate].longitude, squareCenter.longitude);
} }
- (void)testPolygonWithInteriorPolygons { - (void)testPolygonWithInteriorPolygons {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1),
CLLocationCoordinate2DMake(0, 1), CLLocationCoordinate2DMake(10, 20)};
CLLocationCoordinate2DMake(10, 20)
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2D interiorCoordinates[] = {CLLocationCoordinate2DMake(4, 4),
CLLocationCoordinate2DMake(4, 4), CLLocationCoordinate2DMake(6, 6)};
CLLocationCoordinate2DMake(6, 6)
};
NSUInteger numberOfInteriorCoordinates = sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfInteriorCoordinates =
sizeof(interiorCoordinates) / sizeof(CLLocationCoordinate2D);
MLNPolygon *interiorPolygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:numberOfInteriorCoordinates]; MLNPolygon *interiorPolygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:numberOfCoordinates interiorPolygons:@[interiorPolygon]]; count:numberOfInteriorCoordinates];
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates
count:numberOfCoordinates
interiorPolygons:@[ interiorPolygon ]];
NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNPolygon class]];
[NSKeyedArchiver archiveRootObject:polygon toFile:filePath]; [NSKeyedArchiver archiveRootObject:polygon toFile:filePath];
@@ -206,41 +209,41 @@
} }
- (void)testPolylineFeature { - (void)testPolylineFeature {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1),
CLLocationCoordinate2DMake(0, 1), CLLocationCoordinate2DMake(10, 20)};
CLLocationCoordinate2DMake(10, 20)
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolylineFeature *polylineFeature =
polylineFeature.attributes = @{@"bbox": @[@0, @1, @2, @3]}; [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates];
polylineFeature.attributes = @{@"bbox" : @[ @0, @1, @2, @3 ]};
polylineFeature.identifier = @"identifier"; polylineFeature.identifier = @"identifier";
NSString *filePath = [self temporaryFilePathForClass:[MLNPolylineFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNPolylineFeature class]];
[NSKeyedArchiver archiveRootObject:polylineFeature toFile:filePath]; [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 { - (void)testPolygonFeature {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 1),
CLLocationCoordinate2DMake(0, 1), CLLocationCoordinate2DMake(10, 20)};
CLLocationCoordinate2DMake(10, 20)
};
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolygonFeature *polygonFeature =
[MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates];
NSString *filePath = [self temporaryFilePathForClass:[MLNPolygonFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNPolygonFeature class]];
[NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath]; [NSKeyedArchiver archiveRootObject:polygonFeature toFile:filePath];
MLNPolygonFeature *unarchivedPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNPolygonFeature *unarchivedPolygonFeature =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature); XCTAssertEqualObjects(polygonFeature, unarchivedPolygonFeature);
@@ -259,7 +262,8 @@
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPointCollection *pointCollection = [MLNPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; MLNPointCollection *pointCollection =
[MLNPointCollection pointCollectionWithCoordinates:coordinates count:numberOfCoordinates];
CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1); CLLocationCoordinate2D pointsCenter = CLLocationCoordinate2DMake(0, 1);
XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude); XCTAssertEqual([pointCollection coordinate].latitude, pointsCenter.latitude);
@@ -268,7 +272,8 @@
NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollection class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollection class]];
[NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath]; [NSKeyedArchiver archiveRootObject:pointCollection toFile:filePath];
MLNPointCollection *unarchivedPointCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNPointCollection *unarchivedPointCollection =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqualObjects(pointCollection, unarchivedPointCollection); XCTAssertEqualObjects(pointCollection, unarchivedPointCollection);
} }
@@ -290,14 +295,17 @@
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPointCollectionFeature *collection = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:numberOfCoordinates]; MLNPointCollectionFeature *collection =
[MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates
count:numberOfCoordinates];
collection.identifier = @"identifier"; collection.identifier = @"identifier";
collection.attributes = @{@"bbox": @[@1, @2, @3, @4]}; collection.attributes = @{@"bbox" : @[ @1, @2, @3, @4 ]};
NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollectionFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNPointCollectionFeature class]];
[NSKeyedArchiver archiveRootObject:collection toFile:filePath]; [NSKeyedArchiver archiveRootObject:collection toFile:filePath];
MLNPointCollectionFeature *unarchivedCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNPointCollectionFeature *unarchivedCollection =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqualObjects(collection, unarchivedCollection); XCTAssertEqualObjects(collection, unarchivedCollection);
@@ -307,7 +315,6 @@
} }
- (void)testMultiPolyline { - (void)testMultiPolyline {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0, 1), CLLocationCoordinate2DMake(0, 1),
CLLocationCoordinate2DMake(10, 11), CLLocationCoordinate2DMake(10, 11),
@@ -315,25 +322,24 @@
CLLocationCoordinate2DMake(30, 31), CLLocationCoordinate2DMake(30, 31),
}; };
CLLocationCoordinate2D line1[] = { CLLocationCoordinate2D line1[] = {CLLocationCoordinate2DMake(100, 40),
CLLocationCoordinate2DMake(100, 40),
CLLocationCoordinate2DMake(105, 45), CLLocationCoordinate2DMake(105, 45),
CLLocationCoordinate2DMake(110, 55) CLLocationCoordinate2DMake(110, 55)};
};
CLLocationCoordinate2D line2[] = { CLLocationCoordinate2D line2[] = {CLLocationCoordinate2DMake(105, 40),
CLLocationCoordinate2DMake(105, 40),
CLLocationCoordinate2DMake(110, 45), CLLocationCoordinate2DMake(110, 45),
CLLocationCoordinate2DMake(115, 55) CLLocationCoordinate2DMake(115, 55)};
};
NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D); NSUInteger road1CoordinatesCount = sizeof(line1) / sizeof(CLLocationCoordinate2D);
NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D); NSUInteger road2CoordinatesCount = sizeof(line2) / sizeof(CLLocationCoordinate2D);
MLNPolyline *road1Polyline = [MLNPolyline polylineWithCoordinates:line1 count:road1CoordinatesCount]; MLNPolyline *road1Polyline = [MLNPolyline polylineWithCoordinates:line1
MLNPolyline *road2Polyline = [MLNPolyline polylineWithCoordinates:line1 count:road2CoordinatesCount]; count:road1CoordinatesCount];
MLNPolyline *road2Polyline = [MLNPolyline polylineWithCoordinates:line1
count:road2CoordinatesCount];
MLNMultiPolyline *roads = [MLNMultiPolyline multiPolylineWithPolylines:@[road1Polyline, road2Polyline]]; MLNMultiPolyline *roads =
[MLNMultiPolyline multiPolylineWithPolylines:@[ road1Polyline, road2Polyline ]];
CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40); CLLocationCoordinate2D roadCenter = CLLocationCoordinate2DMake(100, 40);
XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude); XCTAssertEqual([roads coordinate].latitude, roadCenter.latitude);
@@ -344,7 +350,8 @@
NSMutableArray *polylines = [NSMutableArray array]; NSMutableArray *polylines = [NSMutableArray array];
for (NSUInteger i = 0; i < 100; i++) { for (NSUInteger i = 0; i < 100; i++) {
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates
count:numberOfCoordinates];
[polylines addObject:polyline]; [polylines addObject:polyline];
} }
@@ -354,14 +361,14 @@
[NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath]; [NSKeyedArchiver archiveRootObject:multiPolyline toFile:filePath];
MLNMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNMultiPolyline *unarchivedMultiPolyline = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MLNMultiPolyline *anotherMultipolyline = [MLNMultiPolyline multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; MLNMultiPolyline *anotherMultipolyline = [MLNMultiPolyline
multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count / 2)]];
XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline); XCTAssertEqualObjects(multiPolyline, unarchivedMultiPolyline);
XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline); XCTAssertNotEqualObjects(unarchivedMultiPolyline, anotherMultipolyline);
} }
- (void)testMultiPolygon { - (void)testMultiPolygon {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0, 1), CLLocationCoordinate2DMake(0, 1),
CLLocationCoordinate2DMake(10, 11), CLLocationCoordinate2DMake(10, 11),
@@ -386,9 +393,13 @@
NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D); NSUInteger outerCoordinatesCount = sizeof(outerSquare) / sizeof(CLLocationCoordinate2D);
NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D); NSUInteger innerCoordinatesCount = sizeof(innerSquare) / sizeof(CLLocationCoordinate2D);
MLNPolygon *innerPolygonSquare = [MLNPolygon polygonWithCoordinates:innerSquare count:innerCoordinatesCount]; MLNPolygon *innerPolygonSquare = [MLNPolygon polygonWithCoordinates:innerSquare
MLNPolygon *outerPolygonSquare = [MLNPolygon polygonWithCoordinates:outerSquare count:outerCoordinatesCount interiorPolygons:@[innerPolygonSquare]]; count:innerCoordinatesCount];
MLNMultiPolygon *squares = [MLNMultiPolygon multiPolygonWithPolygons:@[outerPolygonSquare, innerPolygonSquare]]; MLNPolygon *outerPolygonSquare = [MLNPolygon polygonWithCoordinates:outerSquare
count:outerCoordinatesCount
interiorPolygons:@[ innerPolygonSquare ]];
MLNMultiPolygon *squares =
[MLNMultiPolygon multiPolygonWithPolygons:@[ outerPolygonSquare, innerPolygonSquare ]];
CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5); CLLocationCoordinate2D squareCenter = CLLocationCoordinate2DMake(100.5, 0.5);
XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude); XCTAssertEqual([squares coordinate].latitude, squareCenter.latitude);
@@ -409,11 +420,11 @@
[NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath]; [NSKeyedArchiver archiveRootObject:multiPolygon toFile:filePath];
MLNMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNMultiPolygon *unarchivedMultiPolygon = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MLNMultiPolygon *anotherMultiPolygon = [MLNMultiPolygon multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; MLNMultiPolygon *anotherMultiPolygon = [MLNMultiPolygon
multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count / 2)]];
XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon); XCTAssertEqualObjects(multiPolygon, unarchivedMultiPolygon);
XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon); XCTAssertNotEqualObjects(anotherMultiPolygon, unarchivedMultiPolygon);
} }
- (void)testShapeCollection { - (void)testShapeCollection {
@@ -425,16 +436,20 @@
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:coordinates
count:numberOfCoordinates];
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates: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]]; NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollection class]];
[NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath]; [NSKeyedArchiver archiveRootObject:shapeCollection toFile:filePath];
MLNShapeCollection *unarchivedShapeCollection = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNShapeCollection *unarchivedShapeCollection =
MLNShapeCollection *anotherShapeCollection = [MLNShapeCollection shapeCollectionWithShapes:@[polygon]]; [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MLNShapeCollection *anotherShapeCollection =
[MLNShapeCollection shapeCollectionWithShapes:@[ polygon ]];
XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection); XCTAssertEqualObjects(shapeCollection, unarchivedShapeCollection);
XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection); XCTAssertNotEqualObjects(shapeCollection, anotherShapeCollection);
@@ -451,19 +466,23 @@
NSMutableArray *polylines = [NSMutableArray array]; NSMutableArray *polylines = [NSMutableArray array];
for (NSUInteger i = 0; i < 100; i++) { for (NSUInteger i = 0; i < 100; i++) {
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolylineFeature *polylineFeature =
[MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates];
polylineFeature.identifier = @(arc4random() % 100).stringValue; polylineFeature.identifier = @(arc4random() % 100).stringValue;
[polylines addObject:polylineFeature]; [polylines addObject:polylineFeature];
} }
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:polylines]; MLNMultiPolylineFeature *multiPolylineFeature =
multiPolylineFeature.attributes = @{@"bbox": @[@4, @3, @2, @1]}; [MLNMultiPolylineFeature multiPolylineWithPolylines:polylines];
multiPolylineFeature.attributes = @{@"bbox" : @[ @4, @3, @2, @1 ]};
NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]];
[NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath]; [NSKeyedArchiver archiveRootObject:multiPolylineFeature toFile:filePath];
MLNMultiPolylineFeature *unarchivedMultiPolylineFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNMultiPolylineFeature *unarchivedMultiPolylineFeature =
MLNMultiPolylineFeature *anotherMultiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count/2)]]; [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MLNMultiPolylineFeature *anotherMultiPolylineFeature = [MLNMultiPolylineFeature
multiPolylineWithPolylines:[polylines subarrayWithRange:NSMakeRange(0, polylines.count / 2)]];
XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature); XCTAssertEqualObjects(multiPolylineFeature, unarchivedMultiPolylineFeature);
XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature); XCTAssertNotEqualObjects(unarchivedMultiPolylineFeature, anotherMultiPolylineFeature);
@@ -479,23 +498,29 @@
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++ ) { for (NSUInteger i = 0; i < 100; i++) {
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolygonFeature *polygonFeature =
[MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates];
polygonFeature.identifier = @(arc4random_uniform(100)).stringValue; polygonFeature.identifier = @(arc4random_uniform(100)).stringValue;
[polygons addObject:polygonFeature]; [polygons addObject:polygonFeature];
} }
MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:polygons]; MLNMultiPolygonFeature *multiPolygonFeature =
multiPolygonFeature.attributes = @{@"bbox": @[@(arc4random_uniform(100)), [MLNMultiPolygonFeature multiPolygonWithPolygons:polygons];
@(arc4random_uniform(100)), multiPolygonFeature.attributes = @{
@(arc4random_uniform(100)), @"bbox" : @[
@(arc4random_uniform(100))]}; @(arc4random_uniform(100)), @(arc4random_uniform(100)), @(arc4random_uniform(100)),
@(arc4random_uniform(100))
]
};
NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNMultiPolylineFeature class]];
[NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath]; [NSKeyedArchiver archiveRootObject:multiPolygonFeature toFile:filePath];
MLNMultiPolygonFeature *unarchivedMultiPolygonFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNMultiPolygonFeature *unarchivedMultiPolygonFeature =
MLNMultiPolygonFeature *anotherMultiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count/2)]]; [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
MLNMultiPolygonFeature *anotherMultiPolygonFeature = [MLNMultiPolygonFeature
multiPolygonWithPolygons:[polygons subarrayWithRange:NSMakeRange(0, polygons.count / 2)]];
XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature); XCTAssertEqualObjects(multiPolygonFeature, unarchivedMultiPolygonFeature);
XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature); XCTAssertNotEqualObjects(anotherMultiPolygonFeature, unarchivedMultiPolygonFeature);
@@ -510,20 +535,26 @@
NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D); NSUInteger numberOfCoordinates = sizeof(coordinates) / sizeof(CLLocationCoordinate2D);
MLNPolylineFeature *polyline = [MLNPolylineFeature polylineWithCoordinates:coordinates count:numberOfCoordinates]; MLNPolylineFeature *polyline = [MLNPolylineFeature polylineWithCoordinates:coordinates
MLNPolygonFeature *polygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:numberOfCoordinates]; count:numberOfCoordinates];
MLNPolygonFeature *polygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:numberOfCoordinates];
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[polyline, polygon]]; MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[ polyline, polygon ]];
shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue; shapeCollectionFeature.identifier = @(arc4random_uniform(100)).stringValue;
shapeCollectionFeature.attributes = @{@"bbox":@[@(arc4random_uniform(100)), shapeCollectionFeature.attributes = @{
@(arc4random_uniform(100)), @"bbox" : @[
@(arc4random_uniform(100)), @(arc4random_uniform(100)), @(arc4random_uniform(100)), @(arc4random_uniform(100)),
@(arc4random_uniform(100))]}; @(arc4random_uniform(100))
]
};
NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollectionFeature class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNShapeCollectionFeature class]];
[NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath]; [NSKeyedArchiver archiveRootObject:shapeCollectionFeature toFile:filePath];
MLNShapeCollectionFeature *unarchivedShapeCollectionFeature = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNShapeCollectionFeature *unarchivedShapeCollectionFeature =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature); XCTAssertEqualObjects(shapeCollectionFeature, unarchivedShapeCollectionFeature);
} }
@@ -542,12 +573,15 @@
[image unlockFocus]; [image unlockFocus];
#endif #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]]; NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationImage class]];
[NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath]; [NSKeyedArchiver archiveRootObject:annotationImage toFile:filePath];
MLNAnnotationImage *unarchivedAnnotationImage = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNAnnotationImage *unarchivedAnnotationImage =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage); XCTAssertEqualObjects(annotationImage, unarchivedAnnotationImage);
} }
@@ -564,13 +598,16 @@
NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationView class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNAnnotationView class]];
[NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath];
MLNAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNAnnotationView *unarchivedAnnotationView =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled);
XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected);
XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable);
XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset),
XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); NSStringFromCGVector(unarchivedAnnotationView.centerOffset));
XCTAssertEqual(annotationView.scalesWithViewingDistance,
unarchivedAnnotationView.scalesWithViewingDistance);
} }
#endif #endif
@@ -602,13 +639,16 @@
NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocationAnnotationView class]]; NSString *filePath = [self temporaryFilePathForClass:[MLNUserLocationAnnotationView class]];
[NSKeyedArchiver archiveRootObject:annotationView toFile:filePath]; [NSKeyedArchiver archiveRootObject:annotationView toFile:filePath];
MLNUserLocationAnnotationView *unarchivedAnnotationView = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; MLNUserLocationAnnotationView *unarchivedAnnotationView =
[NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled); XCTAssertEqual(annotationView.enabled, unarchivedAnnotationView.enabled);
XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected); XCTAssertEqual(annotationView.selected, unarchivedAnnotationView.selected);
XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable); XCTAssertEqual(annotationView.draggable, unarchivedAnnotationView.draggable);
XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset), NSStringFromCGVector(unarchivedAnnotationView.centerOffset)); XCTAssertEqualObjects(NSStringFromCGVector(annotationView.centerOffset),
XCTAssertEqual(annotationView.scalesWithViewingDistance, unarchivedAnnotationView.scalesWithViewingDistance); NSStringFromCGVector(unarchivedAnnotationView.centerOffset));
XCTAssertEqual(annotationView.scalesWithViewingDistance,
unarchivedAnnotationView.scalesWithViewingDistance);
} }
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
#import <mbgl/util/geometry.hpp> #import <mbgl/util/geometry.hpp>
#import "MLNFoundation_Private.h"
#import "../../darwin/src/MLNFeature_Private.h" #import "../../darwin/src/MLNFeature_Private.h"
#import "MLNFoundation_Private.h"
@interface MLNFeatureTests : XCTestCase @interface MLNFeatureTests : XCTestCase
@@ -14,37 +14,38 @@
- (void)testGeometryConversion { - (void)testGeometryConversion {
std::vector<mbgl::Feature> features; std::vector<mbgl::Feature> features;
mbgl::Point<double> point = { -90.066667, 29.95 }; mbgl::Point<double> point = {-90.066667, 29.95};
features.push_back(mbgl::Feature { point }); features.push_back(mbgl::Feature{point});
mbgl::LineString<double> lineString = { mbgl::LineString<double> lineString = {
{ -84.516667, 39.1 }, {-84.516667, 39.1},
{ -90.066667, 29.95 }, {-90.066667, 29.95},
}; };
features.push_back(mbgl::Feature { lineString }); features.push_back(mbgl::Feature{lineString});
mbgl::Polygon<double> polygon = { mbgl::Polygon<double> polygon = {
{ {
{ 1, 1 }, {1, 1},
{ 4, 1 }, {4, 1},
{ 4, 4 }, {4, 4},
{ 1, 4 }, {1, 4},
}, },
{ {
{ 2, 2 }, {2, 2},
{ 3, 2 }, {3, 2},
{ 3, 3 }, {3, 3},
{ 2, 3 }, {2, 3},
}, },
}; };
features.push_back(mbgl::Feature { polygon }); features.push_back(mbgl::Feature{polygon});
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features); NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes"); XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes");
MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0]; MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0];
XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]); XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:pointShape.coordinate], XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:pointShape.coordinate],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1]; MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
@@ -52,9 +53,11 @@
XCTAssertEqual(polylineShape.pointCount, 2UL); XCTAssertEqual(polylineShape.pointCount, 2UL);
CLLocationCoordinate2D polylineCoordinates[2]; CLLocationCoordinate2D polylineCoordinates[2];
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)]; [polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[0]], XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[1]], XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2]; MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
@@ -75,7 +78,8 @@
MLNPolygon *interiorPolygon = interiorPolygons.firstObject; MLNPolygon *interiorPolygon = interiorPolygons.firstObject;
XCTAssertEqual(interiorPolygon.pointCount, 4UL); XCTAssertEqual(interiorPolygon.pointCount, 4UL);
CLLocationCoordinate2D interiorPolygonCoordinates[4]; CLLocationCoordinate2D interiorPolygonCoordinates[4];
[interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)]; [interiorPolygon getCoordinates:interiorPolygonCoordinates
range:NSMakeRange(0, interiorPolygon.pointCount)];
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
@@ -87,9 +91,9 @@
} }
- (void)testClusterGeometryConversion { - (void)testClusterGeometryConversion {
mbgl::Point<double> point = { -90.066667, 29.95 }; mbgl::Point<double> point = {-90.066667, 29.95};
mbgl::Feature pointFeature { point }; mbgl::Feature pointFeature{point};
pointFeature.id = { UINT64_MAX }; pointFeature.id = {UINT64_MAX};
pointFeature.properties["cluster"] = true; pointFeature.properties["cluster"] = true;
pointFeature.properties["cluster_id"] = 1ULL; pointFeature.properties["cluster_id"] = 1ULL;
pointFeature.properties["point_count"] = 5ULL; pointFeature.properties["point_count"] = 5ULL;
@@ -109,9 +113,9 @@
- (void)testPropertyConversion { - (void)testPropertyConversion {
std::vector<mbgl::Feature> features; std::vector<mbgl::Feature> features;
mbgl::Point<double> point = { -90.066667, 29.95 }; mbgl::Point<double> point = {-90.066667, 29.95};
mbgl::Feature pointFeature { point }; mbgl::Feature pointFeature{point};
pointFeature.id = { UINT64_MAX }; pointFeature.id = {UINT64_MAX};
pointFeature.properties["null"] = mapbox::feature::null_value; pointFeature.properties["null"] = mapbox::feature::null_value;
pointFeature.properties["bool"] = true; pointFeature.properties["bool"] = true;
pointFeature.properties["unsigned int"] = UINT64_MAX; pointFeature.properties["unsigned int"] = UINT64_MAX;
@@ -124,16 +128,18 @@
vector.push_back(true); vector.push_back(true);
features.push_back(pointFeature); features.push_back(pointFeature);
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features); NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes"); XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes");
MLNShape <MLNFeature> *shape = shapes.firstObject; MLNShape<MLNFeature> *shape = shapes.firstObject;
XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]); XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]);
XCTAssertTrue([shape isKindOfClass:[MLNShape class]]); XCTAssertTrue([shape isKindOfClass:[MLNShape class]]);
NSNumber *identifier = shape.identifier; NSNumber *identifier = shape.identifier;
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]], @"Feature identifier should be NSNumber"); XCTAssertTrue([identifier isKindOfClass:[NSNumber class]],
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0, @"Feature identifier should be 64-bit unsigned integer"); @"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"]; NSNull *null = [shape attributeForKey:@"null"];
XCTAssertNotNil(null); XCTAssertNotNil(null);
@@ -143,28 +149,32 @@
NSNumber *boolean = [shape attributeForKey:@"bool"]; NSNumber *boolean = [shape attributeForKey:@"bool"];
XCTAssertNotNil(boolean); XCTAssertNotNil(boolean);
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]); XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber"); XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0,
@"Boolean property should be converted to bool NSNumber");
XCTAssertTrue(boolean.boolValue); XCTAssertTrue(boolean.boolValue);
XCTAssertEqual(boolean, shape.attributes[@"bool"]); XCTAssertEqual(boolean, shape.attributes[@"bool"]);
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"]; NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
XCTAssertNotNil(unsignedInteger); XCTAssertNotNil(unsignedInteger);
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]); 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(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.unsignedLongLongValue, UINT64_MAX);
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]); XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
NSNumber *integer = [shape attributeForKey:@"int"]; NSNumber *integer = [shape attributeForKey:@"int"];
XCTAssertNotNil(integer); XCTAssertNotNil(integer);
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]); XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0, @"Integer property should be converted to long long NSNumber"); XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0,
@"Integer property should be converted to long long NSNumber");
XCTAssertEqual(integer.longLongValue, INT64_MIN); XCTAssertEqual(integer.longLongValue, INT64_MIN);
XCTAssertEqual(integer, shape.attributes[@"int"]); XCTAssertEqual(integer, shape.attributes[@"int"]);
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"]; NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
XCTAssertNotNil(floatingPointNumber); XCTAssertNotNil(floatingPointNumber);
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]); XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0, @"Floating-point number property should be converted to double NSNumber"); XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0,
@"Floating-point number property should be converted to double NSNumber");
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX); XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]); XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
@@ -177,7 +187,7 @@
- (void)testPointFeatureGeoJSONDictionary { - (void)testPointFeatureGeoJSONDictionary {
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D coordinate = { 10, 10 }; CLLocationCoordinate2D coordinate = {10, 10};
pointFeature.coordinate = coordinate; pointFeature.coordinate = coordinate;
// A GeoJSON feature // A GeoJSON feature
@@ -187,8 +197,8 @@
// it has the correct type // it has the correct type
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature"); XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"Point", NSDictionary *expectedGeometry =
@"coordinates": @[@(coordinate.longitude), @(coordinate.latitude)]}; @{@"type" : @"Point", @"coordinates" : @[ @(coordinate.longitude), @(coordinate.latitude) ]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has no "id" key (or value) // it has no "id" key (or value)
XCTAssertNil(geoJSONFeature[@"id"]); XCTAssertNil(geoJSONFeature[@"id"]);
@@ -203,7 +213,7 @@
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier); XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
// when there are properties // when there are properties
pointFeature.attributes = @{@"name": @"name-value"}; pointFeature.attributes = @{@"name" : @"name-value"};
// it has the properties value in the result // it has the properties value in the result
geoJSONFeature = [pointFeature geoJSONDictionary]; geoJSONFeature = [pointFeature geoJSONDictionary];
@@ -211,137 +221,169 @@
} }
- (void)testPolylineFeatureGeoJSONDictionary { - (void)testPolylineFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coords[] = { coord1, coord2 }; CLLocationCoordinate2D coords[] = {coord1, coord2};
MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
// A GeoJSON feature // A GeoJSON feature
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"LineString", NSDictionary *expectedGeometry = @{
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], @"type" : @"LineString",
@[@(coord2.longitude), @(coord2.latitude)]]}; @"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }
- (void)testPolygonFeatureGeoJSONDictionary { - (void)testPolygonFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = { 0, 0 }; CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 }; CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3];
// A GeoJSON feature // A GeoJSON feature
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"Polygon", NSDictionary *expectedGeometry = @{
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)], @"type" : @"Polygon",
@[@(coord2.longitude), @(coord2.latitude)], @"coordinates" : @[ @[
@[@(coord3.longitude), @(coord3.latitude)]]]}; @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
] ]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }
- (void)testMultiPolylineFeatureGeoJSONDictionary { - (void)testMultiPolylineFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = { 0, 0 }; CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 }; CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3]; MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3];
MLNPolyline *polyLine2 = [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 // A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiLineString", NSDictionary *expectedGeometry = @{
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)], @"type" : @"MultiLineString",
@[@(coord2.longitude), @(coord2.latitude)], @"coordinates" : @[
@[@(coord3.longitude), @(coord3.latitude)]], @[
@[@[@(coord1.longitude), @(coord1.latitude)], @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[@(coord2.longitude), @(coord2.latitude)], @[ @(coord3.longitude), @(coord3.latitude) ]
@[@(coord3.longitude), @(coord3.latitude)]]]}; ],
@[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }
- (void)testMultiPolygonFeatureGeoJSONDictionary { - (void)testMultiPolygonFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = { 0, 0 }; CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 }; CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3]; MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3];
MLNPolygon *polygon2 = [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 // A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiPolygon", NSDictionary *expectedGeometry = @{
@"coordinates": @[ @"type" : @"MultiPolygon",
@[@[@[@(coord1.longitude), @(coord1.latitude)], @"coordinates" : @[
@[@(coord2.longitude), @(coord2.latitude)], @[ @[
@[@(coord3.longitude), @(coord3.latitude)]]], @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[@[@[@(coord1.longitude), @(coord1.latitude)], @[ @(coord3.longitude), @(coord3.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); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }
- (void)testPointCollectionFeatureGeoJSONDictionary { - (void)testPointCollectionFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = { 0, 0 }; CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 }; CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3]; MLNPointCollectionFeature *pointCollectionFeature =
[MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
// A GeoJSON feature // A GeoJSON feature
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiPoint", NSDictionary *expectedGeometry = @{
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], @"type" : @"MultiPoint",
@[@(coord2.longitude), @(coord2.latitude)], @"coordinates" : @[
@[@(coord3.longitude), @(coord3.latitude)]]}; @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }
- (void)testShapeCollectionFeatureGeoJSONDictionary { - (void)testShapeCollectionFeatureGeoJSONDictionary {
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init]; MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D pointCoordinate = { 10, 10 }; CLLocationCoordinate2D pointCoordinate = {10, 10};
pointFeature.coordinate = pointCoordinate; pointFeature.coordinate = pointCoordinate;
CLLocationCoordinate2D coord1 = { 0, 0 }; CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = { 10, 10 }; CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coords[] = { coord1, coord2 }; CLLocationCoordinate2D coords[] = {coord1, coord2};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2]; MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]]; MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[ pointFeature, polylineFeature ]];
// A GeoJSON feature // A GeoJSON feature
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
// it has the correct geometry // it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection", NSDictionary *expectedGeometry = @{
@"geometries": @[ @"type" : @"GeometryCollection",
@{ @"geometry": @{@"type": @"Point", @"geometries" : @[
@"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]}, @{
@"properties": @{}, @"geometry" : @{
@"type": @"Feature", @"type" : @"Point",
@"coordinates" : @[ @(pointCoordinate.longitude), @(pointCoordinate.latitude) ]
}, },
@{ @"geometry": @{@"type": @"LineString", @"properties" : @{},
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], @"type" : @"Feature",
@[@(coord2.longitude), @(coord2.latitude)]]}, },
@"properties": @{}, @{
@"type": @"Feature", @"geometry" : @{
@"type" : @"LineString",
@"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ],
@[ @(coord2.longitude), @(coord2.latitude) ]
]
},
@"properties" : @{},
@"type" : @"Feature",
} }
] ]
}; };
@@ -352,8 +394,7 @@
// it has the correct (empty) geometry // it has the correct (empty) geometry
geoJSONFeature = [shapeCollectionFeature geoJSONDictionary]; geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
expectedGeometry = @{@"type": @"GeometryCollection", expectedGeometry = @{@"type" : @"GeometryCollection", @"geometries" : @[]};
@"geometries": @[]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
} }

View File

@@ -9,9 +9,11 @@
@implementation MLNGeometryTests @implementation MLNGeometryTests
- (void)testCoordinateBoundsIsEmpty { - (void)testCoordinateBoundsIsEmpty {
MLNCoordinateBounds emptyBounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0)); MLNCoordinateBounds emptyBounds =
MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0));
XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds)); XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds));
XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds))); XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(
MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds)));
} }
- (void)testAngleConversions { - (void)testAngleConversions {
@@ -39,36 +41,57 @@
CGSize midSize = CGSizeMake(600, 800); CGSize midSize = CGSizeMake(600, 800);
CGSize shortSize = CGSizeMake(600, 400); CGSize shortSize = CGSizeMake(600, 400);
XCTAssertEqualWithAccuracy(1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize), 1e-8); XCTAssertEqualWithAccuracy(
XCTAssertLessThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, tallSize)); 1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize),
XCTAssertGreaterThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, shortSize)); 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(
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 0, midSize), 0, 0, midSize), 1e-8); 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(
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 40, midSize), 0, 40, midSize), 1e-8); 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(
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 60, 40, midSize), 60, 40, midSize), 1e-8); 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 { - (void)testGeometryBoxing {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
CLLocationCoordinate2D roundTrippedCoordinate = [NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue; CLLocationCoordinate2D roundTrippedCoordinate =
[NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue;
XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude, @"Latitude should round-trip."); XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude,
XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude, @"Longitude should round-trip."); @"Latitude should round-trip.");
XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude,
@"Longitude should round-trip.");
MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997); MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997);
MLNCoordinateSpan roundTrippedSpan = [NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue; MLNCoordinateSpan roundTrippedSpan =
[NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue;
XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta, @"Latitude delta should round-trip."); XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta,
XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta, @"Longitude delta should round-trip."); @"Latitude delta should round-trip.");
XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta,
@"Longitude delta should round-trip.");
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239), MLNCoordinateBounds bounds =
MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239),
CLLocationCoordinate2DMake(37.7757368, -122.4135302)); CLLocationCoordinate2DMake(37.7757368, -122.4135302));
MLNCoordinateBounds roundTrippedBounds = [NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue; MLNCoordinateBounds roundTrippedBounds =
[NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue;
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
[NSValue valueWithMLNCoordinate:roundTrippedBounds.sw], [NSValue valueWithMLNCoordinate:roundTrippedBounds.sw],
@@ -100,17 +123,22 @@
} }
- (void)testGeoJSONDeserialization { - (void)testGeoJSONDeserialization {
NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding]; NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": "
@"[0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error; NSError *error;
MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data
encoding:NSUTF8StringEncoding
error:&error];
XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization."); XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization.");
XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object 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."); XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]],
@"Valid GeoJSON point feature data should produce an MLNPointFeature.");
XCTAssertEqual(feature.attributes.count, 0UL); XCTAssertEqual(feature.attributes.count, 0UL);
XCTAssertEqual(feature.coordinate.latitude, 0); XCTAssertEqual(feature.coordinate.latitude, 0);
XCTAssertEqual(feature.coordinate.longitude, 0); XCTAssertEqual(feature.coordinate.longitude, 0);
data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}" dataUsingEncoding:NSUTF8StringEncoding]; data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}"
dataUsingEncoding:NSUTF8StringEncoding];
error = nil; error = nil;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization."); XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization.");
@@ -125,23 +153,27 @@
NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding]; NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding];
XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object."); XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object.");
NSError *error; NSError *error;
NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&error];
XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON."); XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON.");
XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON."); XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON.");
XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]], @"Serialized GeoJSON data should be a JSON object."); XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]],
@"Serialized GeoJSON data should be a JSON object.");
NSDictionary *geoJSON = @{ NSDictionary *geoJSON = @{
@"type": @"Feature", @"type" : @"Feature",
@"id": @504, @"id" : @504,
@"geometry": @{ @"geometry" : @{
@"type": @"Point", @"type" : @"Point",
@"coordinates": @[ @"coordinates" : @[
@(-90.066667), @(-90.066667),
@29.95, @29.95,
], ],
}, },
@"properties": @{}, @"properties" : @{},
}; };
XCTAssertEqualObjects(serializedGeoJSON, geoJSON, @"MLNPointFeature should serialize as a GeoJSON point feature."); XCTAssertEqualObjects(serializedGeoJSON, geoJSON,
@"MLNPointFeature should serialize as a GeoJSON point feature.");
} }
- (void)testMLNCoordinateBoundsToMLNCoordinateQuad { - (void)testMLNCoordinateBoundsToMLNCoordinateQuad {
@@ -156,10 +188,12 @@
[NSValue valueWithMLNCoordinate:quad.topRight], [NSValue valueWithMLNCoordinate:quad.topRight],
@"Bounds northeast should be top right of quad."); @"Bounds northeast should be top right of quad.");
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)], XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)],
[NSValue valueWithMLNCoordinate:quad.topLeft], [NSValue valueWithMLNCoordinate:quad.topLeft],
@"Quad top left should be computed correctly."); @"Quad top left should be computed correctly.");
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)], XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)],
[NSValue valueWithMLNCoordinate:quad.bottomRight], [NSValue valueWithMLNCoordinate:quad.bottomRight],
@"Quad bottom right should be computed correctly."); @"Quad bottom right should be computed correctly.");
} }

View File

@@ -12,10 +12,13 @@
- (void)testProperties { - (void)testProperties {
MLNPointFeature *feature = [[MLNPointFeature alloc] init]; MLNPointFeature *feature = [[MLNPointFeature alloc] init];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; shape:feature
options:nil];
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID"
source:source];
auto rawLayer = static_cast<mbgl::style::HeatmapLayer*>(layer.rawLayer); auto rawLayer = static_cast<mbgl::style::HeatmapLayer *>(layer.rawLayer);
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"heatmap-color should be unset initially."); @"heatmap-color should be unset initially.");
@@ -24,27 +27,35 @@
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
layer.heatmapColor = constantExpression; layer.heatmapColor = constantExpression;
mbgl::style::PropertyValue<float> propertyValue = {0xff};
mbgl::style::PropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
@"Setting heatmapColor to a constant value expression should update heatmap-color."); @"Setting heatmapColor to a constant value expression should update heatmap-color.");
XCTAssertEqualObjects(layer.heatmapColor, constantExpression, XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
@"heatmapColor should round-trip constant value expressions."); @"heatmapColor should round-trip constant value expressions.");
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]]; NSExpression *constantExpression2 =
[NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
#if TARGET_OS_IPHONE #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 #endif
NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", constantExpression, @{@12: constantExpression2}]; NSExpression *functionExpression =
[NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)",
constantExpression, @{@12 : constantExpression2}];
layer.heatmapColor = functionExpression; layer.heatmapColor = functionExpression;
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); @"Setting heatmapColor to an expression depending on $heatmapDensity should "
@"update heatmap-color.");
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); @"Setting heatmapColor to an expression depending on $heatmapDensity should "
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); @"update heatmap-color.");
XCTAssertEqualObjects(layer.heatmapColor, functionExpression, 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."); @"heatmapColor should round-trip expressions depending on $heatmapDensity.");
layer.heatmapColor = nil; layer.heatmapColor = nil;
@@ -54,13 +65,29 @@
XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description, XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description,
@"heatmapColor should return the default value after being unset."); @"heatmapColor should return the default value after being unset.");
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}]; functionExpression =
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); [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"]; functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}]; NSInvalidArgumentException,
functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}]; @"MLNHeatmapLayer should raise an exception if a data expression is "
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."); @"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 @end

View File

@@ -1,14 +1,14 @@
#import <XCTest/XCTest.h>
#import <Mapbox.h> #import <Mapbox.h>
#import <XCTest/XCTest.h>
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h" #import "UIImage+MLNAdditions.h"
#define MLNImageResizingModeTile UIImageResizingModeTile #define MLNImageResizingModeTile UIImageResizingModeTile
#define MLNImageResizingModeStretch UIImageResizingModeStretch #define MLNImageResizingModeStretch UIImageResizingModeStretch
#else #else
#import "NSImage+MLNAdditions.h" #import "NSImage+MLNAdditions.h"
#define MLNImageResizingModeTile NSImageResizingModeTile #define MLNImageResizingModeTile NSImageResizingModeTile
#define MLNImageResizingModeStretch NSImageResizingModeStretch #define MLNImageResizingModeStretch NSImageResizingModeStretch
#endif #endif
@interface MLNImageTests : XCTestCase @interface MLNImageTests : XCTestCase
@@ -27,11 +27,17 @@
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext(); UIGraphicsEndImageContext();
#else #else
NSImage *image = [NSImage imageWithSize:NSMakeSize(24, 24) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { NSImage *image = [NSImage
// A little more fanciful than the iOS version, but we aren’t testing the actual contents of the image anyways. imageWithSize:NSMakeSize(24, 24)
NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge, NSMaxXEdge}; 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}; 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]; [NSColor.grayColor set];
NSRectFill(dstRect); NSRectFill(dstRect);
return YES; return YES;
@@ -58,7 +64,8 @@
} }
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch]; image = [image resizableImageWithCapInsets:UIEdgeInsetsZero
resizingMode:UIImageResizingModeStretch];
#else #else
image.resizingMode = NSImageResizingModeStretch; image.resizingMode = NSImageResizingModeStretch;
image.capInsets = NSEdgeInsetsZero; image.capInsets = NSEdgeInsetsZero;

View File

@@ -11,7 +11,6 @@ using namespace std::chrono_literals;
@implementation MLNNSDateAdditionsTests @implementation MLNNSDateAdditionsTests
- (void)testDurationToNSTimeInterval { - (void)testDurationToNSTimeInterval {
NSTimeInterval timeInterval = 5; NSTimeInterval timeInterval = 5;
mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval); mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval);
NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration); NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration);
@@ -34,7 +33,6 @@ using namespace std::chrono_literals;
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds)); XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds)); XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds)); XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds));
} }
@end @end

View File

@@ -12,18 +12,30 @@
- (void)testInvalidation { - (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(
XCTAssertThrowsSpecificNamed(invalidPack.context, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its context."); invalidPack.region, NSException, MLNInvalidOfflinePackException,
XCTAssertThrowsSpecificNamed([invalidPack resume], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being resumed."); @"Invalid offline pack should raise an exception when accessing its region.");
XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being suspended."); 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 { - (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 // Now try again, without asserts
NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler]; NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
@@ -45,14 +57,21 @@
.countOfTileBytesCompleted = 6, .countOfTileBytesCompleted = 6,
.maximumResourcesExpected = UINT64_MAX, .maximumResourcesExpected = UINT64_MAX,
}; };
MLNOfflinePackProgress roundTrippedProgress = [NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue; MLNOfflinePackProgress roundTrippedProgress =
[NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue;
XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, @"Completed resources should round-trip."); XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted,
XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, @"Expected resources should round-trip."); @"Completed resources should round-trip.");
XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, @"Completed bytes should round-trip."); XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected,
XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted, @"Completed tiles should round-trip."); @"Expected resources should round-trip.");
XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted, @"Completed tile bytes should round-trip."); XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted,
XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, @"Maximum expected resources should round-trip."); @"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 @end

View File

@@ -2,23 +2,23 @@
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
#import "MLNOfflineStorage_Private.h" #import "MLNOfflineStorage_Private.h"
#import "MLNTestAssertionHandler.h"
#import "NSBundle+MLNAdditions.h" #import "NSBundle+MLNAdditions.h"
#import "NSDate+MLNAdditions.h" #import "NSDate+MLNAdditions.h"
#import "MLNTestAssertionHandler.h"
#include <mbgl/storage/resource.hpp> #include <mbgl/storage/resource.hpp>
#include <mbgl/util/run_loop.hpp> #include <mbgl/util/run_loop.hpp>
#pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wshadow"
@interface MLNOfflineStorageTests : XCTestCase <MLNOfflineStorageDelegate> @interface MLNOfflineStorageTests : XCTestCase <MLNOfflineStorageDelegate>
@end @end
@implementation MLNOfflineStorageTests @implementation MLNOfflineStorageTests
+ (void)tearDown { + (void)tearDown {
NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory NSURL *cacheDirectoryURL =
[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask inDomain:NSUserDomainMask
appropriateForURL:nil appropriateForURL:nil
create:NO create:NO
@@ -26,13 +26,15 @@
NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"];
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Directory containing database should exist."); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path],
@"Directory containing database should exist.");
NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"];
XCTAssertEqualObjects(cacheURL, MLNOfflineStorage.sharedOfflineStorage.databaseURL); XCTAssertEqualObjects(cacheURL, MLNOfflineStorage.sharedOfflineStorage.databaseURL);
[[NSFileManager defaultManager] removeItemAtURL:cacheURL error:nil]; [[NSFileManager defaultManager] removeItemAtURL:cacheURL error:nil];
XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], @"Database should not exist."); XCTAssertFalse([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path],
@"Database should not exist.");
} }
- (void)setUp { - (void)setUp {
@@ -41,8 +43,13 @@
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
XCTestExpectation *expectation = [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { XCTestExpectation *expectation = [self
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage]
keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
return changeKind == NSKeyValueChangeSetting; return changeKind == NSKeyValueChangeSetting;
}]; }];
if ([MLNOfflineStorage sharedOfflineStorage].packs) { if ([MLNOfflineStorage sharedOfflineStorage].packs) {
@@ -52,14 +59,16 @@
[self waitForExpectationsWithTimeout:10 handler:nil]; [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 - (NSURL *)offlineStorage:(MLNOfflineStorage *)storage
URLForResourceOfKind:(MLNResourceKind)kind URLForResourceOfKind:(MLNResourceKind)kind
withURL:(NSURL *)url { withURL:(NSURL *)url {
if ([url.scheme isEqual: @"test"] && [url.host isEqual: @"api"]) { if ([url.scheme isEqual:@"test"] && [url.host isEqual:@"api"]) {
return [NSURL URLWithString:@"https://api.mapbox.com"]; return [NSURL URLWithString:@"https://api.mapbox.com"];
} else { } else {
return url; return url;
@@ -67,7 +76,8 @@
} }
- (void)testSharedObject { - (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 { - (void)testAddPackForBounds {
@@ -76,84 +86,135 @@
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
/// Somewhere near Grape Grove, Ohio, United States. /// Somewhere near Grape Grove, Ohio, United States.
MLNCoordinateBounds bounds = { MLNCoordinateBounds bounds = {
{ .latitude = 39.70358155855172, .longitude = -83.69506472545841 }, {.latitude = 39.70358155855172, .longitude = -83.69506472545841},
{ .latitude = 39.703818870225376, .longitude = -83.69420641857361 }, {.latitude = 39.703818870225376, .longitude = -83.69420641857361},
}; };
double zoomLevel = 20; double zoomLevel = 20;
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:zoomLevel toZoomLevel:zoomLevel]; MLNTilePyramidOfflineRegion *region =
[[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:zoomLevel
toZoomLevel:zoomLevel];
NSString *nameKey = @"Name"; NSString *nameKey = @"Name";
NSString *name = @"🍇 Grape Grove"; NSString *name = @"🍇 Grape Grove";
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{
nameKey: name, nameKey : name,
}]; }];
__block MLNOfflinePack *pack; __block MLNOfflinePack *pack;
[self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage]
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
return changeKind == NSKeyValueChangeInsertion && indices.count == 1; return changeKind == NSKeyValueChangeInsertion &&
indices.count == 1;
}]; }];
XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; XCTestExpectation *additionCompletionHandlerExpectation =
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { [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."); 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; pack = completionHandlerPack;
[additionCompletionHandlerExpectation fulfill]; [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]; NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
XCTAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary."); XCTAssert([userInfo isKindOfClass:[NSDictionary class]],
XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string."); @"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."); 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) { [self keyValueObservingExpectationForObject:pack
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"state"
const auto state = static_cast<MLNOfflinePackState>([change[NSKeyValueChangeNewKey] longValue]); handler:^BOOL(id _Nonnull observedObject,
return changeKind == NSKeyValueChangeSetting && state == MLNOfflinePackStateInactive; NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
const auto state = static_cast<MLNOfflinePackState>(
[change[NSKeyValueChangeNewKey] longValue]);
return changeKind == NSKeyValueChangeSetting &&
state == MLNOfflinePackStateInactive;
}]; }];
[self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { [self
expectationForNotification:MLNOfflinePackProgressChangedNotification
object:pack
handler:^BOOL(NSNotification *_Nonnull notification) {
MLNOfflinePack *notificationPack = notification.object; MLNOfflinePack *notificationPack = notification.object;
XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], @"Object of notification should be an MLNOfflinePack."); XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]],
@"Object of notification should be an MLNOfflinePack.");
NSDictionary *userInfo = notification.userInfo; NSDictionary *userInfo = notification.userInfo;
XCTAssertNotNil(userInfo, @"Progress change notification should have a userInfo dictionary."); XCTAssertNotNil(
userInfo,
@"Progress change notification should have a userInfo dictionary.");
NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState];
XCTAssert([stateNumber isKindOfClass:[NSNumber class]], @"Progress change notification’s state should be an NSNumber."); XCTAssert(
XCTAssertEqual(stateNumber.integerValue, pack.state, @"State in a progress change notification should match the pack’s state."); [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]; NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress];
XCTAssert([progressValue isKindOfClass:[NSValue class]], @"Progress change notification’s progress should be an NSValue."); XCTAssert(
XCTAssertEqualObjects(progressValue, [NSValue valueWithMLNOfflinePackProgress:pack.progress], @"Progress change notification’s progress should match pack’s progress."); [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; return notificationPack == pack &&
pack.state == MLNOfflinePackStateInactive;
}]; }];
[pack requestProgress]; [pack requestProgress];
[self waitForExpectationsWithTimeout:5 handler:nil]; [self waitForExpectationsWithTimeout:5 handler:nil];
XCTAssertEqualObjects(pack.context, context, @"Offline pack context should match the context specified by the application."); XCTAssertEqualObjects(
pack.context, context,
@"Offline pack context should match the context specified by the application.");
NSString *newName = @"🍑 Peach Grove"; NSString *newName = @"🍑 Peach Grove";
NSData *newContext = [NSKeyedArchiver archivedDataWithRootObject:@{ NSData *newContext = [NSKeyedArchiver archivedDataWithRootObject:@{
nameKey: newName, nameKey : newName,
}]; }];
XCTestExpectation *contextCompletionHandlerExpectation = [self expectationWithDescription:@"set pack completion context handler"]; XCTestExpectation *contextCompletionHandlerExpectation =
[self expectationWithDescription:@"set pack completion context handler"];
__weak MLNOfflinePack *weakPack = pack; __weak MLNOfflinePack *weakPack = pack;
[pack setContext:newContext completionHandler:^(NSError * _Nullable error) { [pack setContext:newContext
completionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertNotNil(weakPack); 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]; [contextCompletionHandlerExpectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:5 handler:nil]; [self waitForExpectationsWithTimeout:5 handler:nil];
@@ -164,67 +225,116 @@
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
double zoomLevel = 20; 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 ] ] ]}"; 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; NSError *error;
MLNShape *shape = [MLNShape shapeWithData: [geojson dataUsingEncoding:NSUTF8StringEncoding] encoding: NSUTF8StringEncoding error:&error]; MLNShape *shape = [MLNShape shapeWithData:[geojson dataUsingEncoding:NSUTF8StringEncoding]
encoding:NSUTF8StringEncoding
error:&error];
XCTAssertNil(error); XCTAssertNil(error);
MLNShapeOfflineRegion *region = [[MLNShapeOfflineRegion alloc] initWithStyleURL:styleURL shape:shape fromZoomLevel:zoomLevel toZoomLevel:zoomLevel]; MLNShapeOfflineRegion *region = [[MLNShapeOfflineRegion alloc] initWithStyleURL:styleURL
shape:shape
fromZoomLevel:zoomLevel
toZoomLevel:zoomLevel];
region.includesIdeographicGlyphs = NO; region.includesIdeographicGlyphs = NO;
NSString *nameKey = @"Name"; NSString *nameKey = @"Name";
NSString *name = @"Utrecht centrum"; NSString *name = @"Utrecht centrum";
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey: name}]; NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey : name}];
__block MLNOfflinePack *pack; __block MLNOfflinePack *pack;
[self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage]
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
return changeKind == NSKeyValueChangeInsertion && indices.count == 1; return changeKind == NSKeyValueChangeInsertion &&
indices.count == 1;
}]; }];
XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; XCTestExpectation *additionCompletionHandlerExpectation =
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { [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."); 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; pack = completionHandlerPack;
[additionCompletionHandlerExpectation fulfill]; [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]; NSDictionary *userInfo = [NSKeyedUnarchiver unarchiveObjectWithData:pack.context];
XCTAssert([userInfo isKindOfClass:[NSDictionary class]], @"Context of offline pack isn’t a dictionary."); XCTAssert([userInfo isKindOfClass:[NSDictionary class]],
XCTAssert([userInfo[nameKey] isKindOfClass:[NSString class]], @"Name of offline pack isn’t a string."); @"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."); 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) { [self keyValueObservingExpectationForObject:pack
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"state"
const auto state = static_cast<MLNOfflinePackState>([change[NSKeyValueChangeNewKey] longValue]); handler:^BOOL(id _Nonnull observedObject,
return changeKind == NSKeyValueChangeSetting && state == MLNOfflinePackStateInactive; NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
const auto state = static_cast<MLNOfflinePackState>(
[change[NSKeyValueChangeNewKey] longValue]);
return changeKind == NSKeyValueChangeSetting &&
state == MLNOfflinePackStateInactive;
}]; }];
[self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { [self
expectationForNotification:MLNOfflinePackProgressChangedNotification
object:pack
handler:^BOOL(NSNotification *_Nonnull notification) {
MLNOfflinePack *notificationPack = notification.object; MLNOfflinePack *notificationPack = notification.object;
XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]], @"Object of notification should be an MLNOfflinePack."); XCTAssert([notificationPack isKindOfClass:[MLNOfflinePack class]],
@"Object of notification should be an MLNOfflinePack.");
NSDictionary *userInfo = notification.userInfo; NSDictionary *userInfo = notification.userInfo;
XCTAssertNotNil(userInfo, @"Progress change notification should have a userInfo dictionary."); XCTAssertNotNil(
userInfo,
@"Progress change notification should have a userInfo dictionary.");
NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState]; NSNumber *stateNumber = userInfo[MLNOfflinePackUserInfoKeyState];
XCTAssert([stateNumber isKindOfClass:[NSNumber class]], @"Progress change notification’s state should be an NSNumber."); XCTAssert(
XCTAssertEqual(stateNumber.integerValue, pack.state, @"State in a progress change notification should match the pack’s state."); [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]; NSValue *progressValue = userInfo[MLNOfflinePackUserInfoKeyProgress];
XCTAssert([progressValue isKindOfClass:[NSValue class]], @"Progress change notification’s progress should be an NSValue."); XCTAssert(
XCTAssertEqualObjects(progressValue, [NSValue valueWithMLNOfflinePackProgress:pack.progress], @"Progress change notification’s progress should match pack’s progress."); [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; return notificationPack == pack &&
pack.state == MLNOfflinePackStateInactive;
}]; }];
[pack requestProgress]; [pack requestProgress];
[self waitForExpectationsWithTimeout:5 handler:nil]; [self waitForExpectationsWithTimeout:5 handler:nil];
@@ -232,22 +342,32 @@
} }
- (void)testInvalidatePack { - (void)testInvalidatePack {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect offline pack to be invalidated without an error."]; XCTestExpectation *expectation =
[self expectationWithDescription:@"Expect offline pack to be invalidated without an error."];
MLNCoordinateBounds bounds = { MLNCoordinateBounds bounds = {
{ .latitude = 48.8660, .longitude = 2.3306 }, {.latitude = 48.8660, .longitude = 2.3306},
{ .latitude = 48.8603, .longitude = 2.3213 }, {.latitude = 48.8603, .longitude = 2.3213},
}; };
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds fromZoomLevel:10 toZoomLevel:11]; withExtension:@"json"];
MLNTilePyramidOfflineRegion *region =
[[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:10
toZoomLevel:11];
NSString *nameKey = @"Name"; NSString *nameKey = @"Name";
NSString *name = @"Paris square"; NSString *name = @"Paris square";
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey: name}]; NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{nameKey : name}];
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region withContext:context completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { [[MLNOfflineStorage sharedOfflineStorage]
addPackForRegion:region
withContext:context
completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) {
XCTAssertNotNil(pack); XCTAssertNotNil(pack);
[[MLNOfflineStorage sharedOfflineStorage] invalidatePack:pack withCompletionHandler:^(NSError * _Nullable) { [[MLNOfflineStorage sharedOfflineStorage] invalidatePack:pack
withCompletionHandler:^(NSError *_Nullable) {
XCTAssertNotNil(pack); XCTAssertNotNil(pack);
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
@@ -262,25 +382,39 @@
MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject;
XCTAssertNotNil(pack, @"Added pack should still exist."); XCTAssertNotNil(pack, @"Added pack should still exist.");
[self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage]
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
return changeKind == NSKeyValueChangeRemoval && indices.count == 1; return changeKind == NSKeyValueChangeRemoval &&
indices.count == 1;
}]; }];
XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"remove pack completion handler"]; XCTestExpectation *completionHandlerExpectation =
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"remove pack completion handler"];
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]; [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 { - (void)testBackupExclusion {
NSURL *cacheDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory NSURL *cacheDirectoryURL =
[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask inDomain:NSUserDomainMask
appropriateForURL:nil appropriateForURL:nil
create:NO create:NO
@@ -289,22 +423,25 @@
NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier]; NSString *bundleIdentifier = [NSBundle mgl_applicationBundleIdentifier];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier]; cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:bundleIdentifier];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"]; cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:@".mapbox"];
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path], @"Cache subdirectory should exist."); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheDirectoryURL.path],
@"Cache subdirectory should exist.");
NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"]; NSURL *cacheURL = [cacheDirectoryURL URLByAppendingPathComponent:@"cache.db"];
XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path], @"Cache database should exist."); XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:cacheURL.path],
@"Cache database should exist.");
NSError *error = nil; NSError *error = nil;
NSNumber *exclusionFlag = nil; NSNumber *exclusionFlag = nil;
[cacheDirectoryURL getResourceValue:&exclusionFlag [cacheDirectoryURL getResourceValue:&exclusionFlag
forKey:NSURLIsExcludedFromBackupKey forKey:NSURLIsExcludedFromBackupKey
error:&error]; error:&error];
XCTAssertTrue(exclusionFlag && [exclusionFlag boolValue], @"Backup exclusion flag should be set for the directory containing the cache database."); 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."); XCTAssertNil(error, @"No errors should be returned when checking backup exclusion flag.");
} }
- (void)addPacks:(NSInteger)count { - (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];
@@ -316,23 +453,25 @@
{{37.7, -122.5}, {37.9, -122.4}} // SF {{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++) { for (int i = 0; i < count; i++) {
dispatch_group_enter(group); dispatch_group_enter(group);
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL bounds:bounds[i] fromZoomLevel:20 toZoomLevel:20]; MLNTilePyramidOfflineRegion *region =
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
@"index": @(i) bounds:bounds[i]
}]; fromZoomLevel:20
toZoomLevel:20];
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"index" : @(i)}];
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region [[MLNOfflineStorage sharedOfflineStorage]
addPackForRegion:region
withContext:context withContext:context
completionHandler:^(MLNOfflinePack * _Nullable pack, NSError * _Nullable error) { completionHandler:^(MLNOfflinePack *_Nullable pack, NSError *_Nullable error) {
XCTAssertNotNil(pack); XCTAssertNotNil(pack);
XCTAssertNil(error); XCTAssertNil(error);
@@ -344,11 +483,10 @@
[expectation fulfill]; [expectation fulfill];
}); });
[self waitForExpectations:@[expectation] timeout:1.0]; [self waitForExpectations:@[ expectation ] timeout:1.0];
} }
- (void)testRemovePackTwiceInSuccession { - (void)testRemovePackTwiceInSuccession {
[self addPacks:1]; [self addPacks:1];
NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; NSUInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count;
@@ -356,13 +494,19 @@
MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject; MLNOfflinePack *pack = [MLNOfflineStorage sharedOfflineStorage].packs.lastObject;
XCTAssertNotNil(pack, @"Added pack should still exist."); XCTAssertNotNil(pack, @"Added pack should still exist.");
[self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage] keyPath:@"packs" handler:^BOOL(id _Nonnull observedObject, NSDictionary * _Nonnull change) { [self keyValueObservingExpectationForObject:[MLNOfflineStorage sharedOfflineStorage]
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind = static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey] unsignedLongValue]);
NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; NSIndexSet *indices = change[NSKeyValueChangeIndexesKey];
return changeKind == NSKeyValueChangeRemoval && indices.count == 1; 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];
@@ -371,8 +515,11 @@
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [[MLNOfflineStorage sharedOfflineStorage]
XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid, @"Removed pack should be invalid in the completion handler."); removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
XCTAssertEqual(pack.state, MLNOfflinePackStateInvalid,
@"Removed pack should be invalid in the completion handler.");
[completionHandlerExpectation fulfill]; [completionHandlerExpectation fulfill];
}]; }];
@@ -380,15 +527,18 @@
[[[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 { - (void)test15536RemovePacksWhileReloading {
// This test triggers // This test triggers
// //
// throw std::runtime_error("Malformed offline region definition"); // throw std::runtime_error("Malformed offline region definition");
@@ -412,8 +562,12 @@
// Simulate what happens the first time sharedOfflineStorage is accessed // Simulate what happens the first time sharedOfflineStorage is accessed
[storage reloadPacks]; [storage reloadPacks];
NSArray *validPacks = [packs filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) { NSArray *validPacks = [packs
MLNOfflinePack *pack = (MLNOfflinePack*)evaluatedObject; filteredArrayUsingPredicate:[NSPredicate
predicateWithBlock:^BOOL(
id _Nullable evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
MLNOfflinePack *pack = (MLNOfflinePack *)evaluatedObject;
return pack.state != MLNOfflinePackStateInvalid; return pack.state != MLNOfflinePackStateInvalid;
}]]; }]];
@@ -423,14 +577,15 @@
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
for (MLNOfflinePack *pack in validPacks) { for (MLNOfflinePack *pack in validPacks) {
[storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [storage removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
[expectation fulfill]; [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?
@@ -439,14 +594,14 @@
// Test to explore https://github.com/mapbox/mapbox-gl-native/issues/15536 // Test to explore https://github.com/mapbox/mapbox-gl-native/issues/15536
- (void)test15536RemovePacksOnBackgroundQueueWhileReloading { - (void)test15536RemovePacksOnBackgroundQueueWhileReloading {
[self addPacks:4]; [self addPacks:4];
NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count; NSInteger countOfPacks = [MLNOfflineStorage sharedOfflineStorage].packs.count;
XCTAssert(countOfPacks > 0); XCTAssert(countOfPacks > 0);
// Now delete packs one by one // Now delete packs one by one
dispatch_queue_t queue = dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue =
dispatch_queue_create("com.mapbox.testRemovePacks", DISPATCH_QUEUE_SERIAL);
XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"]; XCTestExpectation *expectation = [self expectationWithDescription:@"all packs removed"];
expectation.expectedFulfillmentCount = countOfPacks; expectation.expectedFulfillmentCount = countOfPacks;
@@ -456,7 +611,7 @@
// Simulate what happens the first time sharedOfflineStorage is accessed // Simulate what happens the first time sharedOfflineStorage is accessed
[storage reloadPacks]; [storage reloadPacks];
// NSArray *packs = [storage.packs copy]; // NSArray *packs = [storage.packs copy];
dispatch_async(queue, ^{ dispatch_async(queue, ^{
NSArray *packs = storage.packs; NSArray *packs = storage.packs;
@@ -465,8 +620,12 @@
[[[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<NSString *,id> * _Nullable bindings) { NSArray *validPacks = [packs
MLNOfflinePack *pack = (MLNOfflinePack*)evaluatedObject; filteredArrayUsingPredicate:[NSPredicate
predicateWithBlock:^BOOL(
id _Nullable evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
MLNOfflinePack *pack = (MLNOfflinePack *)evaluatedObject;
return pack.state != MLNOfflinePackStateInvalid; return pack.state != MLNOfflinePackStateInvalid;
}]]; }]];
@@ -476,7 +635,8 @@
dispatch_group_t group = dispatch_group_create(); dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); dispatch_group_enter(group);
[storage removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [storage removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
dispatch_group_leave(group); dispatch_group_leave(group);
}]; }];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
@@ -487,7 +647,7 @@
[[[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?
@@ -505,7 +665,7 @@
auto fs = os.mbglOnlineFileSource; auto fs = os.mbglOnlineFileSource;
// Delegate returns "https://api.mapbox.com" as a replacement URL. // Delegate returns "https://api.mapbox.com" as a replacement URL.
const mbgl::Resource resource { mbgl::Resource::Unknown, "test://api" }; const mbgl::Resource resource{mbgl::Resource::Unknown, "test://api"};
std::unique_ptr<mbgl::AsyncRequest> req; std::unique_ptr<mbgl::AsyncRequest> req;
req = fs->request(resource, [&](mbgl::Response res) { req = fs->request(resource, [&](mbgl::Response res) {
req.reset(); req.reset();
@@ -524,71 +684,126 @@
NSFileManager *fileManager = [NSFileManager defaultManager]; NSFileManager *fileManager = [NSFileManager defaultManager];
// Valid database // Valid database
[XCTContext runActivityNamed:@"Valid database" block:^(id<XCTActivity> activity) { [XCTContext
NSURL *resourceURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"sideload_sat" ofType:@"db"]]; runActivityNamed:@"Valid database"
block:^(id<XCTActivity> activity) {
NSURL *resourceURL =
[NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]]
pathForResource:@"sideload_sat"
ofType:@"db"]];
NSError *error; NSError *error;
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:resourceURL.path error:&error]; NSDictionary *fileAttributes =
XCTAssertNil(error, @"Getting the file's attributes should not return an error. (%@)", resourceURL.path); [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]; NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
long long fileSize = [fileSizeNumber longLongValue]; long long fileSize = [fileSizeNumber longLongValue];
long long databaseFileSize = 19218432; long long databaseFileSize = 19218432;
// Merging databases creates an empty file if the file does not exist at the given path. // Merging databases creates an empty file if the file does not exist at the
XCTAssertEqual(fileSize, databaseFileSize, @"The database file size must be:%lld actual size:%lld", databaseFileSize, fileSize); // 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) { [self
const auto changeKind = static_cast<NSKeyValueChange>([change[NSKeyValueChangeKindKey] unsignedLongValue]); keyValueObservingExpectationForObject:[MLNOfflineStorage
NSIndexSet *indices = change[NSKeyValueChangeIndexesKey]; sharedOfflineStorage]
return changeKind == NSKeyValueChangeInsertion && indices.count == 1; keyPath:@"packs"
handler:^BOOL(id _Nonnull observedObject,
NSDictionary *_Nonnull change) {
const auto changeKind =
static_cast<NSKeyValueChange>(
[change[NSKeyValueChangeKindKey]
unsignedLongValue]);
NSIndexSet *indices =
change[NSKeyValueChangeIndexesKey];
return changeKind ==
NSKeyValueChangeInsertion &&
indices.count == 1;
}]; }];
XCTestExpectation *fileAdditionCompletionHandlerExpectation = [self expectationWithDescription:@"add database content completion handler"]; XCTestExpectation *fileAdditionCompletionHandlerExpectation =
[self expectationWithDescription:@"add database content completion handler"];
MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage];
[os addContentsOfURL:resourceURL withCompletionHandler:^(NSURL *fileURL, NSArray<MLNOfflinePack *> * _Nullable packs, NSError * _Nullable error) { [os addContentsOfURL:resourceURL
withCompletionHandler:^(NSURL *fileURL,
NSArray<MLNOfflinePack *> *_Nullable packs,
NSError *_Nullable error) {
XCTAssertNotNil(fileURL, @"The fileURL should not be nil."); XCTAssertNotNil(fileURL, @"The fileURL should not be nil.");
XCTAssertNotNil(packs, @"Adding the contents of the sideload_sat.db should update one pack."); XCTAssertNotNil(
XCTAssertNil(error, @"Adding contents to a file should not return an error."); packs,
for (MLNOfflinePack *pack in [MLNOfflineStorage sharedOfflineStorage].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); NSLog(@"PACK:%@", pack);
} }
[fileAdditionCompletionHandlerExpectation fulfill]; [fileAdditionCompletionHandlerExpectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:10 handler:nil]; [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. // Depending on the database it may update or add a pack. For this case
XCTAssertEqual([MLNOfflineStorage sharedOfflineStorage].packs.count, countOfPacks + 1, @"Adding contents of sideload_sat.db should add one pack."); // 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 // Invalid database type
[XCTContext runActivityNamed:@"Invalid database type" block:^(id<XCTActivity> activity) { [XCTContext
NSURL *resourceURL = [NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]] pathForResource:@"one-liner" ofType:@"json"]]; runActivityNamed:@"Invalid database type"
block:^(id<XCTActivity> activity) {
NSURL *resourceURL =
[NSURL fileURLWithPath:[[NSBundle bundleForClass:[self class]]
pathForResource:@"one-liner"
ofType:@"json"]];
XCTestExpectation *invalidFileCompletionHandlerExpectation = [self expectationWithDescription:@"invalid content database completion handler"]; XCTestExpectation *invalidFileCompletionHandlerExpectation = [self
expectationWithDescription:@"invalid content database completion handler"];
MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage];
[os addContentsOfFile:resourceURL.path withCompletionHandler:^(NSURL *fileURL, NSArray<MLNOfflinePack *> * _Nullable packs, NSError * _Nullable error) { [os addContentsOfFile:resourceURL.path
XCTAssertNotNil(error, @"Passing an invalid offline database file should return an error."); withCompletionHandler:^(NSURL *fileURL,
XCTAssertNil(packs, @"Passing an invalid offline database file should not add packs to the offline database."); NSArray<MLNOfflinePack *> *_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]; [invalidFileCompletionHandlerExpectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:10 handler:nil]; [self waitForExpectationsWithTimeout:10 handler:nil];
}]; }];
// File does not exist // File does not exist
[XCTContext runActivityNamed:@"File does not exist" block:^(id<XCTActivity> activity) { [XCTContext runActivityNamed:@"File does not exist"
block:^(id<XCTActivity> activity) {
NSURL *resourceURL = [NSURL URLWithString:@"nonexistent.db"]; NSURL *resourceURL = [NSURL URLWithString:@"nonexistent.db"];
MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage];
XCTAssertThrowsSpecificNamed([os addContentsOfURL:resourceURL withCompletionHandler:nil], NSException, NSInvalidArgumentException, "MLNOfflineStorage should rise an exception if an invalid database file is passed."); 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 // URL to a non-file
[XCTContext runActivityNamed:@"URL to a non-file" block:^(id<XCTActivity> activity) { [XCTContext runActivityNamed:@"URL to a non-file"
block:^(id<XCTActivity> activity) {
NSURL *resourceURL = [NSURL URLWithString:@"https://www.mapbox.com"]; NSURL *resourceURL = [NSURL URLWithString:@"https://www.mapbox.com"];
MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage]; MLNOfflineStorage *os = [MLNOfflineStorage sharedOfflineStorage];
XCTAssertThrowsSpecificNamed([os addContentsOfURL:resourceURL withCompletionHandler:nil], NSException, NSInvalidArgumentException, "MLNOfflineStorage should rise an exception if an invalid URL file is passed."); XCTAssertThrowsSpecificNamed(
[os addContentsOfURL:resourceURL withCompletionHandler:nil],
NSException, NSInvalidArgumentException,
"MLNOfflineStorage should rise an exception if an invalid URL file "
"is passed.");
}]; }];
} }
@@ -599,12 +814,18 @@
std::string testData("test data"); std::string testData("test data");
NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()]; NSData *data = [NSData dataWithBytes:testData.c_str() length:testData.length()];
__block std::unique_ptr<mbgl::AsyncRequest> req; __block std::unique_ptr<mbgl::AsyncRequest> req;
[os preloadData:data forURL:styleURL modificationDate:nil expirationDate:nil eTag:nil mustRevalidate:NO completionHandler:^(NSURL * url, NSError * _Nullable error) { [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"); XCTAssertFalse(error, @"preloadData should not return an error");
XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid"); XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid");
auto fs = os.mbglDatabaseFileSource; 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) { req = fs->request(resource, [&](mbgl::Response res) {
XCTAssertFalse(res.error.get(), @"Request should not return an error"); XCTAssertFalse(res.error.get(), @"Request should not return an error");
XCTAssertTrue(res.data.get(), @"Request should return data"); XCTAssertTrue(res.data.get(), @"Request should return data");
@@ -629,19 +850,29 @@
__block NSDate *now = [NSDate date]; __block NSDate *now = [NSDate date];
__block NSDate *future = [now dateByAddingTimeInterval:600]; __block NSDate *future = [now dateByAddingTimeInterval:600];
__block std::unique_ptr<mbgl::AsyncRequest> req; __block std::unique_ptr<mbgl::AsyncRequest> req;
[os preloadData:data forURL:styleURL modificationDate:now expirationDate:future eTag:@"some etag" mustRevalidate:YES completionHandler:^(NSURL * url, NSError * _Nullable error){ [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"); XCTAssertFalse(error, @"preloadData should not return an error");
XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid"); XCTAssertEqual(styleURL, url, @"Preloaded resource url is invalid");
auto fs = os.mbglDatabaseFileSource; auto fs = os.mbglDatabaseFileSource;
const mbgl::Resource resource { mbgl::Resource::Unknown, "https://api.mapbox.com/some/thing1" }; const mbgl::Resource resource{mbgl::Resource::Unknown,
req = fs->request(resource, [&, tNow = now.timeIntervalSince1970, tFuture = future.timeIntervalSince1970](mbgl::Response res) { "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"); XCTAssertFalse(res.error.get(), @"Request should not return an error");
XCTAssertTrue(res.data.get(), @"Request should return data"); XCTAssertTrue(res.data.get(), @"Request should return data");
XCTAssertTrue(res.modified, @"Request should have a modification timestamp"); XCTAssertTrue(res.modified, @"Request should have a modification timestamp");
XCTAssertEqual(MLNTimeIntervalFromDuration(res.modified->time_since_epoch()), floor(tNow), @"Modification timestamp should roundtrip"); XCTAssertEqual(MLNTimeIntervalFromDuration(res.modified->time_since_epoch()), floor(tNow),
@"Modification timestamp should roundtrip");
XCTAssertTrue(res.expires, @"Request should have an expiration timestamp"); XCTAssertTrue(res.expires, @"Request should have an expiration timestamp");
XCTAssertEqual(MLNTimeIntervalFromDuration(res.expires->time_since_epoch()), floor(tFuture), @"Expiration timestamp should roundtrip"); XCTAssertEqual(MLNTimeIntervalFromDuration(res.expires->time_since_epoch()),
floor(tFuture), @"Expiration timestamp should roundtrip");
XCTAssertTrue(res.etag, @"Request should have an entity tag"); XCTAssertTrue(res.etag, @"Request should have an entity tag");
XCTAssertEqual(*res.etag, "some etag", @"Entity tag should roundtrip"); XCTAssertEqual(*res.etag, "some etag", @"Entity tag should roundtrip");
XCTAssertTrue(res.mustRevalidate, @"Request should require revalidation"); XCTAssertTrue(res.mustRevalidate, @"Request should require revalidation");
@@ -654,10 +885,15 @@
} }
- (void)testSetMaximumAmbientCache { - (void)testSetMaximumAmbientCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect maximum cache size to be raised without an error."]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] setMaximumAmbientCacheSize:0 withCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"Expect maximum cache size to be raised without an error."];
[[MLNOfflineStorage sharedOfflineStorage]
setMaximumAmbientCacheSize:0
withCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[[MLNOfflineStorage sharedOfflineStorage] setMaximumAmbientCacheSize:50*1024*1024 withCompletionHandler:^(NSError * _Nullable error) { [[MLNOfflineStorage sharedOfflineStorage]
setMaximumAmbientCacheSize:50 * 1024 * 1024
withCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
}]; }];
@@ -667,8 +903,10 @@
} }
- (void)testInvalidateAmbientCache { - (void)testInvalidateAmbientCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be invalidated without an error."]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] invalidateAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"Expect cache to be invalidated without an error."];
[[MLNOfflineStorage sharedOfflineStorage]
invalidateAmbientCacheWithCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
}]; }];
@@ -676,8 +914,10 @@
} }
- (void)testClearCache { - (void)testClearCache {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect cache to be cleared without an error."]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] clearAmbientCacheWithCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"Expect cache to be cleared without an error."];
[[MLNOfflineStorage sharedOfflineStorage]
clearAmbientCacheWithCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
}]; }];
@@ -685,8 +925,10 @@
} }
- (void)testResetDatabase { - (void)testResetDatabase {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"Expect database to be reset without an error."];
[[MLNOfflineStorage sharedOfflineStorage]
resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
}]; }];

View File

@@ -1,8 +1,8 @@
#import <XCTest/XCTest.h>
#import <Mapbox.h> #import <Mapbox.h>
#import <XCTest/XCTest.h>
#import "NSPredicate+MLNPrivateAdditions.h"
#import "MLNValueEvaluator.h" #import "MLNValueEvaluator.h"
#import "NSPredicate+MLNPrivateAdditions.h"
@implementation NSString (MLNAdditions) @implementation NSString (MLNAdditions)
@@ -21,23 +21,34 @@
@implementation MLNPredicateTests @implementation MLNPredicateTests
- (void)testUnsupportedFilterPredicates { - (void)testUnsupportedFilterPredicates {
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter, NSException, NSInvalidArgumentException); XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a BEGINSWITH 'L'"].mgl_filter,
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter, NSException, NSInvalidArgumentException); NSException, NSInvalidArgumentException);
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a LIKE 'glob?trotter'"].mgl_filter, NSException, NSInvalidArgumentException); XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a ENDSWITH 'itude'"].mgl_filter,
XCTAssertThrowsSpecificNamed([NSPredicate predicateWithFormat:@"a MATCHES 'i\\w{18}n'"].mgl_filter, NSException, NSInvalidArgumentException); NSException, NSInvalidArgumentException);
NSPredicate *selectorPredicate = [NSPredicate predicateWithFormat:@"(SELF isKindOfClass: %@)", [MLNPolyline class]]; XCTAssertThrowsSpecificNamed(
XCTAssertThrowsSpecificNamed(selectorPredicate.mgl_filter, NSException, NSInvalidArgumentException); [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<NSString *, id> * _Nullable bindings) { XCTAssertThrowsSpecificNamed(
[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
NSDictionary<NSString *, id> *_Nullable bindings) {
XCTAssertTrue(NO, @"Predicate block should not be evaluated."); XCTAssertTrue(NO, @"Predicate block should not be evaluated.");
return NO; return NO;
}].mgl_filter, NSException, NSInvalidArgumentException); }].mgl_filter,
NSException, NSInvalidArgumentException);
} }
- (void)testComparisonPredicates { - (void)testComparisonPredicates {
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"x == YES"];
NSArray *jsonExpression = @[@"==", @[@"get", @"x"], @YES]; NSArray *jsonExpression = @[ @"==", @[ @"get", @"x" ], @YES ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -45,7 +56,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') < 5"];
NSArray *jsonExpression = @[@"<", @[@"to-number", @[@"get", @"x"]], @5]; NSArray *jsonExpression = @[ @"<", @[ @"to-number", @[ @"get", @"x" ] ], @5 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -53,7 +64,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') > 5"];
NSArray *jsonExpression = @[@">", @[@"to-number", @[@"get", @"x"]], @5]; NSArray *jsonExpression = @[ @">", @[ @"to-number", @[ @"get", @"x" ] ], @5 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -61,7 +72,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') <= 5"];
NSArray *jsonExpression = @[@"<=", @[@"to-number", @[@"get", @"x"]], @5]; NSArray *jsonExpression = @[ @"<=", @[ @"to-number", @[ @"get", @"x" ] ], @5 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -69,7 +80,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') >= 5"];
NSArray *jsonExpression = @[@">=", @[@"to-number", @[@"get", @"x"]], @5]; NSArray *jsonExpression = @[ @">=", @[ @"to-number", @[ @"get", @"x" ] ], @5 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -77,7 +88,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSString') > 'value'"];
NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"x"]], @"value"]; NSArray *jsonExpression = @[ @">", @[ @"to-string", @[ @"get", @"x" ] ], @"value" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -85,77 +96,67 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b"]; NSArray *jsonExpression = @[ @"==", @[ @"get", @"a" ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType = 'Point'"];
NSArray *jsonExpression = @[@"==", @[@"geometry-type"], @"Point"]; NSArray *jsonExpression = @[ @"==", @[ @"geometry-type" ], @"Point" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = 67086180"];
NSArray *jsonExpression = @[@"==", @[@"id"], @67086180]; NSArray *jsonExpression = @[ @"==", @[ @"id" ], @67086180 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier = nil"];
NSArray *jsonExpression = @[@"==", @[@"id"], [NSNull null]]; NSArray *jsonExpression = @[ @"==", @[ @"id" ], [NSNull null] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a = nil"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], [NSNull null]]; NSArray *jsonExpression = @[ @"==", @[ @"get", @"a" ], [NSNull null] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$geometryType != 'Point'"];
NSArray *jsonExpression = @[@"!=", @[@"geometry-type"], @"Point"]; NSArray *jsonExpression = @[ @"!=", @[ @"geometry-type" ], @"Point" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != 67086180"];
NSArray *jsonExpression = @[@"!=", @[@"id"], @67086180]; NSArray *jsonExpression = @[ @"!=", @[ @"id" ], @67086180 ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier != nil"];
NSArray *jsonExpression = @[@"!=", @[@"id"], [NSNull null]]; NSArray *jsonExpression = @[ @"!=", @[ @"id" ], [NSNull null] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != 'b'"];
NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b"]; NSArray *jsonExpression = @[ @"!=", @[ @"get", @"a" ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a != nil"];
NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], [NSNull null]]; NSArray *jsonExpression = @[ @"!=", @[ @"get", @"a" ], [NSNull null] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') < 'b'"];
NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b"]; NSArray *jsonExpression = @[ @"<", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -163,7 +164,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <= 'b'"];
NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b"]; NSArray *jsonExpression = @[ @"<=", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -171,7 +172,7 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') > 'b'"];
NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b"]; NSArray *jsonExpression = @[ @">", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -179,71 +180,106 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >= 'b'"];
NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b"]; NSArray *jsonExpression = @[ @">=", @[ @"to-string", @[ @"get", @"a" ] ], @"b" ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') BETWEEN {'b', 'z'}"]; NSPredicate *predicate =
NSArray *jsonExpression =@[@"all", @[@"<=", @"b", @[@"to-string", @[@"get", @"a"]]], @[@"<=", @[@"to-string", @[@"get", @"a"]], @"z"]]; [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(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; NSExpression *limits = [NSExpression expressionForAggregate:@[
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; [NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]
NSArray *jsonExpression = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@"<=", @[@"to-number", @[@"get", @"x"]], @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); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@"<=", @[@"to-number", @[@"get", @"x"]], @100]]; NSArray *expected = @[
NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; @"all", @[ @"<=", @10, @[ @"to-number", @[ @"get", @"x" ] ] ],
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; @[ @"<=", @[ @"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(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSArray *expected = @[@"all", @[@"<=", @10, @[@"to-number", @[@"get", @"x"]]], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; NSArray *expected = @[
NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; @"all", @[ @"<=", @10, @[ @"to-number", @[ @"get", @"x" ] ] ],
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; @[ @">=", @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); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSArray *expected = @[@"all", @[@">=", @[@"to-number", @[@"get", @"x"]], @10], @[@">=", @100, @[@"to-number", @[@"get", @"x"]]]]; NSArray *expected = @[
NSExpression *limits = [NSExpression expressionForAggregate:@[[NSExpression expressionForConstantValue:@10], [NSExpression expressionForConstantValue:@100]]]; @"all", @[ @">=", @[ @"to-number", @[ @"get", @"x" ] ], @10 ],
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(x, 'NSNumber') BETWEEN %@", limits]; @[ @">=", @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); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:NO]; mustRoundTrip:NO];
} }
{ {
NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"]; NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"$featureIdentifier IN { 6, 5, 4, 3}"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"to-number", @[@"id"]], @[@"literal", @[@3002970001, @3004140052, @3002950027, @3002970033]]]; NSArray *expected = @[
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST($featureIdentifier, 'NSNumber') IN { 3002970001, 3004140052, 3002950027, 3002970033 }"]; @"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(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"!", @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]]; NSArray *expected =
@[ @"!", @[ @"in", @[ @"get", @"x" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT x IN { 6, 5, 4, 3}"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
@@ -251,7 +287,7 @@
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"get", @"a"], @[@"literal", @[@"b", @"c"]]]; NSArray *expected = @[ @"in", @[ @"get", @"a" ], @[ @"literal", @[ @"b", @"c" ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a IN { 'b', 'c' }"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
@@ -259,16 +295,21 @@
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; NSArray *expected =
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"], @[@"LineString", @"Polygon"]]; @[ @"in", @[ @"geometry-type" ], @[ @"literal", @[ @"LineString", @"Polygon" ] ] ];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"%@ IN %@", [NSExpression expressionForVariable:@"geometryType"],
@[ @"LineString", @"Polygon" ]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}",
[NSExpression expressionForVariable:@"geometryType"]];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"get", @"x"], @[@"literal", @[@6, @5, @4, @3]]]; NSArray *expected = @[ @"in", @[ @"get", @"x" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3 } CONTAINS x"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3 } CONTAINS x"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"x IN { 6, 5, 4, 3 }"]; NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"x IN { 6, 5, 4, 3 }"];
@@ -277,25 +318,32 @@
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"geometry-type"], @[@"literal", @[@"LineString", @"Polygon"]]]; NSArray *expected =
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[@"LineString", @"Polygon"], [NSExpression expressionForVariable:@"geometryType"]]; @[ @"in", @[ @"geometry-type" ], @[ @"literal", @[ @"LineString", @"Polygon" ] ] ];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"%@ CONTAINS %@", @[ @"LineString", @"Polygon" ],
[NSExpression expressionForVariable:@"geometryType"]];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}", [NSExpression expressionForVariable:@"geometryType"]]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"%@ IN {'LineString', 'Polygon'}",
[NSExpression expressionForVariable:@"geometryType"]];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"]; NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"{ 6, 5, 4, 3} CONTAINS $featureIdentifier"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; NSArray *expected = @[ @"in", @"📍", @[ @"get", @"name" ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN name"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN name"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
@@ -303,7 +351,7 @@
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @"📍", @[@"get", @"name"]]; NSArray *expected = @[ @"in", @"📍", @[ @"get", @"name" ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '📍'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS '📍'"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN name"]; NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN name"];
@@ -312,92 +360,113 @@
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; NSArray *expected = @[ @"in", @"📍", @[ @"literal", @"Pinole" ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"];
#if TARGET_OS_IPHONE #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 #endif
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter],
[NSPredicate predicateWithValue:NO]);
} }
{ {
NSArray *expected = @[@"in", @"📍", @[@"literal", @"Pinole"]]; NSArray *expected = @[ @"in", @"📍", @[ @"literal", @"Pinole" ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'Pinole' CONTAINS '📍'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"'Pinole' CONTAINS '📍'"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"]; NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"'📍' IN 'Pinole'"];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter], [NSPredicate predicateWithValue:NO]); XCTAssertEqualObjects([NSPredicate mgl_predicateWithFilter:predicate.mgl_filter],
[NSPredicate predicateWithValue:NO]);
} }
{ {
NSArray *expected = @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]; NSArray *expected = @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} = $featureIdentifier"]; NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} = $featureIdentifier"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"$featureIdentifier IN {6, 5, 4, 3}"];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"!", @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]]; NSArray *expected = @[ @"!", @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} = $featureIdentifier"]; NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} = $featureIdentifier"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSArray *expected = @[@"!", @[@"in", @[@"id"], @[@"literal", @[@6, @5, @4, @3]]]]; NSArray *expected = @[ @"!", @[ @"in", @[ @"id" ], @[ @"literal", @[ @6, @5, @4, @3 ] ] ] ];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} != $featureIdentifier"]; NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} != $featureIdentifier"];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
NSPredicate *predicateAfter = [NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"]; NSPredicate *predicateAfter =
[NSPredicate predicateWithFormat:@"NOT $featureIdentifier IN {6, 5, 4, 3}"];
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicateAfter);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:expected]
mustRoundTrip:YES]; mustRoundTrip:YES];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} != $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} != $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} > $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} > $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} != $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"NONE {6, 5, 4, 3} != $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} = $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} = $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} < $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} < $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
{ {
NSArray *expected = @[ NSArray *expected = @[
@"within", @"within",
@{ @{
@"type": @"Polygon", @"type" : @"Polygon",
@"coordinates": @[ @"coordinates" : @[
@[ @[
@[@0, @0], @[ @0, @0 ],
@[@0, @1], @[ @0, @1 ],
@[@1, @1], @[ @1, @1 ],
@[@1, @0], @[ @1, @0 ],
], ],
], ],
}, },
]; ];
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
{ .latitude = 0, .longitude = 0 }, {.latitude = 0, .longitude = 0},
{ .latitude = 1, .longitude = 0 }, {.latitude = 1, .longitude = 0},
{ .latitude = 1, .longitude = 1 }, {.latitude = 1, .longitude = 1},
{ .latitude = 0, .longitude = 1 }, {.latitude = 0, .longitude = 1},
}; };
MLNPolygon *shape = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; MLNPolygon *shape =
[MLNPolygon polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", shape]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF IN %@", shape];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:expected], predicate);
@@ -408,29 +477,31 @@
NSArray *expected = @[ NSArray *expected = @[
@"within", @"within",
@{ @{
@"type": @"Feature", @"type" : @"Feature",
@"id": @"unit", @"id" : @"unit",
@"properties": @{}, @"properties" : @{},
@"geometry": @{ @"geometry" : @{
@"type": @"Polygon", @"type" : @"Polygon",
@"coordinates": @[ @"coordinates" : @[
@[ @[
@[@0, @0], @[ @0, @0 ],
@[@0, @1], @[ @0, @1 ],
@[@1, @1], @[ @1, @1 ],
@[@1, @0], @[ @1, @0 ],
], ],
], ],
}, },
}, },
]; ];
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
{ .latitude = 0, .longitude = 0 }, {.latitude = 0, .longitude = 0},
{ .latitude = 1, .longitude = 0 }, {.latitude = 1, .longitude = 0},
{ .latitude = 1, .longitude = 1 }, {.latitude = 1, .longitude = 1},
{ .latitude = 0, .longitude = 1 }, {.latitude = 0, .longitude = 1},
}; };
MLNPolygonFeature *feature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates) / sizeof(coordinates[0])]; MLNPolygonFeature *feature =
[MLNPolygonFeature polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])];
feature.identifier = @"unit"; feature.identifier = @"unit";
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS SELF", feature]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%@ CONTAINS SELF", feature];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, expected);
@@ -444,97 +515,140 @@
- (void)testComparisonPredicatesWithOptions { - (void)testComparisonPredicatesWithOptions {
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[c] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[c] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @YES}]]; NSArray *jsonExpression = @[
@"==", @[ @"get", @"a" ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @YES} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
#if TARGET_OS_IPHONE #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 #endif
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[d] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[d] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @YES, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@"==", @[ @"get", @"a" ], @"b",
@[ @"collator",
@{@"case-sensitive" : @YES,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a =[cd] 'b'"];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@"==", @[ @"get", @"a" ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a !=[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a !=[cd] 'b'"];
NSArray *jsonExpression = @[@"!=", @[@"get", @"a"], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@"!=", @[ @"get", @"a" ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <[cd] 'b'"];
NSArray *jsonExpression = @[@"<", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@"<", @[ @"to-string", @[ @"get", @"a" ] ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <=[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') <=[cd] 'b'"];
NSArray *jsonExpression = @[@"<=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@"<=", @[ @"to-string", @[ @"get", @"a" ] ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >[cd] 'b'"];
NSArray *jsonExpression = @[@">", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@">", @[ @"to-string", @[ @"get", @"a" ] ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >=[cd] 'b'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"CAST(a, 'NSString') >=[cd] 'b'"];
NSArray *jsonExpression = @[@">=", @[@"to-string", @[@"get", @"a"]], @"b", @[@"collator", @{@"case-sensitive": @NO, @"diacritic-sensitive": @NO}]]; NSArray *jsonExpression = @[
@">=", @[ @"to-string", @[ @"get", @"a" ] ], @"b",
@[ @"collator",
@{@"case-sensitive" : @NO,
@"diacritic-sensitive" : @NO} ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"TRUE = MLN_FUNCTION('==', a, 'b', MLN_FUNCTION('collator', %@))", @{ NSPredicate *predicate = [NSPredicate
@"case-sensitive": @NO, predicateWithFormat:@"TRUE = MLN_FUNCTION('==', a, 'b', MLN_FUNCTION('collator', %@))", @{
@"diacritic-sensitive": @NO, @"case-sensitive" : @NO,
@"locale": @"tlh", @"diacritic-sensitive" : @NO,
@"locale" : @"tlh",
}]; }];
NSArray *jsonExpression = @[@"==", @[@"get", @"a"], @"b", NSArray *jsonExpression = @[
@[@"collator", @"==", @[ @"get", @"a" ], @"b",
@{@"case-sensitive": @NO, @[ @"collator",
@"diacritic-sensitive": @NO, @{@"case-sensitive" : @NO,
@"locale": @"tlh"}]]; @"diacritic-sensitive" : @NO,
@"locale" : @"tlh"} ]
];
XCTAssertEqualObjects([predicate.mgl_jsonExpressionObject lastObject], jsonExpression); XCTAssertEqualObjects([predicate.mgl_jsonExpressionObject lastObject], jsonExpression);
} }
// https://github.com/mapbox/mapbox-gl-js/issues/9339 // https://github.com/mapbox/mapbox-gl-js/issues/9339
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"ANY {6, 5, 4, 3} =[c] $featureIdentifier"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [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"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [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"]; NSPredicate *predicate =
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException, NSInvalidArgumentException); [NSPredicate predicateWithFormat:@"ALL {6, 5, 4, 3} !=[cd] $featureIdentifier"];
XCTAssertThrowsSpecificNamed(predicate.mgl_jsonExpressionObject, NSException,
NSInvalidArgumentException);
} }
} }
- (void)testCompoundPredicates { - (void)testCompoundPredicates {
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' AND c == 'd'"];
NSArray *jsonExpression = @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; NSArray *jsonExpression =
@[ @"all", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -542,7 +656,8 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a == 'b' OR c == 'd'"];
NSArray *jsonExpression = @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]; NSArray *jsonExpression =
@[ @"any", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -550,7 +665,9 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' AND c == 'd')"];
NSArray *jsonExpression = @[@"!", @[@"all", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; NSArray *jsonExpression = @[
@"!", @[ @"all", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -558,7 +675,9 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT(a == 'b' OR c == 'd')"];
NSArray *jsonExpression = @[@"!", @[@"any", @[@"==", @[@"get", @"a"], @"b"], @[@"==", @[@"get", @"c"], @"d"]]]; NSArray *jsonExpression = @[
@"!", @[ @"any", @[ @"==", @[ @"get", @"a" ], @"b" ], @[ @"==", @[ @"get", @"c" ], @"d" ] ]
];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate); XCTAssertEqualObjects([NSPredicate predicateWithMLNJSONObject:jsonExpression], predicate);
[self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression] [self testSymmetryWithPredicate:[NSPredicate predicateWithMLNJSONObject:jsonExpression]
@@ -566,27 +685,27 @@
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a == nil"];
NSArray *jsonExpression = @[@"!", @[@"==", @[@"get", @"a"], [NSNull null]]]; NSArray *jsonExpression = @[ @"!", @[ @"==", @[ @"get", @"a" ], [NSNull null] ] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
{ {
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT a != nil"];
NSArray *jsonExpression = @[@"!", @[@"!=", @[@"get", @"a"], [NSNull null]]]; NSArray *jsonExpression = @[ @"!", @[ @"!=", @[ @"get", @"a" ], [NSNull null] ] ];
XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression); XCTAssertEqualObjects(predicate.mgl_jsonExpressionObject, jsonExpression);
[self testSymmetryWithPredicate:predicate [self testSymmetryWithPredicate:predicate mustRoundTrip:NO];
mustRoundTrip:NO];
} }
} }
- (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate mustRoundTrip:(BOOL)mustRoundTrip { - (void)testSymmetryWithPredicate:(NSPredicate *)forwardPredicate
mustRoundTrip:(BOOL)mustRoundTrip {
auto forwardFilter = forwardPredicate.mgl_filter; auto forwardFilter = forwardPredicate.mgl_filter;
NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter]; NSPredicate *forwardPredicateAfter = [NSPredicate mgl_predicateWithFilter:forwardFilter];
if (mustRoundTrip) { if (mustRoundTrip) {
// A collection of ints may turn into an aggregate of longs, for // A collection of ints may turn into an aggregate of longs, for
// example, so compare formats instead of the predicates themselves. // example, so compare formats instead of the predicates themselves.
XCTAssertEqualObjects(forwardPredicate.predicateFormat.stringByRemovingPointerAddresses, forwardPredicateAfter.predicateFormat.stringByRemovingPointerAddresses); XCTAssertEqualObjects(forwardPredicate.predicateFormat.stringByRemovingPointerAddresses,
forwardPredicateAfter.predicateFormat.stringByRemovingPointerAddresses);
} else { } else {
XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter); XCTAssertEqualObjects(forwardPredicate, forwardPredicateAfter);
} }

View File

@@ -3,9 +3,9 @@
#import <mbgl/storage/resource.hpp> #import <mbgl/storage/resource.hpp>
namespace mbgl { namespace mbgl {
extern NSURL *resourceURL(const Resource& resource); extern NSURL *resourceURL(const Resource &resource);
extern BOOL isValidMapboxEndpoint(NSURL *url); extern BOOL isValidMapboxEndpoint(NSURL *url);
} } // namespace mbgl
@interface MLNResourceTests : XCTestCase @interface MLNResourceTests : XCTestCase
@end @end
@@ -27,13 +27,13 @@ namespace mbgl {
} }
- (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL { - (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL {
using namespace mbgl; using namespace mbgl;
// Is our test URL "correct" for subsequent checks? // Is our test URL "correct" for subsequent checks?
{ {
NSURL *url = [NSURL URLWithString:@(testURL.c_str())]; NSURL *url = [NSURL URLWithString:@(testURL.c_str())];
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
NSArray<NSURLQueryItem *> *items = components.queryItems; NSArray<NSURLQueryItem *> *items = components.queryItems;
XCTAssert(items.count == 2); XCTAssert(items.count == 2);
} }
@@ -45,7 +45,8 @@ namespace mbgl {
{ {
NSURL *url = resourceURL(resource); NSURL *url = resourceURL(resource);
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
// For offline, we expect a single offline query item // For offline, we expect a single offline query item
NSInteger foundCount = 0; NSInteger foundCount = 0;

View File

@@ -13,19 +13,26 @@
@implementation MLNShapeSourceTests @implementation MLNShapeSourceTests
- (void)testGeoJSONOptionsFromDictionary { - (void)testGeoJSONOptionsFromDictionary {
NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]]; NSExpression *reduceExpression =
[NSExpression expressionForFunction:@"sum:"
arguments:@[
[NSExpression expressionForKeyPath:@"featureAccumulated"],
[NSExpression expressionForKeyPath:@"sumValue"]
]];
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"]; NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
NSArray *clusterPropertyArray = @[reduceExpression, mapExpression]; NSArray *clusterPropertyArray = @[ reduceExpression, mapExpression ];
NSDictionary *options = @{MLNShapeSourceOptionClustered: @YES, NSDictionary *options = @{
MLNShapeSourceOptionClusterRadius: @42, MLNShapeSourceOptionClustered : @YES,
MLNShapeSourceOptionClusterMinPoints: @3, MLNShapeSourceOptionClusterRadius : @42,
MLNShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray}, MLNShapeSourceOptionClusterMinPoints : @3,
MLNShapeSourceOptionMaximumZoomLevelForClustering: @98, MLNShapeSourceOptionClusterProperties : @{@"sumValue" : clusterPropertyArray},
MLNShapeSourceOptionMaximumZoomLevel: @99, MLNShapeSourceOptionMaximumZoomLevelForClustering : @98,
MLNShapeSourceOptionBuffer: @1976, MLNShapeSourceOptionMaximumZoomLevel : @99,
MLNShapeSourceOptionSimplificationTolerance: @0.42, MLNShapeSourceOptionBuffer : @1976,
MLNShapeSourceOptionLineDistanceMetrics: @YES, MLNShapeSourceOptionSimplificationTolerance : @0.42,
MLNShapeSourceOptionSynchronousUpdate: @YES}; MLNShapeSourceOptionLineDistanceMetrics : @YES,
MLNShapeSourceOptionSynchronousUpdate : @YES
};
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options); auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
XCTAssertTrue(mbglOptions->cluster); XCTAssertTrue(mbglOptions->cluster);
@@ -39,7 +46,7 @@
XCTAssertTrue(!mbglOptions->clusterProperties.empty()); XCTAssertTrue(!mbglOptions->clusterProperties.empty());
XCTAssertTrue(mbglOptions->synchronousUpdate); XCTAssertTrue(mbglOptions->synchronousUpdate);
options = @{MLNShapeSourceOptionClustered: @"number 1"}; options = @{MLNShapeSourceOptionClustered : @"number 1"};
XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options)); XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options));
} }
@@ -50,10 +57,12 @@
- (void)testUnclusterableShape { - (void)testUnclusterableShape {
NSDictionary *options = @{ NSDictionary *options = @{
MLNShapeSourceOptionClustered: @YES, MLNShapeSourceOptionClustered : @YES,
}; };
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:[[MLNPointFeature alloc] init] options:options]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id"
shape:[[MLNPointFeature alloc] init]
options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]); XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
@@ -62,15 +71,19 @@
} }
- (void)testMLNShapeSourceWithDataMultipleFeatures { - (void)testMLNShapeSourceWithDataMultipleFeatures {
NSString *geoJSON =
NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}"; @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": "
@"{},\"geometry\": {\"type\": \"LineString\",\"coordinates\": "
@"[[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error; NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertNotNil(shape); XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shape options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:shape
options:nil];
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape; MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
XCTAssertNotNil(collection); XCTAssertNotNil(collection);
@@ -79,24 +92,31 @@
} }
- (void)testMLNShapeSourceWithSingleGeometry { - (void)testMLNShapeSourceWithSingleGeometry {
NSData *data = [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding]; NSData *data =
[@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error; NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertNotNil(shape); XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
shape:shape
options:nil];
XCTAssertNotNil(source.shape); XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]); XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]);
} }
- (void)testMLNGeoJSONSourceWithSingleFeature { - (void)testMLNGeoJSONSourceWithSingleFeature {
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]; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error; NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error); XCTAssertNil(error);
XCTAssertNotNil(shape); XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
shape:shape
options:nil];
XCTAssertNotNil(source.shape); XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]); XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNPointFeature *feature = (MLNPointFeature *)source.shape; MLNPointFeature *feature = (MLNPointFeature *)source.shape;
@@ -104,10 +124,14 @@
} }
- (void)testMLNShapeSourceWithPolylineFeatures { - (void)testMLNShapeSourceWithPolylineFeatures {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 0),
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:2]; 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); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]);
@@ -115,35 +139,40 @@
- (void)testMLNShapeSourceWithPolygonFeatures { - (void)testMLNShapeSourceWithPolygonFeatures {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5];
polygonFeature.identifier = @"feature-id"; polygonFeature.identifier = @"feature-id";
NSString *stringAttribute = @"string"; NSString *stringAttribute = @"string";
NSNumber *boolAttribute = [NSNumber numberWithBool:YES]; NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23]; NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
NSDictionary *nestedDictionaryValue = @{@"nested-key-1": @"nested-string-value"}; NSDictionary *nestedDictionaryValue = @{@"nested-key-1" : @"nested-string-value"};
NSArray *arrayValue = @[@"string-value", @2]; NSArray *arrayValue = @[ @"string-value", @2 ];
NSDictionary *dictionaryValue = @{@"key-1": @"string-value", NSDictionary *dictionaryValue = @{
@"key-2": @1, @"key-1" : @"string-value",
@"key-3": nestedDictionaryValue, @"key-2" : @1,
@"key-4": arrayValue}; @"key-3" : nestedDictionaryValue,
NSArray *arrayOfArrays = @[@[@1, @"string-value", @[@"jagged"]]]; @"key-4" : arrayValue
NSArray *arrayOfDictionaries = @[@{@"key": @"value"}]; };
NSArray *arrayOfArrays = @[ @[ @1, @"string-value", @[ @"jagged" ] ] ];
NSArray *arrayOfDictionaries = @[ @{@"key" : @"value"} ];
polygonFeature.attributes = @{@"name": stringAttribute, polygonFeature.attributes = @{
@"bool": boolAttribute, @"name" : stringAttribute,
@"double": doubleAttribute, @"bool" : boolAttribute,
@"dictionary-attribute": dictionaryValue, @"double" : doubleAttribute,
@"array-attribute": arrayValue, @"dictionary-attribute" : dictionaryValue,
@"array-of-array-attribute": arrayOfArrays, @"array-attribute" : arrayValue,
@"array-of-dictionary-attribute": arrayOfDictionaries}; @"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); XCTAssertNotNil(source.shape);
MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape; MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape;
@@ -153,45 +182,55 @@
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"], dictionaryValue); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"],
dictionaryValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"], arrayOfArrays); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"],
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"], arrayOfDictionaries); arrayOfArrays);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"],
arrayOfDictionaries);
} }
- (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons { - (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 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); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]);
} }
- (void)testMLNShapeSourceWithMultiPolylineFeatures { - (void)testMLNShapeSourceWithMultiPolylineFeatures {
CLLocationCoordinate2D firstCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; CLLocationCoordinate2D firstCoordinates[] = {CLLocationCoordinate2DMake(0, 0),
MLNPolylineFeature *firstPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2]; CLLocationCoordinate2DMake(10, 10)};
CLLocationCoordinate2D secondCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; MLNPolylineFeature *firstPolylineFeature =
MLNPolylineFeature *secondPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2]; [MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2];
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[firstPolylineFeature, secondPolylineFeature]]; 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); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]);
@@ -199,27 +238,30 @@
- (void)testMLNShapeSourceWithMultiPolygonFeatures { - (void)testMLNShapeSourceWithMultiPolygonFeatures {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 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 *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; 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); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]);
@@ -229,7 +271,9 @@
MLNPointFeature *pointFeature = [MLNPointFeature new]; MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); 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); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]);
@@ -237,13 +281,14 @@
- (void)testMLNShapeSourceWithPointCollectionFeature { - (void)testMLNShapeSourceWithPointCollectionFeature {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; MLNPointCollectionFeature *pointCollectionFeature =
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointCollectionFeature options:nil]; [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
shape:pointCollectionFeature
options:nil];
XCTAssertNotNil(source.shape); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
@@ -251,38 +296,47 @@
- (void)testMLNShapeSourceWithShapeCollectionFeatures { - (void)testMLNShapeSourceWithShapeCollectionFeatures {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)}; CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = { CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.2, 100.8), CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 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)}; CLLocationCoordinate2D coordinates_2[] = {CLLocationCoordinate2DMake(0, 0),
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2 count:2]; 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]; MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2); 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; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertNotNil(shape); XCTAssertNotNil(shape);
@@ -291,40 +345,54 @@
- (void)testMLNShapeSourceWithFeaturesConvenienceInitializer { - (void)testMLNShapeSourceWithFeaturesConvenienceInitializer {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.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]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
features:@[ polygonFeature ]
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]); XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape"); XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
// when a shape is included in the features array // when a shape is included in the features array
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];
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion" #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 #pragma clang diagnostic pop
} }
- (void)testMLNShapeSourceWithShapesConvenienceInitializer { - (void)testMLNShapeSourceWithShapesConvenienceInitializer {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(0.0, 101.0), CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.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]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shapes:@[ polygon ]
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]); XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);

View File

@@ -1,13 +1,13 @@
#import <Mapbox.h> #import <Mapbox.h>
#import "NSBundle+MLNAdditions.h"
#import "MLNVectorTileSource_Private.h" #import "MLNVectorTileSource_Private.h"
#import "NSBundle+MLNAdditions.h"
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#else #else
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#endif #endif
#import <objc/runtime.h> #import <objc/runtime.h>
@@ -28,11 +28,13 @@
[MLNSettings useWellKnownTileServer:MLNMapTiler]; [MLNSettings useWellKnownTileServer:MLNMapTiler];
[MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"]; [MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; 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 = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
self.mapView.delegate = self; self.mapView.delegate = self;
if (!self.mapView.style) { if (!self.mapView.style) {
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; _styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil]; [self waitForExpectationsWithTimeout:10 handler:nil];
} }
} }
@@ -70,7 +72,9 @@
} else { } else {
XCTAssertEqual(self.style.sources.count, 0UL); XCTAssertEqual(self.style.sources.count, 0UL);
} }
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource]; [self.style addSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count + 1); XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]); XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
@@ -79,39 +83,62 @@
} }
- (void)testAddingSourcesTwice { - (void)testAddingSourcesTwice {
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource]; [self.style addSource:shapeSource];
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, MLNRedundantSourceException); XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException,
MLNRedundantSourceException);
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42]; MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource"
configurationURL:[NSURL URLWithString:@".json"]
tileSize:42];
[self.style addSource:rasterTileSource]; [self.style addSource:rasterTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, MLNRedundantSourceException); XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException,
MLNRedundantSourceException);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]]; MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource"
configurationURL:[NSURL URLWithString:@".json"]];
[self.style addSource:vectorTileSource]; [self.style addSource:vectorTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, MLNRedundantSourceException); XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException,
MLNRedundantSourceException);
} }
- (void)testAddingSourcesWithDuplicateIdentifiers { - (void)testAddingSourcesWithDuplicateIdentifiers {
MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc]
MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; 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]; [self.style addSource:source1];
XCTAssertThrowsSpecificNamed([self.style addSource: source2], NSException, MLNRedundantSourceIdentifierException); XCTAssertThrowsSpecificNamed([self.style addSource:source2], NSException,
MLNRedundantSourceIdentifierException);
} }
- (void)testRemovingSourcesBeforeAddingThem { - (void)testRemovingSourcesBeforeAddingThem {
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:rasterTileSource]; [self.style removeSource:rasterTileSource];
[self.style addSource:rasterTileSource]; [self.style addSource:rasterTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]); XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source"
shape:nil
options:nil];
[self.style removeSource:shapeSource]; [self.style removeSource:shapeSource];
[self.style addSource:shapeSource]; [self.style addSource:shapeSource];
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]); XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil]; MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:vectorTileSource]; [self.style removeSource:vectorTileSource];
[self.style addSource:vectorTileSource]; [self.style addSource:vectorTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]); XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
@@ -119,14 +146,21 @@
- (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier {
// Add a raster tile source // Add a raster tile source
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterTileSource]; [self.style addSource:rasterTileSource];
// Attempt to remove an image source with the same identifier as the raster tile source // 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"]]; MLNImageSource *imageSource =
[[MLNImageSource alloc] initWithIdentifier:@"some-identifier"
coordinateQuad:{}
URL:[NSURL URLWithString:@"http://host/image.png"]];
[self.style removeSource:imageSource]; [self.style removeSource:imageSource];
// The raster tile source should still be added // The raster tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MLNRasterTileSource class]]); XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier]
isMemberOfClass:[MLNRasterTileSource class]]);
// Remove the raster tile source // Remove the raster tile source
[self.style removeSource:rasterTileSource]; [self.style removeSource:rasterTileSource];
@@ -135,10 +169,14 @@
[self.style addSource:imageSource]; [self.style addSource:imageSource];
// Attempt to remove a vector tile source with the same identifer as the shape source // 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]; MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:vectorTileSource]; [self.style removeSource:vectorTileSource];
// The image source should still be added // The image source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNImageSource class]]); XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
isMemberOfClass:[MLNImageSource class]]);
// Remove the image source // Remove the image source
[self.style removeSource:imageSource]; [self.style removeSource:imageSource];
@@ -146,19 +184,25 @@
// Add the vector tile source // Add the vector tile source
[self.style addSource:vectorTileSource]; [self.style addSource:vectorTileSource];
// Attempt to remove the previously created raster tile source that has the same identifer as the shape source // Attempt to remove the previously created raster tile source that has the same identifer as the
// shape source
[self.style removeSource:rasterTileSource]; [self.style removeSource:rasterTileSource];
// The vector tile source should still be added // The vector tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]); XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
isMemberOfClass:[MLNVectorTileSource class]]);
} }
- (void)testRemovingSourceInUse { - (void)testRemovingSourceInUse {
// Add a raster tile source // Add a raster tile source
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style addSource:vectorTileSource]; [self.style addSource:vectorTileSource];
// Add a layer using it // Add a layer using it
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:vectorTileSource]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:vectorTileSource];
[self.style addLayer:fillLayer]; [self.style addLayer:fillLayer];
// Attempt to remove the raster tile source // Attempt to remove the raster tile source
@@ -170,7 +214,8 @@
XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove); XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove);
// Ensure it is still there // Ensure it is still there
XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]); XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier]
isMemberOfClass:[MLNVectorTileSource class]]);
} }
- (void)testLayers { - (void)testLayers {
@@ -180,9 +225,12 @@
} else { } else {
XCTAssertEqual(self.style.layers.count, 0UL); XCTAssertEqual(self.style.layers.count, 0UL);
} }
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource]; [self.style addSource:shapeSource];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:shapeSource]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:shapeSource];
[self.style addLayer:fillLayer]; [self.style addLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1); XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]); XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
@@ -191,88 +239,136 @@
} }
- (void)testAddingLayersTwice { - (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"]; MLNBackgroundStyleLayer *backgroundLayer =
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
[self.style addLayer:backgroundLayer]; [self.style addLayer:backgroundLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException,
MLNRedundantLayerException);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" source:source]; MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer"
source:source];
[self.style addLayer:circleLayer]; [self.style addLayer:circleLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException,
MLNRedundantLayerException);
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:source]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:source];
[self.style addLayer:fillLayer]; [self.style addLayer:fillLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException,
MLNRedundantLayerException);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer"
source:source];
[self.style addLayer:lineLayer]; [self.style addLayer:lineLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException,
MLNRedundantLayerException);
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" source:source]; MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer"
source:source];
[self.style addLayer:rasterLayer]; [self.style addLayer:rasterLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException,
MLNRedundantLayerException);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" source:source]; MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer"
source:source];
[self.style addLayer:symbolLayer]; [self.style addLayer:symbolLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, MLNRedundantLayerException); XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException,
MLNRedundantLayerException);
} }
- (void)testAddingLayersWithDuplicateIdentifiers { - (void)testAddingLayersWithDuplicateIdentifiers {
// Just some source // Just some source
MLNVectorTileSource *source = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]]; MLNVectorTileSource *source = [[MLNVectorTileSource alloc]
[self.style addSource: source]; initWithIdentifier:@"my-source"
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
[self.style addSource:source];
// Add initial layer // Add initial layer
MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]; MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source];
[self.style addLayer:initial]; [self.style addLayer:initial];
// Try to add the duplicate // Try to add the duplicate
XCTAssertThrowsSpecificNamed([self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]], NSException, @"MLNRedundantLayerIdentifierException"); XCTAssertThrowsSpecificNamed(
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] belowLayer:initial],NSException, @"MLNRedundantLayerIdentifierException"); [self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] aboveLayer:initial], NSException, @"MLNRedundantLayerIdentifierException"); source:source]],
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException"); NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], 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 { - (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 // 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]; [self.style addSource:source];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:source]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer"
source:source];
[self.style removeLayer:fillLayer]; [self.style removeLayer:fillLayer];
[self.style addLayer:fillLayer]; [self.style addLayer:fillLayer];
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source]; MLNSymbolStyleLayer *symbolLayer =
[[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
[self.style removeLayer:symbolLayer]; [self.style removeLayer:symbolLayer];
[self.style addLayer:symbolLayer]; [self.style addLayer:symbolLayer];
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source]; MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer"
source:source];
[self.style removeLayer:lineLayer]; [self.style removeLayer:lineLayer];
[self.style addLayer:lineLayer]; [self.style addLayer:lineLayer];
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source]; MLNCircleStyleLayer *circleLayer =
[[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
[self.style removeLayer:circleLayer]; [self.style removeLayer:circleLayer];
[self.style addLayer:circleLayer]; [self.style addLayer:circleLayer];
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"]; MLNBackgroundStyleLayer *backgroundLayer =
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
[self.style removeLayer:backgroundLayer]; [self.style removeLayer:backgroundLayer];
[self.style addLayer:backgroundLayer]; [self.style addLayer:backgroundLayer];
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
} }
{ {
MLNRasterTileSource *rasterSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; MLNRasterTileSource *rasterSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterSource]; [self.style addSource:rasterSource];
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource]; MLNRasterStyleLayer *rasterLayer =
[[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
[self.style removeLayer:rasterLayer]; [self.style removeLayer:rasterLayer];
[self.style addLayer:rasterLayer]; [self.style addLayer:rasterLayer];
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]); XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
@@ -280,26 +376,33 @@
} }
- (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier { - (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" shape:nil options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier"
shape:nil
options:nil];
[self.style addSource:source]; [self.style addSource:source];
// Add a fill layer // Add a fill layer
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier" source:source]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier"
source:source];
[self.style addLayer:fillLayer]; [self.style addLayer:fillLayer];
// Attempt to remove a line layer with the same identifier as the fill layer // Attempt to remove a line layer with the same identifier as the fill layer
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier source:source]; MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier
source:source];
[self.style removeLayer:lineLayer]; [self.style removeLayer:lineLayer];
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] isMemberOfClass:[MLNFillStyleLayer class]]); XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier]
isMemberOfClass:[MLNFillStyleLayer class]]);
} }
- (NSString *)stringWithContentsOfStyleHeader { - (NSString *)stringWithContentsOfStyleHeader {
NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL
URLByAppendingPathComponent:@"Headers" isDirectory:YES] URLByAppendingPathComponent:@"Headers"
URLByAppendingPathComponent:@"MLNStyle.h"]; isDirectory:YES] URLByAppendingPathComponent:@"MLNStyle.h"];
NSError *styleHeaderError; NSError *styleHeaderError;
NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL usedEncoding:nil error:&styleHeaderError]; NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL
usedEncoding:nil
error:&styleHeaderError];
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h."); XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
return styleHeader; return styleHeader;
} }
@@ -324,24 +427,32 @@
} }
- (void)testLayersOrder { - (void)testLayersOrder {
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam"
ofType:@"geojson"];
NSURL *url = [NSURL fileURLWithPath:filePath]; NSURL *url = [NSURL fileURLWithPath:filePath];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" URL:url options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
URL:url
options:nil];
[self.style addSource:source]; [self.style addSource:source];
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" source:source]; MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1"
source:source];
[self.style addLayer:layer1]; [self.style addLayer:layer1];
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" source:source]; MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3"
source:source];
[self.style addLayer:layer3]; [self.style addLayer:layer3];
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" source:source]; MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2"
source:source];
[self.style insertLayer:layer2 aboveLayer:layer1]; [self.style insertLayer:layer2 aboveLayer:layer1];
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" source:source]; MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4"
source:source];
[self.style insertLayer:layer4 aboveLayer:layer3]; [self.style insertLayer:layer4 aboveLayer:layer3];
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" source:source]; MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0"
source:source];
[self.style insertLayer:layer0 belowLayer:layer1]; [self.style insertLayer:layer0 belowLayer:layer1];
NSArray<MLNStyleLayer *> *layers = [self.style layers]; NSArray<MLNStyleLayer *> *layers = [self.style layers];
@@ -361,51 +472,58 @@
- (void)testLanguageMatching { - (void)testLanguageMatching {
{ {
NSArray *preferences = @[@"en"]; NSArray *preferences = @[ @"en" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
} }
{ {
NSArray *preferences = @[@"en-US"]; NSArray *preferences = @[ @"en-US" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
} }
{ {
NSArray *preferences = @[@"fr"]; NSArray *preferences = @[ @"fr" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
} }
{ {
NSArray *preferences = @[@"zh-Hans"]; NSArray *preferences = @[ @"zh-Hans" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
} }
{ {
NSArray *preferences = @[@"zh-Hans", @"en"]; NSArray *preferences = @[ @"zh-Hans", @"en" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
} }
{ {
NSArray *preferences = @[@"zh-Hant"]; NSArray *preferences = @[ @"zh-Hant" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
} }
{ {
NSArray *preferences = @[@"en", @"fr", @"el"]; NSArray *preferences = @[ @"en", @"fr", @"el" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
} }
{ {
NSArray *preferences = @[@"tlh"]; NSArray *preferences = @[ @"tlh" ];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
} }
{ {
NSArray *preferences = @[@"tlh", @"en"]; NSArray *preferences = @[ @"tlh", @"en" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
} }
{ {
NSArray *preferences = @[@"mul"]; NSArray *preferences = @[ @"mul" ];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
} }
} }
// MARK: Transition tests // MARK: Transition tests
- (void)testTransition - (void)testTransition {
{
MLNTransition transitionTest = MLNTransitionMake(5, 4); MLNTransition transitionTest = MLNTransitionMake(5, 4);
self.style.transition = transitionTest; self.style.transition = transitionTest;
@@ -414,12 +532,13 @@
XCTAssert(self.style.transition.duration == transitionTest.duration); XCTAssert(self.style.transition.duration == transitionTest.duration);
} }
- (void)testPerformsPlacementTransitions - (void)testPerformsPlacementTransitions {
{ XCTAssertTrue(self.style.performsPlacementTransitions,
XCTAssertTrue(self.style.performsPlacementTransitions, @"The default value for enabling placement transitions should be YES."); @"The default value for enabling placement transitions should be YES.");
self.style.performsPlacementTransitions = NO; self.style.performsPlacementTransitions = NO;
XCTAssertFalse(self.style.performsPlacementTransitions, @"Enabling placement transitions should be NO."); XCTAssertFalse(self.style.performsPlacementTransitions,
@"Enabling placement transitions should be NO.");
} }
- (void)testStyleJSON { - (void)testStyleJSON {
@@ -429,12 +548,14 @@
// Verify the JSON is valid // Verify the JSON is valid
NSError *error = nil; NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding] id jsonObject =
[NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
options:0 options:0
error:&error]; error:&error];
XCTAssertNil(error, @"Style JSON should be valid JSON"); XCTAssertNil(error, @"Style JSON should be valid JSON");
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object"); XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], @"Style JSON should represent a dictionary"); XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]],
@"Style JSON should represent a dictionary");
} }
@end @end

View File

@@ -1,9 +1,9 @@
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
#import <Mapbox.h> #import <Mapbox.h>
#import "MLNVectorTileSource.h"
#import "MLNTileSource_Private.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNTileSource_Private.h"
#import "MLNVectorTileSource.h"
#include <mbgl/util/tileset.hpp> #include <mbgl/util/tileset.hpp>
@@ -14,10 +14,10 @@
@implementation MLNTileSetTests @implementation MLNTileSetTests
namespace { namespace {
// a tile set that provides an mbgl tile set // a tile set that provides an mbgl tile set
NSArray *tileURLTemplates = @[@"tile.1", @"tile.2", @"tile.3"]; NSArray *tileURLTemplates = @[ @"tile.1", @"tile.2", @"tile.3" ];
} } // namespace
- (void)testTileSetFromTileURLTemplates { - (void)testTileSetFromTileURLTemplates {
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil); mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil);
@@ -37,9 +37,9 @@ namespace {
XCTAssertEqual(tileSet.zoomRange.max, 22); XCTAssertEqual(tileSet.zoomRange.max, 22);
// when the tile set has min and/or max zoom level set // when the tile set has min and/or max zoom level set
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionMinimumZoomLevel: @1, MLNTileSourceOptionMinimumZoomLevel : @1,
MLNTileSourceOptionMaximumZoomLevel: @2, MLNTileSourceOptionMaximumZoomLevel : @2,
}); });
// the mbgl object reflects the set values for min and max zoom level // the mbgl object reflects the set values for min and max zoom level
@@ -47,9 +47,10 @@ namespace {
XCTAssertEqual(tileSet.zoomRange.max, 2); XCTAssertEqual(tileSet.zoomRange.max, 2);
// when the tile set has a bounds set // when the tile set has a bounds set
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78)); MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34),
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ CLLocationCoordinate2DMake(56, 78));
MLNTileSourceOptionCoordinateBounds: @(bounds), tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionCoordinateBounds : @(bounds),
}); });
// the mbgl object reflects the set values for the bounds // the mbgl object reflects the set values for the bounds
@@ -62,14 +63,15 @@ namespace {
// when the tile set has an attribution // when the tile set has an attribution
NSString *attribution = @"my tileset © ©️🎈"; NSString *attribution = @"my tileset © ©️🎈";
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionHTMLString: attribution, MLNTileSourceOptionAttributionHTMLString : attribution,
}); });
// the attribution is reflected by the mbgl tileset // the attribution is reflected by the mbgl tileset
XCTAssertEqual(tileSet.attribution, attribution.UTF8String); XCTAssertEqual(tileSet.attribution, attribution.UTF8String);
// when the tile set has attribution infos // when the tile set has attribution infos
MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc] initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"] MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc]
initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]]; URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
UIColor *redColor = [UIColor redColor]; UIColor *redColor = [UIColor redColor];
@@ -80,12 +82,14 @@ namespace {
// <rdar://problem/46115233> <http://www.openradar.me/46115233> // <rdar://problem/46115233> <http://www.openradar.me/46115233>
NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1]; NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
#endif #endif
NSAttributedString *gl = [[NSAttributedString alloc] initWithString:@"GL" attributes:@{ NSAttributedString *gl =
NSBackgroundColorAttributeName: redColor, [[NSAttributedString alloc] initWithString:@"GL"
attributes:@{
NSBackgroundColorAttributeName : redColor,
}]; }];
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil]; MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionInfos: @[mapboxInfo, glInfo], MLNTileSourceOptionAttributionInfos : @[ mapboxInfo, glInfo ],
}); });
// the attribution is reflected by the mbgl tileset // the attribution is reflected by the mbgl tileset
@@ -94,24 +98,29 @@ namespace {
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
// TODO: investigate visual impact // TODO: investigate visual impact
// iOS 13 evidently changes font size from points to pixels // iOS 13 evidently changes font size from points to pixels
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px\">" html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
@"font-size: 12.00px\">"
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" @"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px; background-color: #ff0000\">GL</font>\n"); @"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
@"font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
} else { } else {
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt\">" html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
@"font-size: 12.00pt\">"
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" @"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt; background-color: #ff0000\">GL</font>\n"); @"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
@"font-size: 12.00pt; background-color: #ff0000\">GL</font>\n");
} }
#else #else
NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">" NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">"
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" @"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; background-color: #ff0000\">GL</font>\n"); @"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; "
@"background-color: #ff0000\">GL</font>\n");
#endif #endif
XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html); XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html);
// when the tile coordinate system is changed using an NSNumber // when the tile coordinate system is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem: @(MLNTileCoordinateSystemTMS), MLNTileSourceOptionTileCoordinateSystem : @(MLNTileCoordinateSystemTMS),
}); });
// the scheme is reflected by the mbgl tileset // the scheme is reflected by the mbgl tileset
@@ -120,7 +129,8 @@ namespace {
// when the tile coordinate system is changed using an NSValue // when the tile coordinate system is changed using an NSValue
MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS; MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem: [NSValue value:&tms withObjCType:@encode(MLNTileCoordinateSystem)], MLNTileSourceOptionTileCoordinateSystem : [NSValue value:&tms
withObjCType:@encode(MLNTileCoordinateSystem)],
}); });
// the scheme is reflected by the mbgl tileset // the scheme is reflected by the mbgl tileset
@@ -132,7 +142,7 @@ namespace {
// when the DEM encoding is changed using an NSNumber // when the DEM encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding: @(MLNDEMEncodingTerrarium), MLNTileSourceOptionDEMEncoding : @(MLNDEMEncodingTerrarium),
}); });
// the encoding is reflected by the mbgl tileset // the encoding is reflected by the mbgl tileset
@@ -142,7 +152,8 @@ namespace {
// when the raster encoding is changed using an NSValue // when the raster encoding is changed using an NSValue
MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium; MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MLNDEMEncoding)], MLNTileSourceOptionDEMEncoding : [NSValue value:&terrarium
withObjCType:@encode(MLNDEMEncoding)],
}); });
// the encoding is reflected by the mbgl tileset // the encoding is reflected by the mbgl tileset
@@ -155,7 +166,7 @@ namespace {
// when the raster encoding is changed using an NSNumber // when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMapbox), MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMapbox),
}); });
// the encoding is reflected by the mbgl tileset // the encoding is reflected by the mbgl tileset
@@ -164,7 +175,7 @@ namespace {
// when the raster encoding is changed using an NSNumber // when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMLT), MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMLT),
}); });
// the encoding is reflected by the mbgl tileset // the encoding is reflected by the mbgl tileset
@@ -174,7 +185,8 @@ namespace {
// when the raster encoding is changed using an NSValue // when the raster encoding is changed using an NSValue
MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT; MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: [NSValue value:&mlt withObjCType:@encode(MLNVectorTileSourceEncoding)], MLNVectorTileSourceOptionEncoding : [NSValue value:&mlt
withObjCType:@encode(MLNVectorTileSourceEncoding)],
}); });
// the encoding is reflected by the mbgl tileset // the encoding is reflected by the mbgl tileset
@@ -183,11 +195,13 @@ namespace {
} }
- (void)testInvalidTileSet { - (void)testInvalidTileSet {
// a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels throws an exception // a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels
XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{ // throws an exception
MLNTileSourceOptionMinimumZoomLevel: @10, XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionMaximumZoomLevel: @9, MLNTileSourceOptionMinimumZoomLevel : @10,
}), NSException, NSInvalidArgumentException); MLNTileSourceOptionMaximumZoomLevel : @9,
}),
NSException, NSInvalidArgumentException);
} }
@end @end

View File

@@ -2,10 +2,9 @@
// Called early in GLFW initialization so that the local glyph rasterizer // Called early in GLFW initialization so that the local glyph rasterizer
// can find a suitable CJK font via NSUserDefaults. // can find a suitable CJK font via NSUserDefaults.
__attribute__((constructor)) __attribute__((constructor)) static void setDefaultIdeographicFont() {
static void setDefaultIdeographicFont() {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) { if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) {
[defaults setObject:@[@"PingFang TC"] forKey:@"MLNIdeographicFontFamilyName"]; [defaults setObject:@[ @"PingFang TC" ] forKey:@"MLNIdeographicFontFamilyName"];
} }
} }

View File

@@ -10,14 +10,14 @@ struct GLFWwindow;
@class NSWindow; @class NSWindow;
class GLFWMetalBackend final : public GLFWBackend { class GLFWMetalBackend final : public GLFWBackend {
public: public:
GLFWMetalBackend(GLFWwindow *window_, const bool capFrameRate); GLFWMetalBackend(GLFWwindow *window_, const bool capFrameRate);
~GLFWMetalBackend() = default; ~GLFWMetalBackend() = default;
mbgl::gfx::RendererBackend &getRendererBackend() override; mbgl::gfx::RendererBackend &getRendererBackend() override;
void setSize(mbgl::Size) override; void setSize(mbgl::Size) override;
mbgl::Size getSize() const override; mbgl::Size getSize() const override;
private: private:
NSWindow *window; NSWindow *window;
MetalBackend rendererBackend; MetalBackend rendererBackend;
}; };

View File

@@ -7,20 +7,10 @@
#include <GLFW/glfw3native.h> #include <GLFW/glfw3native.h>
GLFWMetalBackend::GLFWMetalBackend(GLFWwindow* window_, [[maybe_unused]] const bool capFrameRate) GLFWMetalBackend::GLFWMetalBackend(GLFWwindow* window_, [[maybe_unused]] const bool capFrameRate)
: : window(glfwGetCocoaWindow(window_)), rendererBackend(window) {}
window(glfwGetCocoaWindow(window_)),
rendererBackend(window)
{
}
mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() { mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() { return rendererBackend; }
return rendererBackend;
}
void GLFWMetalBackend::setSize(mbgl::Size size) { void GLFWMetalBackend::setSize(mbgl::Size size) { rendererBackend.setSize(size); }
rendererBackend.setSize(size);
}
mbgl::Size GLFWMetalBackend::getSize() const { mbgl::Size GLFWMetalBackend::getSize() const { return rendererBackend.getSize(); }
return rendererBackend.getSize();
}

View File

@@ -10,30 +10,27 @@ namespace mbgl {
using namespace mtl; using namespace mtl;
class MetalRenderableResource final: public mtl::RenderableResource { class MetalRenderableResource final : public mtl::RenderableResource {
public: public:
MetalRenderableResource(MetalBackend &backend): MetalRenderableResource(MetalBackend& backend)
rendererBackend(backend), : rendererBackend(backend),
commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())), commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())),
swapchain(NS::TransferPtr(CA::MetalLayer::layer())) swapchain(NS::TransferPtr(CA::MetalLayer::layer())) {
{
swapchain->setDevice(backend.getDevice().get()); swapchain->setDevice(backend.getDevice().get());
} }
void setBackendSize(mbgl::Size size_) { void setBackendSize(mbgl::Size size_) {
size = size_; size = size_;
swapchain->setDrawableSize({static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)}); swapchain->setDrawableSize(
{static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)});
buffersInvalid = true; buffersInvalid = true;
} }
mbgl::Size getSize() const { mbgl::Size getSize() const { return size; }
return size;
}
void bind() override { void bind() override {
surface = NS::TransferPtr(swapchain->nextDrawable()); surface = NS::TransferPtr(swapchain->nextDrawable());
auto texSize = mbgl::Size{ auto texSize = mbgl::Size{static_cast<uint32_t>(swapchain->drawableSize().width),
static_cast<uint32_t>(swapchain->drawableSize().width),
static_cast<uint32_t>(swapchain->drawableSize().height)}; static_cast<uint32_t>(swapchain->drawableSize().height)};
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer()); commandBuffer = NS::TransferPtr(commandQueue->commandBuffer());
@@ -45,30 +42,37 @@ public:
depthTexture = rendererBackend.getContext().createTexture2D(); depthTexture = rendererBackend.getContext().createTexture2D();
depthTexture->setSize(texSize); depthTexture->setSize(texSize);
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float); depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
depthTexture->setSamplerConfiguration( depthTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); gfx::TextureWrapType::Clamp,
static_cast<mtl::Texture2D*>(depthTexture.get())->setUsage( gfx::TextureWrapType::Clamp});
MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); static_cast<mtl::Texture2D*>(depthTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
MTL::TextureUsageRenderTarget);
stencilTexture = rendererBackend.getContext().createTexture2D(); stencilTexture = rendererBackend.getContext().createTexture2D();
stencilTexture->setSize(texSize); stencilTexture->setSize(texSize);
stencilTexture->setFormat(gfx::TexturePixelType::Stencil, gfx::TextureChannelDataType::UnsignedByte); stencilTexture->setFormat(gfx::TexturePixelType::Stencil,
stencilTexture->setSamplerConfiguration( gfx::TextureChannelDataType::UnsignedByte);
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp}); stencilTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
gfx::TextureWrapType::Clamp,
gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(stencilTexture.get()) static_cast<mtl::Texture2D*>(stencilTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget); ->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
MTL::TextureUsageRenderTarget);
} }
if (depthTexture) { if (depthTexture) {
depthTexture->create(); depthTexture->create();
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) { if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
depthTarget->setTexture(static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture()); depthTarget->setTexture(
static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
} }
} }
if (stencilTexture) { if (stencilTexture) {
stencilTexture->create(); stencilTexture->create();
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) { if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture()); stencilTarget->setTexture(
static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
} }
} }
} }
@@ -80,13 +84,9 @@ public:
renderPassDescriptor.reset(); renderPassDescriptor.reset();
} }
const mtl::RendererBackend& getBackend() const override { const mtl::RendererBackend& getBackend() const override { return rendererBackend; }
return rendererBackend;
}
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; }
return commandBuffer;
}
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override { mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init()); return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
@@ -96,12 +96,10 @@ public:
return renderPassDescriptor; return renderPassDescriptor;
} }
const CAMetalLayerPtr& getSwapchain() const { const CAMetalLayerPtr& getSwapchain() const { return swapchain; }
return swapchain;
}
private: private:
MetalBackend &rendererBackend; MetalBackend& rendererBackend;
MTLCommandQueuePtr commandQueue; MTLCommandQueuePtr commandQueue;
MTLCommandBufferPtr commandBuffer; MTLCommandBufferPtr commandBuffer;
MTLRenderPassDescriptorPtr renderPassDescriptor; MTLRenderPassDescriptorPtr renderPassDescriptor;
@@ -115,17 +113,18 @@ private:
} // namespace mbgl } // namespace mbgl
MetalBackend::MetalBackend(NSWindow *window): MetalBackend::MetalBackend(NSWindow* window)
mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique), : mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique),
mbgl::gfx::Renderable(mbgl::Size{0, 0}, std::make_unique<mbgl::MetalRenderableResource>(*this)) mbgl::gfx::Renderable(mbgl::Size{0, 0},
{ std::make_unique<mbgl::MetalRenderableResource>(*this)) {
window.contentView.layer = (__bridge CALayer *)getDefaultRenderable().getResource<mbgl::MetalRenderableResource>().getSwapchain().get(); window.contentView.layer = (__bridge CALayer*)getDefaultRenderable()
.getResource<mbgl::MetalRenderableResource>()
.getSwapchain()
.get();
window.contentView.wantsLayer = YES; window.contentView.wantsLayer = YES;
} }
mbgl::gfx::Renderable &MetalBackend::getDefaultRenderable() { mbgl::gfx::Renderable& MetalBackend::getDefaultRenderable() { return *this; }
return *this;
}
void MetalBackend::activate() {} void MetalBackend::activate() {}
void MetalBackend::deactivate() {} void MetalBackend::deactivate() {}
@@ -135,4 +134,6 @@ void MetalBackend::setSize(mbgl::Size size_) {
getResource<mbgl::MetalRenderableResource>().setBackendSize(size_); getResource<mbgl::MetalRenderableResource>().setBackendSize(size_);
} }
mbgl::Size MetalBackend::getSize() const { return getResource<mbgl::MetalRenderableResource>().getSize(); } mbgl::Size MetalBackend::getSize() const {
return getResource<mbgl::MetalRenderableResource>().getSize();
}

View File

@@ -1,15 +1,15 @@
#import "MLNMapViewIntegrationTest.h"
#import "MLNTestUtility.h"
#import "MLNMapAccessibilityElement.h"
#import "MLNTestLocationManager.h"
#import "MLNCompactCalloutView.h" #import "MLNCompactCalloutView.h"
#import "MLNMapAccessibilityElement.h"
#import "MLNMapViewIntegrationTest.h"
#import "MLNTestLocationManager.h"
#import "MLNTestUtility.h"
#import "MLNGeometry_Private.h" #import "MLNGeometry_Private.h"
#import "MLNMapView_Private.h" #import "MLNMapView_Private.h"
#include <mbgl/util/geo.hpp>
#include <mbgl/map/camera.hpp> #include <mbgl/map/camera.hpp>
#include <mbgl/map/map.hpp> #include <mbgl/map/map.hpp>
#include <mbgl/util/geo.hpp>
@interface MLNTestCalloutView : MLNCompactCalloutView @interface MLNTestCalloutView : MLNCompactCalloutView
@property (nonatomic) BOOL implementsMarginHints; @property (nonatomic) BOOL implementsMarginHints;
@@ -25,11 +25,11 @@
} }
@end @end
@interface MLNMapView (Tests) @interface MLNMapView (Tests)
- (MLNAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist; - (MLNAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist;
- (id <MLNAnnotation>)annotationWithTag:(MLNAnnotationTag)tag; - (id<MLNAnnotation>)annotationWithTag:(MLNAnnotationTag)tag;
- (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint; - (MLNMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees
aroundAnchorPoint:(CGPoint)anchorPoint;
- (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint; - (MLNMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint;
- (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions; - (MLNMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions;
@property (nonatomic) UIView<MLNCalloutView> *calloutViewForSelectedAnnotation; @property (nonatomic) UIView<MLNCalloutView> *calloutViewForSelectedAnnotation;
@@ -52,10 +52,11 @@ typedef struct PanTestData {
} PanTestData; } PanTestData;
#define PAN_TEST_TERMINATOR {{FLT_MAX, FLT_MAX}, NO, NO, NO, NO, NO} #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; CGPoint relativeCoordinate = test.relativeCoord;
BOOL showsCallout = test.showsCallout; BOOL showsCallout = test.showsCallout;
BOOL calloutImplementsMarginHints = test.implementsMargins; BOOL calloutImplementsMarginHints = test.implementsMargins;
@@ -70,19 +71,21 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
XCTAssert(self.mapView.annotations.count == 0); XCTAssert(self.mapView.annotations.count == 0);
NSString * const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer"; NSString *const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer";
CGSize size = self.mapView.bounds.size; CGSize size = self.mapView.bounds.size;
CGSize annotationSize = CGSizeMake(floor(size.width*kAnnotationRelativeScale.x), floor(size.height*kAnnotationRelativeScale.y)); CGSize annotationSize = CGSizeMake(floor(size.width * kAnnotationRelativeScale.x),
floor(size.height * kAnnotationRelativeScale.y));
self.viewForAnnotation = ^MLNAnnotationView*(MLNMapView *view, id<MLNAnnotation> annotation) {
self.viewForAnnotation = ^MLNAnnotationView *(MLNMapView *view, id<MLNAnnotation> annotation) {
if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { if (![annotation isKindOfClass:[MLNPointAnnotation class]]) {
return nil; return nil;
} }
// No dequeue // No dequeue
MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MLNTestAnnotationReuseIdentifer]; MLNAnnotationView *annotationView =
annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; [[MLNAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:MLNTestAnnotationReuseIdentifer];
annotationView.bounds = (CGRect){.origin = CGPointZero, .size = annotationSize};
annotationView.backgroundColor = UIColor.redColor; annotationView.backgroundColor = UIColor.redColor;
annotationView.enabled = YES; annotationView.enabled = YES;
@@ -90,20 +93,23 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
}; };
// Coordinate for annotation screen coordinate // Coordinate for annotation screen coordinate
CGPoint annotationPoint = CGPointMake(floor(relativeCoordinate.x * size.width), floor(relativeCoordinate.y * size.height) ); CGPoint annotationPoint = CGPointMake(floor(relativeCoordinate.x * size.width),
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:annotationPoint toCoordinateFromView:self.mapView]; floor(relativeCoordinate.y * size.height));
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:annotationPoint
toCoordinateFromView:self.mapView];
MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init]; MLNPointAnnotation *point = [[MLNPointAnnotation alloc] init];
point.title = title; point.title = title;
point.coordinate = coordinate; point.coordinate = coordinate;
self.mapViewAnnotationCanShowCalloutForAnnotation = ^BOOL(MLNMapView *mapView, id<MLNAnnotation> annotation) { self.mapViewAnnotationCanShowCalloutForAnnotation =
^BOOL(MLNMapView *mapView, id<MLNAnnotation> annotation) {
return showsCallout; return showsCallout;
}; };
self.mapViewCalloutViewForAnnotation = ^id<MLNCalloutView>(MLNMapView *mapView, id<MLNAnnotation> annotation) { self.mapViewCalloutViewForAnnotation =
if (!showsCallout) ^id<MLNCalloutView>(MLNMapView *mapView, id<MLNAnnotation> annotation) {
return nil; if (!showsCallout) return nil;
MLNTestCalloutView *calloutView = [[MLNTestCalloutView alloc] init]; MLNTestCalloutView *calloutView = [[MLNTestCalloutView alloc] init];
calloutView.representedObject = annotation; calloutView.representedObject = annotation;
@@ -125,7 +131,10 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// will have run, and `viewForAnnotation` may return nil // will have run, and `viewForAnnotation` may return nil
XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"];
[self.mapView selectAnnotation:point moveIntoView:moveIntoView animateSelection:animateSelection completionHandler:^{ [self.mapView selectAnnotation:point
moveIntoView:moveIntoView
animateSelection:animateSelection
completionHandler:^{
[selectionCompleted fulfill]; [selectionCompleted fulfill];
}]; }];
@@ -137,9 +146,9 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// Something is amiss with dispatch_async & XCTest on simulators in a recent // Something is amiss with dispatch_async & XCTest on simulators in a recent
// iOS SDK release. // iOS SDK release.
// TODO: Add issue // TODO: Add issue
[self waitForExpectations:@[selectionCompleted] timeout:animateSelection ? 0.4: 0.1]; [self waitForExpectations:@[ selectionCompleted ] timeout:animateSelection ? 0.4 : 0.1];
#else #else
[self waitForExpectations:@[selectionCompleted] timeout:animateSelection ? 0.4: 0.05]; [self waitForExpectations:@[ selectionCompleted ] timeout:animateSelection ? 0.4 : 0.05];
#endif #endif
UIView *annotationViewAfterSelection = [self.mapView viewForAnnotation:point]; UIView *annotationViewAfterSelection = [self.mapView viewForAnnotation:point];
@@ -148,17 +157,21 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// If the annotation is still "offscreen" at this point, then the above annotation view // If the annotation is still "offscreen" at this point, then the above annotation view
// may be nil, which is expected. // may be nil, which is expected.
BOOL (^CGRectContainsRectWithAccuracy)(CGRect, CGRect, CGFloat) = ^(CGRect rect1, CGRect rect2, CGFloat accuracy) { BOOL (^CGRectContainsRectWithAccuracy)(CGRect, CGRect, CGFloat) =
^(CGRect rect1, CGRect rect2, CGFloat accuracy) {
CGRect expandedRect1 = CGRectInset(rect1, -accuracy, -accuracy); CGRect expandedRect1 = CGRectInset(rect1, -accuracy, -accuracy);
return (BOOL)CGRectContainsRect(expandedRect1, rect2); return (BOOL)CGRectContainsRect(expandedRect1, rect2);
}; };
CGFloat epsilon = 0.00001; CGFloat epsilon = 0.00001;
if (expectMapToHavePanned) { if (expectMapToHavePanned) {
CLLocationDegrees latitudeDelta = fabs(mapCenterAfterSelection.latitude - mapCenterBeforeSelection.latitude); CLLocationDegrees latitudeDelta =
CLLocationDegrees longitudeDelta = fabs(mapCenterAfterSelection.longitude - mapCenterBeforeSelection.longitude); 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, // 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 // and so should have an annotation view and that it is fully on screen
@@ -166,30 +179,33 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
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 // Check the callout
if (showsCallout) { if (showsCallout) {
UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation; UIView *calloutView = self.mapView.calloutViewForSelectedAnnotation;
XCTAssertNotNil(calloutView); XCTAssertNotNil(calloutView);
// This can fail if the callout view's width is < the annotations. This is really a warning, so // This can fail if the callout view's width is < the annotations. This is really a warning,
// if you need this NOT to fail the tests, consider replacing with MLNTestWarning // so if you need this NOT to fail the tests, consider replacing with MLNTestWarning
XCTAssert(expectCalloutToBeFullyOnscreen == CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25), XCTAssert(expectCalloutToBeFullyOnscreen ==
CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25),
@"Expect contains:%d, Mapview:%@ annotation:%@ callout:%@", @"Expect contains:%d, Mapview:%@ annotation:%@ callout:%@",
expectCalloutToBeFullyOnscreen, expectCalloutToBeFullyOnscreen, NSStringFromCGRect(self.mapView.bounds),
NSStringFromCGRect(self.mapView.bounds),
NSStringFromCGRect(annotationFrameAfterSelection), NSStringFromCGRect(annotationFrameAfterSelection),
NSStringFromCGRect(calloutView.frame)); NSStringFromCGRect(calloutView.frame));
} }
} } else {
else {
// The map shouldn't have moved, so use equality (rather than an error check) // The map shouldn't have moved, so use equality (rather than an error check)
XCTAssertEqual(mapCenterBeforeSelection.latitude, mapCenterAfterSelection.latitude); XCTAssertEqual(mapCenterBeforeSelection.latitude, mapCenterAfterSelection.latitude);
XCTAssertEqual(mapCenterBeforeSelection.longitude, mapCenterAfterSelection.longitude); XCTAssertEqual(mapCenterBeforeSelection.longitude, mapCenterAfterSelection.longitude);
// Annotation shouldn't have moved // Annotation shouldn't have moved
CGPoint annotationPoint2 = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; CGPoint annotationPoint2 = [self.mapView convertCoordinate:point.coordinate
toPointToView:self.mapView];
CGFloat xDelta = fabs(annotationPoint2.x - annotationPoint.x); CGFloat xDelta = fabs(annotationPoint2.x - annotationPoint.x);
CGFloat yDelta = fabs(annotationPoint2.y - annotationPoint.y); CGFloat yDelta = fabs(annotationPoint2.y - annotationPoint.y);
@@ -204,13 +220,12 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// If kAnnotationScale == 0.25, then the following assert can fail. // 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 ) // 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 // If you need this NOT to fail the tests, consider replacing with MLNTestWarning
XCTAssert((expectCalloutToBeFullyOnscreen == CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25)), XCTAssert((expectCalloutToBeFullyOnscreen ==
@"Mapview:%@ annotation:%@ callout:%@", CGRectContainsRectWithAccuracy(self.mapView.bounds, calloutView.frame, 0.25)),
NSStringFromCGRect(self.mapView.bounds), @"Mapview:%@ annotation:%@ callout:%@", NSStringFromCGRect(self.mapView.bounds),
NSStringFromCGRect(annotationViewAfterSelection.frame), NSStringFromCGRect(annotationViewAfterSelection.frame),
NSStringFromCGRect(calloutView.frame)); NSStringFromCGRect(calloutView.frame));
} } else {
else {
// If there's no annotation view, should we expect a callout? // If there's no annotation view, should we expect a callout?
XCTAssertNil(calloutView); XCTAssertNil(calloutView);
XCTAssertFalse(expectCalloutToBeFullyOnscreen); XCTAssertFalse(expectCalloutToBeFullyOnscreen);
@@ -228,10 +243,9 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// What follows are tests based on this table. // What follows are tests based on this table.
// This is not a full-set of possible combinations, just the most important/likely // This is not a full-set of possible combinations, just the most important/likely
// ones // ones
- (void)internalRunTests:(PanTestData*)testData - (void)internalRunTests:(PanTestData *)testData {
{
// Test both animated and not-animated. // Test both animated and not-animated.
for (int i = 0; i<2; i++) { for (int i = 0; i < 2; i++) {
int row = 0; int row = 0;
PanTestData *test = testData; PanTestData *test = testData;
while (test->relativeCoord.x != FLT_MAX) { while (test->relativeCoord.x != FLT_MAX) {
@@ -253,24 +267,24 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// WITHOUT a callout // WITHOUT a callout
PanTestData tests[] = { PanTestData tests[] = {
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
// calloutOnScreen
// Offscreen // Offscreen
{ {-1.0f, 0.5f}, NO, NO, NO, NO, NO }, {{-1.0f, 0.5f}, NO, NO, NO, NO, NO},
{ { 2.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, -1.0f}, NO, NO, NO, NO, NO},
{ { 0.5f, 2.0f}, NO, NO, NO, NO, NO }, {{0.5f, 2.0f}, NO, NO, NO, NO, NO},
// Partial // Partial
{ { 0.0f, 0.5f}, NO, NO, NO, NO, NO }, {{0.0f, 0.5f}, NO, NO, NO, NO, NO},
{ { 1.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, 0.0f}, NO, NO, NO, NO, NO},
{ { 0.5f, 1.0f}, NO, NO, NO, NO, NO }, {{0.5f, 1.0f}, NO, NO, NO, NO, NO},
// Onscreen // Onscreen
{ { 0.5f, 0.5f}, NO, NO, NO, NO, NO }, {{0.5f, 0.5f}, NO, NO, NO, NO, NO},
PAN_TEST_TERMINATOR PAN_TEST_TERMINATOR};
};
[self internalRunTests:tests]; [self internalRunTests:tests];
} }
@@ -280,14 +294,15 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// WITH the default callout (implements marginshint) // WITH the default callout (implements marginshint)
PanTestData tests[] = { PanTestData tests[] = {
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
{ {-1.0f, 0.5f}, YES, YES, NO, NO, NO }, // calloutOnScreen
{ { 0.0f, 0.5f}, YES, YES, NO, NO, NO }, {{-1.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.0f, 0.5f}, YES, YES, NO, NO, NO},
{ { 0.5f, 0.5f}, YES, YES, NO, NO, YES }, {{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];
} }
@@ -298,32 +313,33 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 // 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? | // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when
// selected with moveIntoView=YES? |
// |---------------------|--------------|---------------------------------------|-----------------------------------------------| // |---------------------|--------------|---------------------------------------|-----------------------------------------------|
// | Offscreen | No | n/a | Yes (no margins) | // | Offscreen | No | n/a | Yes (no margins)
// | Partially | No | n/a | No | // | | Partially | No | n/a | No | |
// | Onscreen | No | n/a | No | // Onscreen | No | n/a | No |
// //
PanTestData tests[] = { PanTestData tests[] = {
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
// calloutOnScreen
// Offscreen // Offscreen
{ {-1.0f, 0.5f}, NO, NO, YES, YES, NO }, {{-1.0f, 0.5f}, NO, NO, YES, YES, NO},
{ { 2.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, -1.0f}, NO, NO, YES, YES, NO},
{ { 0.5f, 2.0f}, NO, NO, YES, YES, NO }, {{0.5f, 2.0f}, NO, NO, YES, YES, NO},
// Partial // Partial
{ { 0.0f, 0.5f}, NO, NO, YES, NO, NO }, {{0.0f, 0.5f}, NO, NO, YES, NO, NO},
{ { 1.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, 0.0f}, NO, NO, YES, NO, NO},
{ { 0.5f, 1.0f}, NO, NO, YES, NO, NO }, {{0.5f, 1.0f}, NO, NO, YES, NO, NO},
// Onscreen // Onscreen
{ { 0.5f, 0.5f}, NO, NO, YES, NO, NO }, {{0.5f, 0.5f}, NO, NO, YES, NO, NO},
PAN_TEST_TERMINATOR PAN_TEST_TERMINATOR};
};
[self internalRunTests:tests]; [self internalRunTests:tests];
} }
@@ -334,43 +350,46 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 // 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? | // | Annotation position | Has callout? | Callout implements `marginInsets...`? | Map pans when
// selected with moveIntoView=YES? |
// |---------------------|--------------|---------------------------------------|-----------------------------------------------| // |---------------------|--------------|---------------------------------------|-----------------------------------------------|
// | Offscreen | Yes | Yes | Yes to ensure callout is fully visible | // | Offscreen | Yes | Yes | Yes to ensure
// | Partially | Yes | Yes | Yes to ensure callout is fully visible | // callout is fully visible | | Partially | Yes | Yes | Yes to ensure
// | Onscreen | Yes | Yes | Yes, but *only* to ensure callout is fully visible | // 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[] = { PanTestData tests[] = {
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
// calloutOnScreen
// Offscreen // Offscreen
{ {-1.0f, 0.5f}, YES, YES, YES, YES, YES }, {{-1.0f, 0.5f}, YES, YES, YES, YES, YES},
{ { 2.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, -1.0f}, YES, YES, YES, YES, YES},
{ { 0.5f, 2.0f}, YES, YES, YES, YES, YES }, {{0.5f, 2.0f}, YES, YES, YES, YES, YES},
// Partial // Partial
{ { 0.0f, 0.5f}, YES, YES, YES, YES, YES }, {{0.0f, 0.5f}, YES, YES, YES, YES, YES},
{ { 1.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, 0.0f}, YES, YES, YES, YES, YES},
{ { 0.5f, 1.0f}, YES, YES, YES, YES, YES }, {{0.5f, 1.0f}, YES, YES, YES, YES, YES},
// Onscreen // Onscreen
{ { 0.5f, 0.5f}, YES, YES, YES, NO, YES }, {{0.5f, 0.5f}, YES, YES, YES, NO, YES},
// Just at the edge of the screen. // Just at the edge of the screen.
// Expects to move, because although onscreen, callout would not be. // Expects to move, because although onscreen, callout would not be.
// However, if the scale is 0.25, then expectToPan should be NO, because // However, if the scale is 0.25, then expectToPan should be NO, because
// of the width of the annotation // of the width of the annotation
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
{ {offset, 0.5f}, YES, YES, YES, YES, YES }, // calloutOnScreen
{ {(CGFloat)(1.0 - offset), 0.5f}, YES, YES, YES, YES, YES }, {{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];
} }
@@ -381,34 +400,37 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// From https://github.com/mapbox/mapbox-gl-native/pull/13727#issuecomment-454028698 // 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? | // | 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 | // | Offscreen | Yes | No | Yes, but only to
// | Partially | Yes | No | No | // show annotation (not callout) with no margins | | Partially | Yes | No | No
// | Onscreen | Yes | No | No | // | | Onscreen | Yes | No | No |
// //
PanTestData tests[] = { PanTestData tests[] = {
// Coord showsCallout impl margins? moveIntoView expectMapToPan calloutOnScreen // Coord showsCallout impl margins? moveIntoView expectMapToPan
// calloutOnScreen
// Offscreen // Offscreen
{ {-1.0f, 0.5f}, YES, NO, YES, YES, NO }, {{-1.0f, 0.5f}, YES, NO, YES, YES, NO},
{ { 2.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, -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 {{0.5f, 2.0f}, YES, NO, YES, YES, YES}, // Because annotation was off the bottom of screen,
{ { 2.0f, 2.0f}, YES, NO, YES, YES, NO }, // and callout is above annotation
{{2.0f, 2.0f}, YES, NO, YES, YES, NO},
// Partial // Partial
{ { 0.0f, 0.5f}, YES, NO, YES, NO, NO }, {{0.0f, 0.5f}, YES, NO, YES, NO, NO},
{ { 1.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, 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 {{0.5f, 1.0f}, YES, NO, YES, NO, YES}, // Because annotation was off the bottom of screen,
{ { 1.0f, 1.0f}, YES, NO, YES, NO, NO }, // and callout is above annotation
{{1.0f, 1.0f}, YES, NO, YES, NO, NO},
// Onscreen // Onscreen
{ { 0.5f, 0.5f}, YES, NO, YES, NO, YES }, {{0.5f, 0.5f}, YES, NO, YES, NO, YES},
PAN_TEST_TERMINATOR PAN_TEST_TERMINATOR};
};
[self internalRunTests:tests]; [self internalRunTests:tests];
} }
@@ -416,34 +438,35 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// MARK: - Selection with an offset // MARK: - Selection with an offset
- (void)testSelectingAnnotationWithCenterOffset { - (void)testSelectingAnnotationWithCenterOffset {
for (CGFloat dx = -100.0; dx <= 100.0; dx += 100.0) {
for (CGFloat dx = -100.0; dx <= 100.0; dx += 100.0 ) { for (CGFloat dy = -100.0; dy <= 100.0; dy += 100.0) {
for (CGFloat dy = -100.0; dy <= 100.0; dy += 100.0 ) {
CGVector offset = CGVectorMake(dx, dy); CGVector offset = CGVectorMake(dx, dy);
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(fmax(-dy, 0.0), fmax(-dy, 0.0), fmax(dy, 0.0), fmax(dx, 0.0)); UIEdgeInsets edgeInsets =
UIEdgeInsetsMake(fmax(-dy, 0.0), fmax(-dy, 0.0), fmax(dy, 0.0), fmax(dx, 0.0));
[self internalTestSelectingAnnotationWithCenterOffsetWithOffset:offset edgeInsets:edgeInsets]; [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; self.mapView.contentInset = edgeInsets;
CGSize size = self.mapView.bounds.size; 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<MLNAnnotation> annotation) { self.viewForAnnotation = ^MLNAnnotationView *(MLNMapView *view, id<MLNAnnotation> annotation) {
if (![annotation isKindOfClass:[MLNPointAnnotation class]]) { if (![annotation isKindOfClass:[MLNPointAnnotation class]]) {
return nil; return nil;
} }
// No dequeue // No dequeue
MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MLNTestAnnotationReuseIdentifer]; MLNAnnotationView *annotationView =
annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; [[MLNAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:MLNTestAnnotationReuseIdentifer];
annotationView.bounds = (CGRect){.origin = CGPointZero, .size = annotationSize};
annotationView.backgroundColor = UIColor.redColor; annotationView.backgroundColor = UIColor.redColor;
annotationView.enabled = YES; annotationView.enabled = YES;
annotationView.centerOffset = offset; annotationView.centerOffset = offset;
@@ -469,9 +492,12 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
[self waitForCollisionDetectionToRun]; [self waitForCollisionDetectionToRun];
// Check that the annotation is in the center of the view // Check that the annotation is in the center of the view
CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate
XCTAssertEqualWithAccuracy(annotationPoint.x, (size.width - edgeInsets.right + edgeInsets.left)/2.0, 0.25); toPointToView:self.mapView];
XCTAssertEqualWithAccuracy(annotationPoint.y, (size.height - edgeInsets.bottom + edgeInsets.top)/2.0, 0.25); 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 // Now test taps around the annotation
CGPoint tapPoint = CGPointMake(annotationPoint.x + offset.dx, annotationPoint.y + offset.dy); CGPoint tapPoint = CGPointMake(annotationPoint.x + offset.dx, annotationPoint.y + offset.dy);
@@ -479,13 +505,11 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:tapPoint persistingResults:YES]; MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:tapPoint persistingResults:YES];
XCTAssert(tagAtPoint != UINT32_MAX, @"Should have tapped on annotation"); XCTAssert(tagAtPoint != UINT32_MAX, @"Should have tapped on annotation");
CGPoint testPoints[] = { CGPoint testPoints[] = {{tapPoint.x - annotationSize.width, tapPoint.y},
{ tapPoint.x - annotationSize.width, tapPoint.y }, {tapPoint.x + annotationSize.width, tapPoint.y},
{ tapPoint.x + annotationSize.width, tapPoint.y }, {tapPoint.x, tapPoint.y - annotationSize.height},
{ tapPoint.x, tapPoint.y - annotationSize.height }, {tapPoint.x, tapPoint.y + annotationSize.height},
{ tapPoint.x, tapPoint.y + annotationSize.height }, CGPointZero};
CGPointZero
};
CGPoint *testPoint = testPoints; CGPoint *testPoint = testPoints;
@@ -510,8 +534,9 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
CGPoint offset = CGPointMake(20, 20); CGPoint offset = CGPointMake(20, 20);
self.mapViewUserLocationAnchorPoint = ^CGPoint (MLNMapView *mapView) { self.mapViewUserLocationAnchorPoint = ^CGPoint(MLNMapView *mapView) {
return offset;; return offset;
;
}; };
[self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil]; [self.mapView setUserTrackingMode:MLNUserTrackingModeFollow animated:NO completionHandler:nil];
@@ -524,11 +549,7 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
// MARK: - Rotating/zooming // MARK: - Rotating/zooming
- (void)testSelectingAnnotationWhenMapIsRotated { - (void)testSelectingAnnotationWhenMapIsRotated {
CLLocationCoordinate2D coordinates[] = {{40.0, 40.0}, {NAN, NAN}};
CLLocationCoordinate2D coordinates[] = {
{ 40.0, 40.0 },
{ NAN, NAN }
};
NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates];
MLNPointAnnotation *annotation = annotations.firstObject; MLNPointAnnotation *annotation = annotations.firstObject;
@@ -537,17 +558,19 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
CLLocationDirection lastAngle = 0.0; CLLocationDirection lastAngle = 0.0;
srand48(0); srand48(0);
for (NSInteger iter = 0; iter < 10; iter++ ) { for (NSInteger iter = 0; iter < 10; iter++) {
CLLocationDirection angle = (CLLocationDirection)((drand48() * 1080.0) - 540.0);
CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); CGPoint anchor = CGPointMake(drand48() * CGRectGetWidth(self.mapView.bounds),
drand48() * CGRectGetHeight(self.mapView.bounds));
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];
NSString *activityTitle = [NSString stringWithFormat:@"Rotate to: %0.1f from: %0.1f", angle, lastAngle];
[XCTContext runActivityNamed:activityTitle [XCTContext runActivityNamed:activityTitle
block:^(id<XCTActivity> _Nonnull activity) { block:^(id<XCTActivity> _Nonnull activity) {
MLNMapCamera *toCamera =
MLNMapCamera *toCamera = [self.mapView cameraByRotatingToDirection:angle aroundAnchorPoint:anchor]; [self.mapView cameraByRotatingToDirection:angle
aroundAnchorPoint:anchor];
[self internalTestSelecting:annotation withCamera:toCamera]; [self internalTestSelecting:annotation withCamera:toCamera];
}]; }];
@@ -556,66 +579,60 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
} }
- (void)testSelectingAnnotationWhenMapIsScaled { - (void)testSelectingAnnotationWhenMapIsScaled {
CLLocationCoordinate2D coordinates[] = {{0.005, 0.005}, {NAN, NAN}};
CLLocationCoordinate2D coordinates[] = {
{ 0.005, 0.005 },
{ NAN, NAN }
};
NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates];
MLNPointAnnotation *annotation = annotations.firstObject; 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); srand48(0);
for (NSInteger iter = 0; iter < 10; iter++ ) { for (NSInteger iter = 0; iter < 10; iter++) {
double zoom = (double)(drand48() * 14.0);
double zoom = (double)(drand48()*14.0);
NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom];
[XCTContext runActivityNamed:activityTitle [XCTContext runActivityNamed:activityTitle
block:^(id<XCTActivity> _Nonnull activity) { block:^(id<XCTActivity> _Nonnull activity) {
MLNMapCamera *toCamera = [self.mapView cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:anchor]; MLNMapCamera *toCamera =
[self.mapView cameraByZoomingToZoomLevel:zoom
aroundAnchorPoint:anchor];
[self internalTestSelecting:annotation withCamera:toCamera]; [self internalTestSelecting:annotation withCamera:toCamera];
}]; }];
} }
} }
- (void)testSelectingAnnotationWhenMapIsScaledAndRotated { - (void)testSelectingAnnotationWhenMapIsScaledAndRotated {
CLLocationCoordinate2D coordinates[] = {{0.005, 0.005}, {NAN, NAN}};
CLLocationCoordinate2D coordinates[] = {
{ 0.005, 0.005 },
{ NAN, NAN }
};
NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates];
MLNPointAnnotation *annotation = annotations.firstObject; MLNPointAnnotation *annotation = annotations.firstObject;
srand48(0); srand48(0);
for (NSInteger iter = 0; iter < 10; iter++ ) { for (NSInteger iter = 0; iter < 10; iter++) {
double zoom = (double)(7.0 + drand48() * 7.0);
CLLocationDirection angle = (CLLocationDirection)((drand48() * 1080.0) - 540.0);
double zoom = (double)(7.0 + drand48()*7.0); CGPoint anchor = CGPointMake(drand48() * CGRectGetWidth(self.mapView.bounds),
CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); drand48() * CGRectGetHeight(self.mapView.bounds));
CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds));
NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom];
[XCTContext runActivityNamed:activityTitle [XCTContext runActivityNamed:activityTitle
block:^(id<XCTActivity> _Nonnull activity) block:^(id<XCTActivity> _Nonnull activity) {
{
mbgl::CameraOptions currentCameraOptions; mbgl::CameraOptions currentCameraOptions;
currentCameraOptions.bearing = angle; currentCameraOptions.bearing = angle;
currentCameraOptions.anchor = mbgl::ScreenCoordinate { anchor.x, anchor.y }; currentCameraOptions.anchor =
mbgl::ScreenCoordinate{anchor.x, anchor.y};
currentCameraOptions.zoom = zoom; currentCameraOptions.zoom = zoom;
MLNMapCamera *toCamera = [self.mapView cameraForCameraOptions:currentCameraOptions]; MLNMapCamera *toCamera =
[self.mapView cameraForCameraOptions:currentCameraOptions];
[self internalTestSelecting:annotation withCamera:toCamera]; [self internalTestSelecting:annotation withCamera:toCamera];
}]; }];
} }
} }
- (void)testShowingAnnotationsThenSelectingAnimated { - (void)testShowingAnnotationsThenSelectingAnimated {
[self internalTestShowingAnnotationsThenSelectingAnimated:YES]; [self internalTestShowingAnnotationsThenSelectingAnimated:YES];
} }
@@ -627,31 +644,34 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated { - (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated {
srand48(0); srand48(0);
CGFloat maxXPadding = std::max(CGRectGetWidth(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); CGFloat maxYPadding = std::max(CGRectGetHeight(self.mapView.bounds) / 5.0, 100.0);
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
UIEdgeInsets edgePadding; UIEdgeInsets edgePadding;
edgePadding.top = floor(drand48()*maxYPadding); edgePadding.top = floor(drand48() * maxYPadding);
edgePadding.bottom = floor(drand48()*maxYPadding); edgePadding.bottom = floor(drand48() * maxYPadding);
edgePadding.left = floor(drand48()*maxXPadding); edgePadding.left = floor(drand48() * maxXPadding);
edgePadding.right = floor(drand48()*maxXPadding); edgePadding.right = floor(drand48() * maxXPadding);
UIEdgeInsets contentInsets; UIEdgeInsets contentInsets;
contentInsets.top = floor(drand48()*maxYPadding); contentInsets.top = floor(drand48() * maxYPadding);
contentInsets.bottom = floor(drand48()*maxYPadding); contentInsets.bottom = floor(drand48() * maxYPadding);
contentInsets.left = floor(drand48()*maxXPadding); contentInsets.left = floor(drand48() * maxXPadding);
contentInsets.right = 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 { - (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated
edgePadding:(UIEdgeInsets)edgeInsets
contentInsets:(UIEdgeInsets)contentInsets {
CLLocationCoordinate2D coordinates[21]; CLLocationCoordinate2D coordinates[21];
for (int i = 0; i < (int)(sizeof(coordinates)/sizeof(coordinates[0])); i++) for (int i = 0; i < (int)(sizeof(coordinates) / sizeof(coordinates[0])); i++) {
{
coordinates[i].latitude = drand48(); coordinates[i].latitude = drand48();
coordinates[i].longitude = drand48(); coordinates[i].longitude = drand48();
} }
@@ -669,7 +689,7 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
[showCompleted fulfill]; [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 // 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 // seeing in https://github.com/mapbox/mapbox-gl-native/issues/15106
@@ -684,27 +704,28 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
[self waitForCollisionDetectionToRun]; [self waitForCollisionDetectionToRun];
} }
- (NSArray*)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D*)coordinates - (NSArray *)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D *)coordinates {
{
__block NSMutableArray *annotations = [NSMutableArray array]; __block NSMutableArray *annotations = [NSMutableArray array];
[XCTContext runActivityNamed:@"Map setup" [XCTContext runActivityNamed:@"Map setup"
block:^(id<XCTActivity> _Nonnull activity) block:^(id<XCTActivity> _Nonnull activity) {
{ NSString *const MLNTestAnnotationReuseIdentifer =
@"MLNTestAnnotationReuseIdentifer";
NSString * const MLNTestAnnotationReuseIdentifer = @"MLNTestAnnotationReuseIdentifer";
CGSize annotationSize = CGSizeMake(40.0, 40.0); CGSize annotationSize = CGSizeMake(40.0, 40.0);
self.viewForAnnotation = ^MLNAnnotationView*(MLNMapView *view, id<MLNAnnotation> annotation2) { self.viewForAnnotation = ^MLNAnnotationView *(
MLNMapView *view, id<MLNAnnotation> annotation2) {
if (![annotation2 isKindOfClass:[MLNPointAnnotation class]]) { if (![annotation2 isKindOfClass:[MLNPointAnnotation class]]) {
return nil; return nil;
} }
// No dequeue // No dequeue
MLNAnnotationView *annotationView = [[MLNAnnotationView alloc] initWithAnnotation:annotation2 reuseIdentifier:MLNTestAnnotationReuseIdentifer]; MLNAnnotationView *annotationView = [[MLNAnnotationView alloc]
annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; initWithAnnotation:annotation2
reuseIdentifier:MLNTestAnnotationReuseIdentifer];
annotationView.bounds =
(CGRect){.origin = CGPointZero, .size = annotationSize};
annotationView.backgroundColor = UIColor.redColor; annotationView.backgroundColor = UIColor.redColor;
annotationView.enabled = YES; annotationView.enabled = YES;
@@ -722,7 +743,6 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
} }
[self.mapView addAnnotations:annotations]; [self.mapView addAnnotations:annotations];
}]; }];
NSArray *copiedAnnotations = [annotations copy]; NSArray *copiedAnnotations = [annotations copy];
@@ -731,15 +751,17 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
return copiedAnnotations; return copiedAnnotations;
} }
- (void)internalTestSelecting:(MLNPointAnnotation*)point withCamera:(MLNMapCamera*)camera { - (void)internalTestSelecting:(MLNPointAnnotation *)point withCamera:(MLNMapCamera *)camera {
// Rotate // Rotate
XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"]; XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"];
[self.mapView setCamera:camera withDuration:0.1 animationTimingFunction:nil completionHandler:^{ [self.mapView setCamera:camera
withDuration:0.1
animationTimingFunction:nil
completionHandler:^{
[rotationCompleted fulfill]; [rotationCompleted fulfill];
}]; }];
[self waitForExpectations:@[rotationCompleted] timeout:1.5]; [self waitForExpectations:@[ rotationCompleted ] timeout:1.5];
// Collision detection may not have completed, if not we may not get our annotation. // Collision detection may not have completed, if not we may not get our annotation.
[self waitForCollisionDetectionToRun]; [self waitForCollisionDetectionToRun];
@@ -748,38 +770,43 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
[self internalSelectDeselectAnnotation:point]; [self internalSelectDeselectAnnotation:point];
} }
- (void)internalSelectDeselectAnnotation:(MLNPointAnnotation*)point { - (void)internalSelectDeselectAnnotation:(MLNPointAnnotation *)point {
[XCTContext runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point] [XCTContext
block:^(id<XCTActivity> _Nonnull activity) runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point]
{ block:^(id<XCTActivity> _Nonnull activity) {
CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate
toPointToView:self.mapView];
MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint persistingResults:YES]; MLNAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint
if (tagAtPoint != UINT32_MAX) persistingResults:YES];
{ if (tagAtPoint != UINT32_MAX) {
id <MLNAnnotation> annotation = [self.mapView annotationWithTag:tagAtPoint]; id<MLNAnnotation> annotation = [self.mapView annotationWithTag:tagAtPoint];
XCTAssertNotNil(annotation); XCTAssertNotNil(annotation);
// Select // Select
XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; XCTestExpectation *selectionCompleted =
[self.mapView selectAnnotation:annotation moveIntoView:NO animateSelection:NO completionHandler:^{ [self expectationWithDescription:@"Selection completed"];
[self.mapView selectAnnotation:annotation
moveIntoView:NO
animateSelection:NO
completionHandler:^{
[selectionCompleted fulfill]; [selectionCompleted fulfill];
}]; }];
[self waitForExpectations:@[selectionCompleted] timeout:0.05]; [self waitForExpectations:@[ selectionCompleted ] timeout:0.05];
XCTAssert(self.mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation"); XCTAssert(self.mapView.selectedAnnotations.count == 1,
XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); @"There should only be 1 selected annotation");
XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation,
@"The annotation should be selected");
// Deselect // Deselect
[self.mapView deselectAnnotation:annotation animated:NO]; [self.mapView deselectAnnotation:annotation animated:NO];
} } else {
else XCTFail(@"Should be an annotation at this point: %@",
{ NSStringFromCGPoint(annotationPoint));
XCTFail(@"Should be an annotation at this point: %@", NSStringFromCGPoint(annotationPoint));
} }
}]; }];
} }
// MARK: - Utilities // MARK: - Utilities
@@ -802,7 +829,7 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
[timerExpired fulfill]; [timerExpired fulfill];
}); });
[self waitForExpectations:@[timerExpired] timeout:seconds + 1.0]; [self waitForExpectations:@[ timerExpired ] timeout:seconds + 1.0];
[timer invalidate]; [timer invalidate];
} }
@@ -814,11 +841,12 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f };
XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"];
// Wait 1/2 second // Wait 1/2 second
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC >> 1)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC >> 1)),
dispatch_get_main_queue(), ^{
[timerExpired fulfill]; [timerExpired fulfill];
}); });
[self waitForExpectations:@[timerExpired, self.renderFinishedExpectation] timeout:5]; [self waitForExpectations:@[ timerExpired, self.renderFinishedExpectation ] timeout:5];
self.renderFinishedExpectation = nil; self.renderFinishedExpectation = nil;
} }

View File

@@ -1,6 +1,6 @@
#import "../../../darwin/src/MLNGeometry_Private.h"
#import "MLNMapViewIntegrationTest.h" #import "MLNMapViewIntegrationTest.h"
#import "MLNTestUtility.h" #import "MLNTestUtility.h"
#import "../../../darwin/src/MLNGeometry_Private.h"
#include <mbgl/map/camera.hpp> #include <mbgl/map/camera.hpp>
@@ -10,12 +10,12 @@
@implementation MLNCameraTransitionFinishTests @implementation MLNCameraTransitionFinishTests
- (void)testEaseToCompletionHandler { - (void)testEaseToCompletionHandler {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0)); CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; 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 [self.mapView setCamera:camera
withDuration:0.0 withDuration:0.0
@@ -24,16 +24,16 @@
[expectation fulfill]; [expectation fulfill];
}]; }];
[self waitForExpectations:@[expectation] timeout:0.5]; [self waitForExpectations:@[ expectation ] timeout:0.5];
} }
- (void)testEaseToCompletionHandlerAnimated { - (void)testEaseToCompletionHandlerAnimated {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0)); CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; 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 [self.mapView setCamera:camera
withDuration:0.3 withDuration:0.3
@@ -42,16 +42,16 @@
[expectation fulfill]; [expectation fulfill];
}]; }];
[self waitForExpectations:@[expectation] timeout:0.5]; [self waitForExpectations:@[ expectation ] timeout:0.5];
} }
- (void)testFlyToCompletionHandler { - (void)testFlyToCompletionHandler {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0)); CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; 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 [self.mapView flyToCamera:camera
withDuration:0.0 withDuration:0.0
@@ -59,16 +59,16 @@
[expectation fulfill]; [expectation fulfill];
}]; }];
[self waitForExpectations:@[expectation] timeout:0.5]; [self waitForExpectations:@[ expectation ] timeout:0.5];
} }
- (void)testFlyToCompletionHandlerAnimated { - (void)testFlyToCompletionHandlerAnimated {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0)); CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; 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 [self.mapView flyToCamera:camera
withDuration:0.3 withDuration:0.3
@@ -76,22 +76,25 @@
[expectation fulfill]; [expectation fulfill];
}]; }];
[self waitForExpectations:@[expectation] timeout:0.5]; [self waitForExpectations:@[ expectation ] timeout:0.5];
} }
@end @end
// MARK: - camera transitions with NaN values // MARK: - camera transitions with NaN values
@interface MLNMapView (MLNCameraTransitionFinishNaNTests) @interface MLNMapView (MLNCameraTransitionFinishNaNTests)
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets; - (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
edgePadding:(UIEdgeInsets)insets;
@end @end
@interface MLNCameraTransitionNaNZoomMapView: MLNMapView @interface MLNCameraTransitionNaNZoomMapView : MLNMapView
@end @end
@implementation MLNCameraTransitionNaNZoomMapView @implementation MLNCameraTransitionNaNZoomMapView
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets { - (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; edgePadding:(UIEdgeInsets)insets {
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera
edgePadding:insets];
options.zoom = NAN; options.zoom = NAN;
return options; return options;
} }

View File

@@ -1,6 +1,6 @@
#import "../../../darwin/src/MLNGeometry_Private.h"
#import "MLNMapViewIntegrationTest.h" #import "MLNMapViewIntegrationTest.h"
#import "MLNTestUtility.h" #import "MLNTestUtility.h"
#import "../../../darwin/src/MLNGeometry_Private.h"
@interface MLNCameraTransitionTests : MLNMapViewIntegrationTest @interface MLNCameraTransitionTests : MLNMapViewIntegrationTest
@end @end
@@ -8,7 +8,6 @@
@implementation MLNCameraTransitionTests @implementation MLNCameraTransitionTests
- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod { - (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2; expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES; expectation.assertForOverFulfill = YES;
@@ -16,7 +15,6 @@
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself; MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return; if (!strongSelf) return;
@@ -34,12 +32,10 @@
[self.mapView setDirection:90 animated:YES]; [self.mapView setDirection:90 animated:YES];
// loop, render, and wait // loop, render, and wait
[self waitForExpectations:@[expectation] timeout:10]; [self waitForExpectations:@[ expectation ] timeout:10];
} }
- (void)testSetAndResetNorthInDelegateMethod { - (void)testSetAndResetNorthInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2; expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES; expectation.assertForOverFulfill = YES;
@@ -47,7 +43,6 @@
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself; MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return; if (!strongSelf) return;
@@ -62,11 +57,10 @@
}; };
[self.mapView setDirection:90 animated:YES]; [self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [self waitForExpectations:@[ expectation ] timeout:10];
} }
- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging { - (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
// Reset to non-zero, prior to testing // Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO]; [self.mapView setDirection:45 animated:NO];
@@ -99,20 +93,21 @@
if (finishedReset) { if (finishedReset) {
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled)); MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
[expectation fulfill]; [expectation fulfill];
} } else {
else {
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled); MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
} }
}; };
[self.mapView setDirection:90 animated:YES]; [self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [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 { - (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 = CLLocationCoordinate2DMake(38.894368, -77.036487);
CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487); CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
@@ -124,30 +119,24 @@
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_after(
dispatch_get_main_queue(), dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
^{
MLNCameraTransitionTests *strongSelf = weakself; MLNCameraTransitionTests *strongSelf = weakself;
[strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; [strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, dc.latitude,
dc.latitude, strongSelf.mapView.centerCoordinate.latitude, 0.0005,
strongSelf.mapView.centerCoordinate.latitude,
0.0005,
@"setting center coordinate should cancel transitions"); @"setting center coordinate should cancel transitions");
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, dc.longitude,
dc.longitude, strongSelf.mapView.centerCoordinate.longitude, 0.0005,
strongSelf.mapView.centerCoordinate.longitude,
0.0005,
@"setting center coordinate should cancel transitions"); @"setting center coordinate should cancel transitions");
[cameraIsInDCExpectation fulfill]; [cameraIsInDCExpectation fulfill];
}); });
[self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0]; [self waitForExpectations:@[ cameraIsInDCExpectation ] timeout:10.0];
} }
- (void)testSetCenterCoordinateInDelegateMethod { - (void)testSetCenterCoordinateInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2; expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES; expectation.assertForOverFulfill = YES;
@@ -159,7 +148,6 @@
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0); CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself; MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return; if (!strongSelf) return;
@@ -168,21 +156,14 @@
CLLocationCoordinate2D center = mapView.centerCoordinate; CLLocationCoordinate2D center = mapView.centerCoordinate;
switch(delegateCallCount) { switch (delegateCallCount) {
case 0: case 0: {
{
// Our center coordinate should match our target (assuming we're not // Our center coordinate should match our target (assuming we're not
// constrained by zoom level) // constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
target.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target"); @"center coordinate longitude should be at target");
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
target.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target"); @"center coordinate latitude should be at target");
// Now set another coordinate. // Now set another coordinate.
@@ -191,23 +172,15 @@
break; break;
} }
case 1: case 1: {
{
// Our center coordinate should match our target (assuming we're not // Our center coordinate should match our target (assuming we're not
// constrained by zoom level) // constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2"); @"center coordinate longitude should be at target2");
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
target2.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target2"); @"center coordinate latitude should be at target2");
break; break;
} }
default: default:
@@ -222,11 +195,10 @@
// Should take MLNAnimationDuration seconds (0.3) // Should take MLNAnimationDuration seconds (0.3)
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES]; [self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [self waitForExpectations:@[ expectation ] timeout:10];
} }
- (void)testFlyToCameraInDelegateMethod { - (void)testFlyToCameraInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
@@ -244,10 +216,10 @@
__block NSTimeInterval stop2 = 0.0; __block NSTimeInterval stop2 = 0.0;
double zoomLevel = 5.0; double zoomLevel = 5.0;
double altitude = MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size); double altitude =
MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself; MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return; if (!strongSelf) return;
@@ -256,23 +228,16 @@
CLLocationCoordinate2D center = mapView.centerCoordinate; CLLocationCoordinate2D center = mapView.centerCoordinate;
switch(delegateCallCount) { switch (delegateCallCount) {
case 0: case 0: {
{
stop1 = CACurrentMediaTime(); stop1 = CACurrentMediaTime();
// Our center coordinate should match our target (assuming we're not // Our center coordinate should match our target (assuming we're not
// constrained by zoom level) // constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
target.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target"); @"center coordinate longitude should be at target");
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
target.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target"); @"center coordinate latitude should be at target");
// Now set another coordinate. // Now set another coordinate.
@@ -282,28 +247,23 @@
heading:0.0]; heading:0.0];
// flyToCamera can take a while... // flyToCamera can take a while...
[mapView flyToCamera:camera completionHandler:^{ [mapView flyToCamera:camera
MLNTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method"); completionHandler:^{
MLNTestAssert(strongSelf, !runloop,
@"Completion block should be called after delegate method");
[expectation fulfill]; [expectation fulfill];
stop2 = CACurrentMediaTime(); stop2 = CACurrentMediaTime();
}]; }];
break; break;
} }
case 1: case 1: {
{
// Our center coordinate should match our target (assuming we're not // Our center coordinate should match our target (assuming we're not
// constrained by zoom level) // constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2"); @"center coordinate longitude should be at target2");
MLNTestAssertEqualWithAccuracy(strongSelf, MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
target2.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target2"); @"center coordinate latitude should be at target2");
runloop = NO; runloop = NO;
@@ -323,17 +283,18 @@
// Should take MLNAnimationDuration // Should take MLNAnimationDuration
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES]; [self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [self waitForExpectations:@[ expectation ] timeout:10];
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0); NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1); 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 // MARK: - Pending tests
- (void)testContinuallyResettingNorthInIsChangingPENDING{ - (void)testContinuallyResettingNorthInIsChangingPENDING {
// See https://github.com/mapbox/mapbox-gl-native/pull/11614 // See https://github.com/mapbox/mapbox-gl-native/pull/11614
// This test currently fails, unsurprisingly, since we're continually // This test currently fails, unsurprisingly, since we're continually
// setting the camera to the same parameters during its update. // setting the camera to the same parameters during its update.
@@ -360,9 +321,10 @@
}; };
[self.mapView setDirection:90 animated:YES]; [self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [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 { - (void)testContinuallySettingCoordinateInIsChangingPENDING {
@@ -386,9 +348,10 @@
}; };
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
[self waitForExpectations:@[expectation] timeout:10]; [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 @end

View File

@@ -1,6 +1,6 @@
#import "MLNMapView+Impl.h"
#import "MLNMapViewIntegrationTest.h" #import "MLNMapViewIntegrationTest.h"
#import "MLNMapView_Private.h" #import "MLNMapView_Private.h"
#import "MLNMapView+Impl.h"
#include <mbgl/gfx/renderable.hpp> #include <mbgl/gfx/renderable.hpp>
@@ -27,7 +27,7 @@
- (void)testAddingRemovingOpenGLLayerWithoutRendering { - (void)testAddingRemovingOpenGLLayerWithoutRendering {
XCTAssertNotNil(self.style); XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{ void (^addRemoveGLLayer)(void) = ^{
__weak id weakLayer = nil; __weak id weakLayer = nil;
@autoreleasepool { @autoreleasepool {
@@ -83,8 +83,7 @@
- (void)testAddingRemovingOpenGLLayer { - (void)testAddingRemovingOpenGLLayer {
XCTAssertNotNil(self.style); XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{ void (^addRemoveGLLayer)(void) = ^{
__weak id retrievedLayer = nil; __weak id retrievedLayer = nil;
@autoreleasepool { @autoreleasepool {
@@ -163,12 +162,15 @@
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"]; NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; withExtension:@"json"];
self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL]; [self.mapView setStyleURL:styleURL];
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:10]; [self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:10];
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed // At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been
// NULLed
XCTAssert(weakLayer == layer2); XCTAssert(weakLayer == layer2);
XCTAssertNotNil(weakLayer); XCTAssertNotNil(weakLayer);
@@ -177,19 +179,20 @@
XCTAssertNil(layer3); XCTAssertNil(layer3);
} }
- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs { - (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs {
__weak id weakLayer; __weak id weakLayer;
@autoreleasepool { @autoreleasepool {
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; withExtension:@"json"];
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds
styleURL:styleURL];
mapView2.delegate = self; mapView2.delegate = self;
XCTAssertNil(mapView2.style); XCTAssertNil(mapView2.style);
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil]; [self waitForExpectationsWithTimeout:10 handler:nil];
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
@@ -203,47 +206,51 @@
} }
- (void)testMLNMapViewImplHasCorrectSize { - (void)testMLNMapViewImplHasCorrectSize {
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; withExtension:@"json"];
self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL]; [self.mapView setStyleURL:styleURL];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:1]; [self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:1];
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl]; MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl];
CGFloat scaleFactor = [UIScreen mainScreen].scale; CGFloat scaleFactor = [UIScreen mainScreen].scale;
mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
mbgl::Size viewSize = { mbgl::Size viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor), static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)
};
// Test that mapView and default renderable have the same size. // Test that mapView and default renderable have the same size.
XCTAssertTrue(renderableSize == viewSize); XCTAssertTrue(renderableSize == viewSize);
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(9.6315313, 52.4133574),
CLLocationCoordinate2DMake(9.6315313, 52.4133574),
CLLocationCoordinate2DMake(24.9410248, 60.1733244)}; CLLocationCoordinate2DMake(24.9410248, 60.1733244)};
MLNPointCollectionFeature *points = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0])]; MLNPointCollectionFeature *points = [MLNPointCollectionFeature
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap" shape:points options:nil]; pointCollectionWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap"
shape:points
options:nil];
[self.style addSource:source]; [self.style addSource:source];
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer"
source:source];
[self.style addLayer:heatmapLayer]; [self.style addLayer:heatmapLayer];
// Test that heatmap layer can create a texture and be successfully rendered. // Test that heatmap layer can create a texture and be successfully rendered.
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
// Resize frame of the view. // Resize frame of the view.
[self.mapView setFrame: CGRect{self.mapView.bounds.origin, {256, 256}}]; [self.mapView setFrame:CGRect{self.mapView.bounds.origin, {256, 256}}];
// Force sync re-layout. // Force sync re-layout.
[self.mapView layoutIfNeeded]; [self.mapView layoutIfNeeded];
// Test that mapView and default renderable have the same size after re-layout. // Test that mapView and default renderable have the same size after re-layout.
renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize(); renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
viewSize = { static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor), viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor) }; static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
XCTAssertTrue(renderableSize == viewSize); XCTAssertTrue(renderableSize == viewSize);
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
} }

View File

@@ -1,28 +1,27 @@
#import <XCTest/XCTest.h> #import <XCTest/XCTest.h>
@import Mapbox; @import Mapbox;
#import "MLNFoundation_Private.h"
#import "MLNMapViewIntegrationTest.h" #import "MLNMapViewIntegrationTest.h"
#import "MLNNetworkConfiguration_Private.h" #import "MLNNetworkConfiguration_Private.h"
#import "MLNOfflineStorage_Private.h" #import "MLNOfflineStorage_Private.h"
#import "MLNFoundation_Private.h"
@interface MLNNetworkConfiguration (Testing) @interface MLNNetworkConfiguration (Testing)
+ (void)testing_clearNativeNetworkManagerDelegate; + (void)testing_clearNativeNetworkManagerDelegate;
+ (id)testing_nativeNetworkManagerDelegate; + (id)testing_nativeNetworkManagerDelegate;
@end @end
@interface MLNNetworkConfigurationTestDelegate: NSObject <MLNNetworkConfigurationDelegate> @interface MLNNetworkConfigurationTestDelegate : NSObject <MLNNetworkConfigurationDelegate>
@property (nonatomic) NSURLSession *(^handler)(); @property (nonatomic) NSURLSession * (^handler)();
@end @end
@interface MLNNetworkConfigurationSessionDelegate: NSObject <NSURLSessionDelegate> @interface MLNNetworkConfigurationSessionDelegate : NSObject <NSURLSessionDelegate>
@property (nonatomic) dispatch_block_t authHandler; @property (nonatomic) dispatch_block_t authHandler;
@end @end
@interface MLNNetworkConfigurationSessionDataDelegate: NSObject <NSURLSessionDataDelegate> @interface MLNNetworkConfigurationSessionDataDelegate : NSObject <NSURLSessionDataDelegate>
@property (nonatomic) void (^dataHandler)(NSURLSessionDataTask *, NSData *); @property (nonatomic) void (^dataHandler)(NSURLSessionDataTask *, NSData *);
@end @end
@interface MLNNetworkConfigurationIntegrationTests : MLNIntegrationTestCase @interface MLNNetworkConfigurationIntegrationTests : MLNIntegrationTestCase
@end @end
@@ -42,15 +41,15 @@
[MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate]; [MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate];
} }
- (void)test0_NativeNetworkManagerDelegateIsSet - (void)test0_NativeNetworkManagerDelegateIsSet {
{
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init]; MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init];
[config resetNativeNetworkManagerDelegate]; [config resetNativeNetworkManagerDelegate];
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
id<MLNNativeNetworkDelegate> manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); id<MLNNativeNetworkDelegate> manager =
MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
XCTAssertNotNil(manager); XCTAssertNotNil(manager);
// Expected properties // Expected properties
@@ -61,8 +60,7 @@
XCTAssert(delegate != delegate2); XCTAssert(delegate != delegate2);
} }
- (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager - (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager {
{
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
// Just calling the shared manager is also sufficient (even though, it's a // Just calling the shared manager is also sufficient (even though, it's a
@@ -72,15 +70,15 @@
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
id<MLNNativeNetworkDelegate> manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate); id<MLNNativeNetworkDelegate> manager =
MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
XCTAssertNotNil(manager); XCTAssertNotNil(manager);
// Expected properties // Expected properties
XCTAssertNotNil([manager sessionConfiguration]); XCTAssertNotNil([manager sessionConfiguration]);
} }
- (void)test2_NativeNetworkManagerDelegateIsSet - (void)test2_NativeNetworkManagerDelegateIsSet {
{
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
[MLNNetworkConfiguration sharedManager]; [MLNNetworkConfiguration sharedManager];
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
@@ -90,8 +88,7 @@
XCTAssert(delegate == delegate2); XCTAssert(delegate == delegate2);
} }
- (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage - (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage {
{
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
// Similar to `[MLNNetworkConfiguration sharedManager]`, // Similar to `[MLNNetworkConfiguration sharedManager]`,
@@ -100,38 +97,36 @@
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
} }
- (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime - (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime {
{
// Testing a second time... // Testing a second time...
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
[MLNOfflineStorage sharedOfflineStorage]; [MLNOfflineStorage sharedOfflineStorage];
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
} }
- (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit - (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit {
{
ASSERT_NATIVE_DELEGATE_IS_NIL(); ASSERT_NATIVE_DELEGATE_IS_NIL();
(void)[[MLNMapView alloc] init]; (void)[[MLNMapView alloc] init];
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL(); ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
} }
- (void)testNetworkConfigurationDelegateIsNil - (void)testNetworkConfigurationDelegateIsNil {
{
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
XCTAssertNil(manager.delegate); XCTAssertNil(manager.delegate);
} }
- (void)internalTestNetworkConfigurationWithSession:(NSURLSession*)session shouldDownload:(BOOL)shouldDownload { - (void)internalTestNetworkConfigurationWithSession:(NSURLSession *)session
shouldDownload:(BOOL)shouldDownload {
__block BOOL didCallSessionDelegate = NO; __block BOOL didCallSessionDelegate = NO;
__block BOOL isMainThread = YES; __block BOOL isMainThread = YES;
// Setup delegate object that provides a NSURLSession // Setup delegate object that provides a NSURLSession
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
MLNNetworkConfigurationTestDelegate *delegate = [[MLNNetworkConfigurationTestDelegate alloc] init]; MLNNetworkConfigurationTestDelegate *delegate =
[[MLNNetworkConfigurationTestDelegate alloc] init];
delegate.handler = ^{ delegate.handler = ^{
NSURLSession *internalSession; NSURLSession *internalSession;
@synchronized (self) { @synchronized(self) {
didCallSessionDelegate = YES; didCallSessionDelegate = YES;
isMainThread = [NSThread isMainThread]; isMainThread = [NSThread isMainThread];
internalSession = session; internalSession = session;
@@ -146,8 +141,10 @@
// //
// Want to ensure we download from the network; nuclear option // Want to ensure we download from the network; nuclear option
{ {
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"Expect database to be reset without an error."];
[[MLNOfflineStorage sharedOfflineStorage]
resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error); XCTAssertNil(error);
[expectation fulfill]; [expectation fulfill];
}]; }];
@@ -156,28 +153,30 @@
// Boston // Boston
MLNCoordinateBounds bounds = { MLNCoordinateBounds bounds = {
{ .latitude = 42.360, .longitude = -71.056 }, {.latitude = 42.360, .longitude = -71.056},
{ .latitude = 42.358, .longitude = -71.053 }, {.latitude = 42.358, .longitude = -71.053},
}; };
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url]; NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL MLNTilePyramidOfflineRegion *region =
[[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
bounds:bounds bounds:bounds
fromZoomLevel:20 fromZoomLevel:20
toZoomLevel:20]; toZoomLevel:20];
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"Name" : @"Faneuil Hall"}];
@"Name": @"Faneuil Hall"
}];
__block MLNOfflinePack *pack = nil; __block MLNOfflinePack *pack = nil;
// Add pack // Add pack
{ {
XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; XCTestExpectation *additionCompletionHandlerExpectation =
[self expectationWithDescription:@"add pack completion handler"];
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region [[MLNOfflineStorage sharedOfflineStorage]
addPackForRegion:region
withContext:context withContext:context
completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) { completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack,
NSError *_Nullable error) {
pack = completionHandlerPack; pack = completionHandlerPack;
[additionCompletionHandlerExpectation fulfill]; [additionCompletionHandlerExpectation fulfill];
}]; }];
@@ -188,14 +187,17 @@
// Download // Download
{ {
XCTestExpectation *expectation = [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { XCTestExpectation *expectation =
[self expectationForNotification:MLNOfflinePackProgressChangedNotification
object:pack
handler:^BOOL(NSNotification *_Nonnull notification) {
return pack.state == MLNOfflinePackStateComplete; return pack.state == MLNOfflinePackStateComplete;
}]; }];
expectation.inverted = !shouldDownload; expectation.inverted = !shouldDownload;
[pack resume]; [pack resume];
[self waitForExpectations:@[expectation] timeout:15]; [self waitForExpectations:@[ expectation ] timeout:15];
} }
XCTAssert(didCallSessionDelegate); XCTAssert(didCallSessionDelegate);
@@ -203,8 +205,10 @@
// Remove pack, so we don't affect other tests // Remove pack, so we don't affect other tests
{ {
XCTestExpectation *expectation = [self expectationWithDescription:@"remove pack completion handler"]; XCTestExpectation *expectation =
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { [self expectationWithDescription:@"remove pack completion handler"];
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
[expectation fulfill]; [expectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:5 handler:nil]; [self waitForExpectationsWithTimeout:5 handler:nil];
@@ -219,7 +223,8 @@
- (void)testNetworkConfigurationWithBackgroundSessionConfiguration { - (void)testNetworkConfigurationWithBackgroundSessionConfiguration {
// Background session configurations are NOT supported, we expect this test // Background session configurations are NOT supported, we expect this test
// trigger an exception // trigger an exception
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)]; NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration
backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]); XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]);
@@ -227,17 +232,20 @@
// We cannot do this yet, as it requires intecepting the exception in gl-native // We cannot do this yet, as it requires intecepting the exception in gl-native
// It makes more sense to support background configs (requiring delegation // It makes more sense to support background configs (requiring delegation
// rather than blocks in gl-native) // rather than blocks in gl-native)
// [self internalTestNetworkConfigurationWithSession:session], NSException, NSInvalidArgumentException); // [self internalTestNetworkConfigurationWithSession:session], NSException,
// NSInvalidArgumentException);
} }
- (void)testNetworkConfigurationWithDefaultSessionConfigurationLOCKED { - (void)testNetworkConfigurationWithDefaultSessionConfigurationLOCKED {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
- (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED { - (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
@@ -246,10 +254,12 @@
__block BOOL didCallAuthChallenge = NO; __block BOOL didCallAuthChallenge = NO;
__block BOOL isMainThread = YES; __block BOOL isMainThread = YES;
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
MLNNetworkConfigurationSessionDelegate *delegate = [[MLNNetworkConfigurationSessionDelegate alloc] init]; [NSURLSessionConfiguration defaultSessionConfiguration];
MLNNetworkConfigurationSessionDelegate *delegate =
[[MLNNetworkConfigurationSessionDelegate alloc] init];
delegate.authHandler = ^{ delegate.authHandler = ^{
@synchronized (self) { @synchronized(self) {
didCallAuthChallenge = YES; didCallAuthChallenge = YES;
isMainThread = [NSThread isMainThread]; isMainThread = [NSThread isMainThread];
} }
@@ -269,10 +279,12 @@
- (void)testFailureForNetworkConfigurationWithSessionWithDataDelegateLOCKED { - (void)testFailureForNetworkConfigurationWithSessionWithDataDelegateLOCKED {
__block BOOL didCallReceiveData = NO; __block BOOL didCallReceiveData = NO;
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
MLNNetworkConfigurationSessionDataDelegate *delegate = [[MLNNetworkConfigurationSessionDataDelegate alloc] init]; [NSURLSessionConfiguration defaultSessionConfiguration];
MLNNetworkConfigurationSessionDataDelegate *delegate =
[[MLNNetworkConfigurationSessionDataDelegate alloc] init];
delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) { delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) {
@synchronized (self) { @synchronized(self) {
didCallReceiveData = YES; didCallReceiveData = YES;
} }
}; };
@@ -299,8 +311,9 @@
- (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED { - (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED {
// Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration`` // Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration``
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
sessionConfig.HTTPAdditionalHeaders = @{ @"testing" : @YES }; [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.HTTPAdditionalHeaders = @{@"testing" : @YES};
sessionConfig.HTTPMaximumConnectionsPerHost = 1; sessionConfig.HTTPMaximumConnectionsPerHost = 1;
sessionConfig.timeoutIntervalForResource = 30; sessionConfig.timeoutIntervalForResource = 30;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
@@ -328,7 +341,10 @@
// MARK: - NSURLSession delegate // MARK: - NSURLSession delegate
@implementation MLNNetworkConfigurationSessionDelegate @implementation MLNNetworkConfigurationSessionDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler { - (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *_Nullable credential))completionHandler {
if (self.authHandler) { if (self.authHandler) {
self.authHandler(); self.authHandler();
} }
@@ -341,7 +357,9 @@
// MARK: - NSURLSession data delegate // MARK: - NSURLSession data delegate
@implementation MLNNetworkConfigurationSessionDataDelegate @implementation MLNNetworkConfigurationSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
if (self.dataHandler) { if (self.dataHandler) {
self.dataHandler(dataTask, data); self.dataHandler(dataTask, data);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@
- (BOOL)application:(UIApplication*)application - (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
#ifndef MLN_LOGGING_DISABLED #ifndef MLN_LOGGING_DISABLED
[MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault; [MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault;
#endif #endif

View File

@@ -19,16 +19,15 @@
frameRenderingTime:(double)frameRenderingTime; frameRenderingTime:(double)frameRenderingTime;
@end @end
class BenchMapObserver : public mbgl::MapObserver { class BenchMapObserver : public mbgl::MapObserver {
public: public:
BenchMapObserver() = delete; BenchMapObserver() = delete;
BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {} BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {}
virtual ~BenchMapObserver() = default; virtual ~BenchMapObserver() = default;
void onDidFinishRenderingFrame(const RenderFrameStatus& status) override final { void onDidFinishRenderingFrame(const RenderFrameStatus &status) override final {
//NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3); // NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3);
//NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3); // NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3);
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full; bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
[mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered [mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered
@@ -54,58 +53,58 @@ protected:
// MARK: - Setup // MARK: - Setup
+ (void)initialize + (void)initialize {
{ if (self == [MBXBenchViewController class]) {
if (self == [MBXBenchViewController class])
{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"MBXUserTrackingMode": @(MLNUserTrackingModeNone), @"MBXUserTrackingMode" : @(MLNUserTrackingModeNone),
@"MBXShowsUserLocation": @NO, @"MBXShowsUserLocation" : @NO,
@"MBXDebug": @NO, @"MBXDebug" : @NO,
}]; }];
} }
} }
- (void)viewDidLoad - (void)viewDidLoad {
{
[super viewDidLoad]; [super viewDidLoad];
} }
- (void)viewDidAppear:(BOOL)animated - (void)viewDidAppear:(BOOL)animated {
{
[super viewDidAppear:animated]; [super viewDidAppear:animated];
#ifdef LOG_TO_DOCUMENTS_DIR #ifdef LOG_TO_DOCUMENTS_DIR
[self setAndRedirectLogFileToDocuments]; [self setAndRedirectLogFileToDocuments];
#endif #endif
// Use a local style and local assets if they’ve been downloaded. // 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 *tile = [[NSBundle mainBundle] URLForResource:@"11"
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles" withExtension:@"json" subdirectory:@"tiles"]; withExtension:@"pbf"
subdirectory:@"tiles/tiles/v3/5/7"];
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles"
withExtension:@"json"
subdirectory:@"tiles"];
const std::vector<std::string> styles = { const std::vector<std::string> styles = {
"maptiler://maps/streets", "maptiler://maps/streets",
}; };
constexpr auto styleIndex = 0; constexpr auto styleIndex = 0;
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]]; NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json"
: [NSString stringWithCString:styles[styleIndex].c_str()
encoding:NSUTF8StringEncoding]];
NSLog(@"Using style URL: \"%@\"", [url absoluteString]); NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.imageView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.imageView]; [self.view addSubview:self.imageView];
mbgl::Size viewSize = { static_cast<uint32_t>(self.view.bounds.size.width), mbgl::Size viewSize = {static_cast<uint32_t>(self.view.bounds.size.width),
static_cast<uint32_t>(self.view.bounds.size.height) }; static_cast<uint32_t>(self.view.bounds.size.height)};
auto pixelRatio = [[UIScreen mainScreen] scale]; auto pixelRatio = [[UIScreen mainScreen] scale];
observer = std::make_unique<BenchMapObserver>(self); observer = std::make_unique<BenchMapObserver>(self);
frontend = std::make_unique<mbgl::HeadlessFrontend>( frontend = std::make_unique<mbgl::HeadlessFrontend>(
viewSize, viewSize, pixelRatio, mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
pixelRatio,
mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
mbgl::gfx::ContextMode::Unique, mbgl::gfx::ContextMode::Unique,
/* localFontFamily */ std::nullopt, /* localFontFamily */ std::nullopt,
/* invalidateOnUpdate */ false /* invalidateOnUpdate */ false);
);
mbgl::MapOptions mapOptions; mbgl::MapOptions mapOptions;
mapOptions.withMapMode(mbgl::MapMode::Continuous) mapOptions.withMapMode(mbgl::MapMode::Continuous)
@@ -115,7 +114,8 @@ protected:
.withViewportMode(mbgl::ViewportMode::Default) .withViewportMode(mbgl::ViewportMode::Default)
.withCrossSourceCollisions(true); .withCrossSourceCollisions(true);
mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; mbgl::TileServerOptions *tileServerOptions =
[[MLNSettings sharedSettings] tileServerOptionsInternal];
mbgl::ResourceOptions resourceOptions; mbgl::ResourceOptions resourceOptions;
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
@@ -127,7 +127,8 @@ protected:
resourceOptions.withApiKey([apiKey UTF8String]); resourceOptions.withApiKey([apiKey UTF8String]);
} }
map = std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions); map =
std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
map->setSize(viewSize); map->setSize(viewSize);
map->setDebug(mbgl::MapDebugOptions::NoDebug); map->setDebug(mbgl::MapDebugOptions::NoDebug);
map->getStyle().loadURL([url.absoluteString UTF8String]); map->getStyle().loadURL([url.absoluteString UTF8String]);
@@ -136,20 +137,21 @@ protected:
} }
// For setting the filename // For setting the filename
NSDate* const currentDate = [NSDate date]; NSDate *const currentDate = [NSDate date];
/*! /*!
\description \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 - (void)setAndRedirectLogFileToDocuments {
{ NSString *const name = UIDevice.currentDevice.name;
NSString* const name = UIDevice.currentDevice.name; #if TARGET_IPHONE_SIMULATOR
#if TARGET_IPHONE_SIMULATOR NSString *const DeviceMode = @"Simulator";
NSString* const DeviceMode = @"Simulator"; #else
#else NSString *const DeviceMode = @"Device";
NSString* const DeviceMode = @"Device"; #endif
#endif
// Set log file name date // Set log file name date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
@@ -157,20 +159,21 @@ NSDate* const currentDate = [NSDate date];
NSString *dateString = [formatter stringFromDate:currentDate]; NSString *dateString = [formatter stringFromDate:currentDate];
// Set the log file name // Set the log file name
NSString* filename = [NSString stringWithFormat: @"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString]; NSString *filename =
[NSString stringWithFormat:@"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString];
// Set log file path // Set log file path
NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSArray *allPaths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [allPaths objectAtIndex:0]; NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent: filename]; NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:filename];
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command"); NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
NSLog(@" open \"%@\"", pathForLog); NSLog(@" open \"%@\"", pathForLog);
[self redirectLogToDocuments :pathForLog]; [self redirectLogToDocuments:pathForLog];
} }
- (void)redirectLogToDocuments: (NSString*) fileName - (void)redirectLogToDocuments:(NSString *)fileName {
{
// write to file in append "a+" mode // write to file in append "a+" mode
freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr); freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
} }
@@ -181,16 +184,15 @@ int frames = 0;
double totalFrameEncodingTime = 0; double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0; double totalFrameRenderingTime = 0;
std::chrono::steady_clock::time_point started; std::chrono::steady_clock::time_point started;
std::vector<std::pair<std::string, std::pair<double, double>> > result; std::vector<std::pair<std::string, std::pair<double, double>>> result;
static const int benchmarkDuration = 5; // seconds static const int benchmarkDuration = 5; // seconds
namespace mbgl { namespace mbgl {
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments; extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments;
} }
- (void)renderFrame - (void)renderFrame {
{
mbgl::gfx::BackendScope guard{*(frontend->getBackend())}; mbgl::gfx::BackendScope guard{*(frontend->getBackend())};
frontend->renderFrame(); frontend->renderFrame();
@@ -198,15 +200,9 @@ namespace mbgl {
auto image = frontend->readStillImage(); auto image = frontend->readStillImage();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate( CGContextRef bitmapContext =
image.data.get(), CGBitmapContextCreate(image.data.get(), image.size.width, image.size.height, 8,
image.size.width, 4 * image.size.width, colorSpace, kCGImageAlphaPremultipliedLast);
image.size.height,
8,
4 * image.size.width,
colorSpace,
kCGImageAlphaPremultipliedLast
);
CFRelease(colorSpace); CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext); CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext); CGContextRelease(bitmapContext);
@@ -215,17 +211,17 @@ namespace mbgl {
CGImageRelease(cgImage); CGImageRelease(cgImage);
} }
- (void)startBenchmarkIteration - (void)startBenchmarkIteration {
{
if (mbgl::bench::locations.size() > idx) { if (mbgl::bench::locations.size() > idx) {
const auto& location = mbgl::bench::locations[idx]; const auto &location = mbgl::bench::locations[idx];
mbgl::CameraOptions cameraOptions; mbgl::CameraOptions cameraOptions;
cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude); cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude);
cameraOptions.zoom = location.zoom; cameraOptions.zoom = location.zoom;
cameraOptions.bearing = location.bearing; cameraOptions.bearing = location.bearing;
mbgl::AnimationOptions animationOptions; mbgl::AnimationOptions animationOptions;
animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(benchmarkDuration))); animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(
std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
map->easeTo(cameraOptions, animationOptions); map->easeTo(cameraOptions, animationOptions);
state = State::WaitingForAssets; state = State::WaitingForAssets;
@@ -241,14 +237,15 @@ namespace mbgl {
NSLog(@"Result:"); NSLog(@"Result:");
size_t colWidth = 0; size_t colWidth = 0;
for (const auto& row : result) { for (const auto &row : result) {
colWidth = std::max(row.first.size(), colWidth); colWidth = std::max(row.first.size(), colWidth);
} }
double totalFrameEncodingTime = 0; double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0; double totalFrameRenderingTime = 0;
for (const auto& row : result) { 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); 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; totalFrameEncodingTime += row.second.first;
totalFrameRenderingTime += row.second.second; totalFrameRenderingTime += row.second.second;
} }
@@ -271,15 +268,14 @@ namespace mbgl {
// this does not shut the application down correctly, // this does not shut the application down correctly,
// and results in an assertion failure in thread-local code // and results in an assertion failure in thread-local code
//exit(0); // exit(0);
// Use the UIApplication lifecycle instead. // Use the UIApplication lifecycle instead.
// Terminating an app programmatically is strongly discouraged by Apple. // Terminating an app programmatically is strongly discouraged by Apple.
// Combined with the plist setting "Application does not run in background" suspend allows // 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. // the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait.
UIApplication *app = [UIApplication sharedApplication]; UIApplication *app = [UIApplication sharedApplication];
if ([app respondsToSelector:@selector(suspend)]) if ([app respondsToSelector:@selector(suspend)]) {
{
[app performSelector:@selector(suspend)]; [app performSelector:@selector(suspend)];
} }
} }
@@ -287,24 +283,25 @@ namespace mbgl {
- (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered - (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered
frameEncodingTime:(double)frameEncodingTime frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime frameRenderingTime:(double)frameRenderingTime {
{ if (state == State::Benchmarking) {
if (state == State::Benchmarking)
{
frames++; frames++;
totalFrameEncodingTime += frameEncodingTime; totalFrameEncodingTime += frameEncodingTime;
totalFrameRenderingTime += frameRenderingTime; totalFrameRenderingTime += frameRenderingTime;
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count(); const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
if (duration >= benchmarkDuration * 1e6) std::chrono::steady_clock::now() - started)
{ .count();
if (duration >= benchmarkDuration * 1e6) {
state = State::None; state = State::None;
// Report FPS // Report FPS
const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames; const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames; const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime)); result.emplace_back(mbgl::bench::locations[idx].name,
NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames); 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. // Start benchmarking the next location.
idx++; idx++;
@@ -315,11 +312,8 @@ namespace mbgl {
}); });
} }
return; return;
} } else if (state == State::WaitingForAssets) {
else if (state == State::WaitingForAssets) if (map->isFullyLoaded()) {
{
if (map->isFullyLoaded())
{
// Start the benchmarking timer. // Start the benchmarking timer.
frames = 0; frames = 0;
totalFrameEncodingTime = 0; totalFrameEncodingTime = 0;
@@ -335,8 +329,7 @@ namespace mbgl {
} }
} }
- (NSUInteger)supportedInterfaceOrientations - (NSUInteger)supportedInterfaceOrientations {
{
return UIInterfaceOrientationMaskLandscape; return UIInterfaceOrientationMaskLandscape;
} }

View File

@@ -2,8 +2,7 @@
#import "MBXBenchAppDelegate.h" #import "MBXBenchAppDelegate.h"
int main(int argc, char * argv[]) int main(int argc, char* argv[]) {
{
@autoreleasepool { @autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXBenchAppDelegate class])); return UIApplicationMain(argc, argv, nil, NSStringFromClass([MBXBenchAppDelegate class]));
} }

View File

@@ -1,10 +1,10 @@
#import "MLNAnnotationView.h" #import "MLNAnnotationView.h"
#import "MLNAnnotationView_Private.h"
#import "MLNMapView_Private.h"
#import "MLNCalloutView.h"
#import "MLNAnnotation.h" #import "MLNAnnotation.h"
#import "MLNPointAnnotation.h" #import "MLNAnnotationView_Private.h"
#import "MLNCalloutView.h"
#import "MLNLoggingConfiguration_Private.h" #import "MLNLoggingConfiguration_Private.h"
#import "MLNMapView_Private.h"
#import "MLNPointAnnotation.h"
#import "NSBundle+MLNAdditions.h" #import "NSBundle+MLNAdditions.h"
#import "NSValue+MLNAdditions.h" #import "NSValue+MLNAdditions.h"
@@ -39,7 +39,8 @@
return self; return self;
} }
- (instancetype)initWithAnnotation:(nullable id<MLNAnnotation>)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier { - (instancetype)initWithAnnotation:(nullable id<MLNAnnotation>)annotation
reuseIdentifier:(nullable NSString *)reuseIdentifier {
MLNLogDebug(@"Initializing with annotation: %@ reuseIdentifier: %@", annotation, reuseIdentifier); MLNLogDebug(@"Initializing with annotation: %@ reuseIdentifier: %@", annotation, reuseIdentifier);
self = [super initWithFrame:CGRectZero]; self = [super initWithFrame:CGRectZero];
if (self) { if (self) {
@@ -48,7 +49,8 @@
return self; return self;
} }
- (void)commonInitWithAnnotation:(nullable id<MLNAnnotation>)annotation reuseIdentifier:(nullable NSString *)reuseIdentifier { - (void)commonInitWithAnnotation:(nullable id<MLNAnnotation>)annotation
reuseIdentifier:(nullable NSString *)reuseIdentifier {
_lastAppliedScaleTransform = CATransform3DIdentity; _lastAppliedScaleTransform = CATransform3DIdentity;
_lastAppliedRotationTransform = CATransform3DIdentity; _lastAppliedRotationTransform = CATransform3DIdentity;
_annotation = annotation; _annotation = annotation;
@@ -83,42 +85,37 @@
[coder encodeBool:_draggable forKey:@"draggable"]; [coder encodeBool:_draggable forKey:@"draggable"];
} }
- (void)prepareForReuse - (void)prepareForReuse {
{
// Intentionally left blank. The default implementation of this method does nothing. // Intentionally left blank. The default implementation of this method does nothing.
} }
- (void)setCenterOffset:(CGVector)centerOffset - (void)setCenterOffset:(CGVector)centerOffset {
{
MLNLogDebug(@"Setting centerOffset: %@", NSStringFromCGVector(centerOffset)); MLNLogDebug(@"Setting centerOffset: %@", NSStringFromCGVector(centerOffset));
_centerOffset = centerOffset; _centerOffset = centerOffset;
self.center = self.center; self.center = self.center;
} }
- (void)setSelected:(BOOL)selected - (void)setSelected:(BOOL)selected {
{
MLNLogDebug(@"Setting selected: %@", MLNStringFromBOOL(selected)); MLNLogDebug(@"Setting selected: %@", MLNStringFromBOOL(selected));
[self setSelected:selected animated:NO]; [self setSelected:selected animated:NO];
} }
- (void)setSelected:(BOOL)selected animated:(BOOL)animated - (void)setSelected:(BOOL)selected animated:(BOOL)animated {
{ MLNLogDebug(@"Setting selected: %@ animated: %@", MLNStringFromBOOL(selected),
MLNLogDebug(@"Setting selected: %@ animated: %@", MLNStringFromBOOL(selected), MLNStringFromBOOL(animated)); MLNStringFromBOOL(animated));
[self willChangeValueForKey:@"selected"]; [self willChangeValueForKey:@"selected"];
_selected = selected; _selected = selected;
[self didChangeValueForKey:@"selected"]; [self didChangeValueForKey:@"selected"];
} }
- (CGPoint)center - (CGPoint)center {
{
CGPoint center = super.center; CGPoint center = super.center;
center.x -= _centerOffset.dx; center.x -= _centerOffset.dx;
center.y -= _centerOffset.dy; center.y -= _centerOffset.dy;
return center; return center;
} }
- (void)setCenter:(CGPoint)center - (void)setCenter:(CGPoint)center {
{
MLNLogDebug(@"Setting center: %@", NSStringFromCGPoint(center)); MLNLogDebug(@"Setting center: %@", NSStringFromCGPoint(center));
center.x += _centerOffset.dx; center.x += _centerOffset.dx;
center.y += _centerOffset.dy; center.y += _centerOffset.dy;
@@ -128,27 +125,26 @@
[self updateRotateTransform]; [self updateRotateTransform];
} }
- (void)setScalesWithViewingDistance:(BOOL)scalesWithViewingDistance - (void)setScalesWithViewingDistance:(BOOL)scalesWithViewingDistance {
{ MLNLogDebug(@"Setting scaleWithViewingDistance: %@",
MLNLogDebug(@"Setting scaleWithViewingDistance: %@", MLNStringFromBOOL(scalesWithViewingDistance)); MLNStringFromBOOL(scalesWithViewingDistance));
if (_scalesWithViewingDistance != scalesWithViewingDistance) if (_scalesWithViewingDistance != scalesWithViewingDistance) {
{
_scalesWithViewingDistance = scalesWithViewingDistance; _scalesWithViewingDistance = scalesWithViewingDistance;
[self updateScaleTransformForViewingDistance]; [self updateScaleTransformForViewingDistance];
} }
} }
- (void)updateScaleTransformForViewingDistance - (void)updateScaleTransformForViewingDistance {
{ if (self.scalesWithViewingDistance == NO || self.dragState == MLNAnnotationViewDragStateDragging)
if (self.scalesWithViewingDistance == NO || self.dragState == MLNAnnotationViewDragStateDragging) return; return;
CGFloat superviewHeight = CGRectGetHeight(self.superview.frame); CGFloat superviewHeight = CGRectGetHeight(self.superview.frame);
if (superviewHeight > 0.0) { if (superviewHeight > 0.0) {
// Find the maximum amount of scale reduction to apply as the view's center moves from the top // 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 // 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 // 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 // or 75%. The range goes from a maximum of 100% to 0% as the view moves from the top to the
// along the y axis of its superview. // bottom along the y axis of its superview.
CGFloat maxScaleReduction = 1.0 - self.center.y / superviewHeight; 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 // Since it is possible for the map view to report a pitch less than 0 due to the nature of
@@ -161,7 +157,8 @@
// The pitch intensity represents how much the map view is actually pitched compared to // 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 // 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`. // 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); CGFloat pitchIntensity = pitch / MLNDegreesFromRadians(mbgl::util::PITCH_MAX);
// The pitch adjusted scale is the inverse proportion of the maximum possible scale reduction // The pitch adjusted scale is the inverse proportion of the maximum possible scale reduction
@@ -174,25 +171,24 @@
// we can account for it so that we don't get cumulative scaling every time we move. // 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. // We also avoid clobbering any existing transform passed in by the client or this SDK.
CATransform3D undoOfLastScaleTransform = CATransform3DInvert(_lastAppliedScaleTransform); CATransform3D undoOfLastScaleTransform = CATransform3DInvert(_lastAppliedScaleTransform);
CATransform3D newScaleTransform = CATransform3DMakeScale(pitchAdjustedScale, pitchAdjustedScale, 1); CATransform3D newScaleTransform =
CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastScaleTransform, newScaleTransform); CATransform3DMakeScale(pitchAdjustedScale, pitchAdjustedScale, 1);
CATransform3D effectiveTransform =
CATransform3DConcat(undoOfLastScaleTransform, newScaleTransform);
self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform); self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform);
_lastAppliedScaleTransform = newScaleTransform; _lastAppliedScaleTransform = newScaleTransform;
} }
} }
- (void)setRotatesToMatchCamera:(BOOL)rotatesToMatchCamera - (void)setRotatesToMatchCamera:(BOOL)rotatesToMatchCamera {
{
MLNLogDebug(@"Setting rotatesToMatchCamera: %@", MLNStringFromBOOL(rotatesToMatchCamera)); MLNLogDebug(@"Setting rotatesToMatchCamera: %@", MLNStringFromBOOL(rotatesToMatchCamera));
if (_rotatesToMatchCamera != rotatesToMatchCamera) if (_rotatesToMatchCamera != rotatesToMatchCamera) {
{
_rotatesToMatchCamera = rotatesToMatchCamera; _rotatesToMatchCamera = rotatesToMatchCamera;
[self updateRotateTransform]; [self updateRotateTransform];
} }
} }
- (void)updateRotateTransform - (void)updateRotateTransform {
{
if (self.rotatesToMatchCamera == NO) return; if (self.rotatesToMatchCamera == NO) return;
CGFloat direction = -MLNRadiansFromDegrees(self.mapView.direction); CGFloat direction = -MLNRadiansFromDegrees(self.mapView.direction);
@@ -206,57 +202,52 @@
// We also avoid clobbering any existing transform passed in by the client or this SDK. // We also avoid clobbering any existing transform passed in by the client or this SDK.
CATransform3D undoOfLastRotationTransform = CATransform3DInvert(_lastAppliedRotationTransform); CATransform3D undoOfLastRotationTransform = CATransform3DInvert(_lastAppliedRotationTransform);
CATransform3D newRotationTransform = CATransform3DMakeRotation(direction, 0, 0, 1); CATransform3D newRotationTransform = CATransform3DMakeRotation(direction, 0, 0, 1);
CATransform3D effectiveTransform = CATransform3DConcat(undoOfLastRotationTransform, newRotationTransform); CATransform3D effectiveTransform =
CATransform3DConcat(undoOfLastRotationTransform, newRotationTransform);
self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform); self.layer.transform = CATransform3DConcat(self.layer.transform, effectiveTransform);
_lastAppliedRotationTransform = newRotationTransform; _lastAppliedRotationTransform = newRotationTransform;
} }
// MARK: - Draggable // MARK: - Draggable
- (void)setDraggable:(BOOL)draggable - (void)setDraggable:(BOOL)draggable {
{
MLNLogDebug(@"Setting draggable: %@", MLNStringFromBOOL(draggable)); MLNLogDebug(@"Setting draggable: %@", MLNStringFromBOOL(draggable));
[self willChangeValueForKey:@"draggable"]; [self willChangeValueForKey:@"draggable"];
_draggable = draggable; _draggable = draggable;
[self didChangeValueForKey:@"draggable"]; [self didChangeValueForKey:@"draggable"];
if (draggable) if (draggable) {
{
[self enableDrag]; [self enableDrag];
} } else {
else
{
[self disableDrag]; [self disableDrag];
} }
} }
- (void)enableDrag - (void)enableDrag {
{ if (!_longPressRecognizer) {
if (!_longPressRecognizer) UILongPressGestureRecognizer *recognizer =
{ [[UILongPressGestureRecognizer alloc] initWithTarget:self
UILongPressGestureRecognizer *recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)]; action:@selector(handleLongPress:)];
recognizer.delegate = self; recognizer.delegate = self;
[self addGestureRecognizer:recognizer]; [self addGestureRecognizer:recognizer];
_longPressRecognizer = recognizer; _longPressRecognizer = recognizer;
} }
if (!_panGestureRecognizer) if (!_panGestureRecognizer) {
{ UIPanGestureRecognizer *recognizer =
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)]; [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
recognizer.delegate = self; recognizer.delegate = self;
[self addGestureRecognizer:recognizer]; [self addGestureRecognizer:recognizer];
_panGestureRecognizer = recognizer; _panGestureRecognizer = recognizer;
} }
} }
- (void)disableDrag - (void)disableDrag {
{
[self removeGestureRecognizer:_longPressRecognizer]; [self removeGestureRecognizer:_longPressRecognizer];
[self removeGestureRecognizer:_panGestureRecognizer]; [self removeGestureRecognizer:_panGestureRecognizer];
} }
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender - (void)handleLongPress:(UILongPressGestureRecognizer *)sender {
{
switch (sender.state) { switch (sender.state) {
case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateBegan:
self.dragState = MLNAnnotationViewDragStateStarting; self.dragState = MLNAnnotationViewDragStateStarting;
@@ -278,8 +269,7 @@
} }
} }
- (void)handlePan:(UIPanGestureRecognizer *)sender - (void)handlePan:(UIPanGestureRecognizer *)sender {
{
self.center = [sender locationInView:sender.view.superview]; self.center = [sender locationInView:sender.view.superview];
if (sender.state == UIGestureRecognizerStateEnded) { if (sender.state == UIGestureRecognizerStateEnded) {
@@ -287,62 +277,58 @@
} }
} }
- (void)setDragState:(MLNAnnotationViewDragState)dragState - (void)setDragState:(MLNAnnotationViewDragState)dragState {
{
MLNLogDebug(@"Setting dragState: %lu", (unsigned long)dragState); MLNLogDebug(@"Setting dragState: %lu", (unsigned long)dragState);
[self setDragState:dragState animated:YES]; [self setDragState:dragState animated:YES];
} }
- (void)setDragState:(MLNAnnotationViewDragState)dragState animated:(BOOL)animated - (void)setDragState:(MLNAnnotationViewDragState)dragState animated:(BOOL)animated {
{ MLNLogDebug(@"Setting dragState: %lu animated: %@", (unsigned long)dragState,
MLNLogDebug(@"Setting dragState: %lu animated: %@", (unsigned long)dragState, MLNStringFromBOOL(animated)); MLNStringFromBOOL(animated));
[self willChangeValueForKey:@"dragState"]; [self willChangeValueForKey:@"dragState"];
_dragState = dragState; _dragState = dragState;
[self didChangeValueForKey:@"dragState"]; [self didChangeValueForKey:@"dragState"];
if (dragState == MLNAnnotationViewDragStateStarting) if (dragState == MLNAnnotationViewDragStateStarting) {
{
[self.mapView.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated]; [self.mapView.calloutViewForSelectedAnnotation dismissCalloutAnimated:animated];
[self.superview bringSubviewToFront:self]; [self.superview bringSubviewToFront:self];
} } else if (dragState == MLNAnnotationViewDragStateCanceling) {
else if (dragState == MLNAnnotationViewDragStateCanceling)
{
if (!self.annotation) { if (!self.annotation) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Annotation property should not be nil."]; format:@"Annotation property should not be nil."];
} }
self.panGestureRecognizer.enabled = NO; self.panGestureRecognizer.enabled = NO;
self.longPressRecognizer.enabled = NO; self.longPressRecognizer.enabled = NO;
self.center = [self.mapView convertCoordinate:self.annotation.coordinate toPointToView:self.mapView]; self.center = [self.mapView convertCoordinate:self.annotation.coordinate
toPointToView:self.mapView];
self.panGestureRecognizer.enabled = YES; self.panGestureRecognizer.enabled = YES;
self.longPressRecognizer.enabled = YES; self.longPressRecognizer.enabled = YES;
self.dragState = MLNAnnotationViewDragStateNone; self.dragState = MLNAnnotationViewDragStateNone;
} } else if (dragState == MLNAnnotationViewDragStateEnding) {
else if (dragState == MLNAnnotationViewDragStateEnding) if ([self.annotation respondsToSelector:@selector(setCoordinate:)]) {
{ CLLocationCoordinate2D coordinate = [self.mapView convertPoint:self.center
if ([self.annotation respondsToSelector:@selector(setCoordinate:)]) toCoordinateFromView:self.mapView];
{ [(NSObject *)self.annotation setValue:[NSValue valueWithMLNCoordinate:coordinate]
CLLocationCoordinate2D coordinate = [self.mapView convertPoint:self.center toCoordinateFromView:self.mapView]; forKey:@"coordinate"];
[(NSObject *)self.annotation setValue:[NSValue valueWithMLNCoordinate:coordinate] forKey:@"coordinate"];
} }
} }
} }
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
{
BOOL isDragging = self.dragState == MLNAnnotationViewDragStateDragging; BOOL isDragging = self.dragState == MLNAnnotationViewDragStateDragging;
if (gestureRecognizer == _panGestureRecognizer && !(isDragging)) if (gestureRecognizer == _panGestureRecognizer && !(isDragging)) {
{
return NO; return NO;
} }
return YES; return YES;
} }
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{ shouldRecognizeSimultaneouslyWithGestureRecognizer:
return otherGestureRecognizer == _longPressRecognizer || otherGestureRecognizer == _panGestureRecognizer; (UIGestureRecognizer *)otherGestureRecognizer {
return otherGestureRecognizer == _longPressRecognizer ||
otherGestureRecognizer == _panGestureRecognizer;
} }
// MARK: UIAccessibility methods // MARK: UIAccessibility methods
@@ -356,20 +342,23 @@
} }
- (NSString *)accessibilityLabel { - (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 { - (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 { - (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 {
CGRect accessibilityFrame = self.frame; CGRect accessibilityFrame = self.frame;
CGRect minimumFrame = CGRectInset({ self.center, CGSizeZero }, CGRect minimumFrame = CGRectInset({self.center, CGSizeZero},
-MLNAnnotationAccessibilityElementMinimumSize.width / 2, -MLNAnnotationAccessibilityElementMinimumSize.width / 2,
-MLNAnnotationAccessibilityElementMinimumSize.height / 2); -MLNAnnotationAccessibilityElementMinimumSize.height / 2);
accessibilityFrame = CGRectUnion(accessibilityFrame, minimumFrame); accessibilityFrame = CGRectUnion(accessibilityFrame, minimumFrame);

Some files were not shown because too many files have changed in this diff Show More