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,271 +22,302 @@ 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)];
} }
@end @end
class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost { class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost {
public: public:
ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) { ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) {
layerRef = styleLayer; layerRef = styleLayer;
layer = nil; layer = nil;
}
void initialize() override {
if (layerRef == nil)
return;
else if (layer == nil)
layer = layerRef;
}
void update(Interface &interface) override {
// if we have built our drawable(s) already, either update or skip
if (interface.getDrawableCount()) return;
constexpr float extent = mbgl::util::EXTENT;
// add classic polylines
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
/*width=*/8.0f, /*color=*/Color::red()},
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
/*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
/*width=*/0.5f, /*color=*/Color::black()},
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints,
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index{0}; index < numLines; ++index) {
for (auto &p : polyline) {
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
}
} }
void initialize() override { // add wide vector polylines with tile coordinates
if (layerRef == nil) return; {
else if (layer == nil) layer = layerRef; using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
/*width=*/8.0f, /*color=*/Color::red()},
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
/*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
/*width=*/0.5f, /*color=*/Color::black()},
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints,
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index{0}; index < numLines; ++index) {
for (auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone
for (auto &p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for (auto &p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
} }
void update(Interface& interface) override { // add fill polygon
{
using namespace mbgl;
// if we have built our drawable(s) already, either update or skip // set tile
if (interface.getDrawableCount()) interface.setTileID({11, 327, 790});
return;
constexpr float extent = mbgl::util::EXTENT; GeometryCollection geometry{
{
// ring 1
{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.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.0f), static_cast<int16_t>(extent * 0.5f)},
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
},
{
// ring 2
{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.25f), static_cast<int16_t>(extent * 0.45f)},
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
},
};
// add classic polylines // set properties
{ interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
using namespace mbgl;
// set tile // add fill
interface.setTileID({11, 327, 792}); interface.addFill(geometry);
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index {0}; index < numLines; ++index) {
for(auto &p : polyline) {
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
}
}
// add wide vector polylines with tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index {0}; index < numLines; ++index) {
for(auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone
for(auto &p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for(auto &p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
}
// add fill polygon
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
GeometryCollection geometry{
{
// ring 1
{static_cast<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.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.0f), static_cast<int16_t>(extent* 0.5f)},
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)},
},
{
// ring 2
{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.25f), static_cast<int16_t>(extent* 0.45f)},
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)},
},
};
// set properties
interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
// add fill
interface.addFill(geometry);
}
// add symbol
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 789});
GeometryCoordinate position {static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)};
// load image
UIImage *assetImage = [UIImage imageNamed:@"pin"];
assert(assetImage.CGImage != NULL);
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(MLNPremultipliedImageFromCGImage(assetImage.CGImage));
// set symbol options
Interface::SymbolOptions options;
options.texture = interface.context.createTexture2D();
options.texture->setImage(image);
options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
const std::array<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 yspan = textureCoordinates[1][1] - textureCoordinates[0][1];
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.anchor = {0.5f, 0.95f};
options.angleDegrees = 45.0f;
options.scaleWithMap = true;
options.pitchWithMap = false;
interface.setSymbolOptions(options);
// add symbol
interface.addSymbol(position, textureCoordinates);
}
// add polylines using wide vectors using geographic coordinates
{
using namespace mbgl;
// add polyline with geographic coordinates
Interface::LineOptions options = {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/12.0f, /*color=*/{.0f, .0f, .0f, .5f} };
options.geometry.beginCap = style::LineCapType::Square;
options.geometry.endCap = style::LineCapType::Square;
options.geometry.joinType = style::LineJoinType::Bevel;
options.geometry.type = FeatureType::LineString;
interface.setLineOptions(options);
LineString<double> polyline_geo {
// San Francisco
{-122.38186800073211, 37.77466003457463},
{-122.3869373450997, 37.774352128895615},
{-122.38680767979824, 37.773294612880306},
{-122.38476465260224, 37.773350946288765},
{-122.38146223043374, 37.77194168964067},
{-122.6813560305925, 37.666084247570964},
{-122.26765538866474, 37.65037232584494},
{-122.42528413673159, 38.020443518012584},
// Seattle
{-122.34927775216809, 47.62050663596438},
// New York
{-74.04454331829972, 40.6892168305434},
};
interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector);
}
// add polylines using wide vectors in tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
Interface::LineOptions options
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/7.0f, /*color=*/{1.0f, 0, 0, .5f} };
options.geometry.beginCap = style::LineCapType::Round;
options.geometry.endCap = style::LineCapType::Round;
options.geometry.joinType = style::LineJoinType::Round;
// add polyline with tile coordinates
GeometryCollection polyline_tile{
{
// ring 1
{static_cast<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.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.0f), static_cast<int16_t>(extent* 0.5f)},
},
{
// ring 2
{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.25f), static_cast<int16_t>(extent* 0.45f)},
},
};
options.geometry.type = FeatureType::Polygon;
interface.setLineOptions(options);
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
}
// finish
interface.finish();
} }
void deinitialize() override { // add symbol
if (layer == nil) return; {
using namespace mbgl;
layerRef = layer; // set tile
layer = nil; interface.setTileID({11, 327, 789});
GeometryCoordinate position{static_cast<int16_t>(extent * 0.5f),
static_cast<int16_t>(extent * 0.5f)};
// load image
UIImage *assetImage = [UIImage imageNamed:@"pin"];
assert(assetImage.CGImage != NULL);
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(
MLNPremultipliedImageFromCGImage(assetImage.CGImage));
// set symbol options
Interface::SymbolOptions options;
options.texture = interface.context.createTexture2D();
options.texture->setImage(image);
options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
gfx::TextureWrapType::Clamp,
gfx::TextureWrapType::Clamp});
const std::array<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 yspan = textureCoordinates[1][1] - textureCoordinates[0][1];
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.anchor = {0.5f, 0.95f};
options.angleDegrees = 45.0f;
options.scaleWithMap = true;
options.pitchWithMap = false;
interface.setSymbolOptions(options);
// add symbol
interface.addSymbol(position, textureCoordinates);
} }
// add polylines using wide vectors using geographic coordinates
{
using namespace mbgl;
// add polyline with geographic coordinates
Interface::LineOptions options = {/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/12.0f,
/*color=*/{.0f, .0f, .0f, .5f}};
options.geometry.beginCap = style::LineCapType::Square;
options.geometry.endCap = style::LineCapType::Square;
options.geometry.joinType = style::LineJoinType::Bevel;
options.geometry.type = FeatureType::LineString;
interface.setLineOptions(options);
LineString<double> polyline_geo{
// San Francisco
{-122.38186800073211, 37.77466003457463},
{-122.3869373450997, 37.774352128895615},
{-122.38680767979824, 37.773294612880306},
{-122.38476465260224, 37.773350946288765},
{-122.38146223043374, 37.77194168964067},
{-122.6813560305925, 37.666084247570964},
{-122.26765538866474, 37.65037232584494},
{-122.42528413673159, 38.020443518012584},
// Seattle
{-122.34927775216809, 47.62050663596438},
// New York
{-74.04454331829972, 40.6892168305434},
};
interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector);
}
// add polylines using wide vectors in tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
Interface::LineOptions options{/*geometry=*/{},
/*blur=*/0.0f,
/*opacity=*/1.0f,
/*gapWidth=*/0.0f,
/*offset=*/0.0f,
/*width=*/7.0f,
/*color=*/{1.0f, 0, 0, .5f}};
options.geometry.beginCap = style::LineCapType::Round;
options.geometry.endCap = style::LineCapType::Round;
options.geometry.joinType = style::LineJoinType::Round;
// add polyline with tile coordinates
GeometryCollection polyline_tile{
{
// ring 1
{static_cast<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.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.0f), static_cast<int16_t>(extent * 0.5f)},
},
{
// ring 2
{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.25f), static_cast<int16_t>(extent * 0.45f)},
},
};
options.geometry.type = FeatureType::Polygon;
interface.setLineOptions(options);
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
}
// finish
interface.finish();
}
void deinitialize() override {
if (layer == nil) return;
layerRef = layer;
layer = nil;
}
private: private:
__weak ExampleCustomDrawableStyleLayer * layerRef; __weak ExampleCustomDrawableStyleLayer *layerRef;
ExampleCustomDrawableStyleLayer * layer = nil; ExampleCustomDrawableStyleLayer *layer = nil;
}; };

View File

@@ -2,29 +2,24 @@
@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);
} }
@end @end

View File

@@ -10,224 +10,208 @@
#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;
@interface PluginLayerExampleMetalRendering () { @interface PluginLayerExampleMetalRendering () {
// The render pipeline state // The render pipeline state
id<MTLRenderPipelineState> _pipelineState; id<MTLRenderPipelineState> _pipelineState;
id<MTLDepthStencilState> _depthStencilStateWithoutStencil; id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
// Properties
float _offsetX;
float _offsetY;
float _scale;
float _r, _g, _b, _a;
// Properties
float _offsetX;
float _offsetY;
float _scale;
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];
tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES;
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; // Define the paint properties that this layer implements and
tempResult.layerID = @"plugin-layer-metal-rendering"; // what types they are
tempResult.requiresPass3D = YES; tempResult.layerProperties = @[
// The scale property
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// Define the paint properties that this layer implements and // The fill color property
// what types they are [MLNPluginLayerProperty propertyWithName:@"fill-color"
tempResult.layerProperties = @[ propertyType:MLNPluginLayerPropertyTypeColor
// The scale property defaultValue:[UIColor blueColor]]
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// The fill color property ];
[MLNPluginLayerProperty propertyWithName:@"fill-color"
propertyType:MLNPluginLayerPropertyTypeColor
defaultValue:[UIColor blueColor]]
];
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;
id<MTLDevice> _device = resource.device;
id<MTLLibrary> library = [_device newLibraryWithSource:shaderSource options:nil error:&error];
NSAssert(library, @"Error compiling shaders: %@", error);
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"];
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"];
NSError *error = nil; // Configure a pipeline descriptor that is used to create a pipeline state.
id<MTLDevice> _device = resource.device; MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
id<MTLLibrary> library = [_device newLibraryWithSource:shaderSource options:nil error:&error]; pipelineStateDescriptor.label = @"Simple Pipeline";
NSAssert(library, @"Error compiling shaders: %@", error); pipelineStateDescriptor.vertexFunction = vertexFunction;
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"]; pipelineStateDescriptor.fragmentFunction = fragmentFunction;
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
// Configure a pipeline descriptor that is used to create a pipeline state. _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; error:&error];
pipelineStateDescriptor.label = @"Simple Pipeline"; NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor // Notice that we don't configure the stencilTest property, leaving stencil testing disabled
error:&error]; MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error); depthStencilDescriptor.depthCompareFunction =
MTLCompareFunctionAlways; // Or another value as needed
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled depthStencilDescriptor.depthWriteEnabled = NO;
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed
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) {
[self setupRendering:mapView];
}
if (_pipelineState == nil) { if (renderEncoder == nil) {
[self setupRendering:mapView]; return;
} }
if (renderEncoder == nil) { vector_uint2 _viewportSize;
return; _viewportSize.x = resource.mtkView.drawableSize.width;
} _viewportSize.y = resource.mtkView.drawableSize.height;
Vertex triangleVerticesWithColor[] = {
// 2D positions, RGBA colors
{{(250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
{{(-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
{{(0 + _offsetX) * _scale, (250 + _offsetY) * _scale}, {_r, _g, _b, _a}},
};
vector_uint2 _viewportSize; [renderEncoder setRenderPipelineState:_pipelineState];
_viewportSize.x = resource.mtkView.drawableSize.width; [renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
_viewportSize.y = resource.mtkView.drawableSize.height;
Vertex triangleVerticesWithColor[] = { // Pass in the parameter data.
// 2D positions, RGBA colors [renderEncoder setVertexBytes:triangleVerticesWithColor
{ { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } }, length:sizeof(triangleVerticesWithColor)
{ { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} }, atIndex:0];
{ { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } },
};
[renderEncoder setRenderPipelineState:_pipelineState]; [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
// Pass in the parameter data. // Draw the triangle.
[renderEncoder setVertexBytes:triangleVerticesWithColor [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
length:sizeof(triangleVerticesWithColor)
atIndex:0];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:1];
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
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
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"]; NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
if (offsetX) { if (offsetX) {
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue]; _offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
}
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
if (offsetY) {
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
}
// These are the paint properties and can be updated each frame or zoom level
// change/etc depending on if they are static or expression based/etc.
NSNumber *scale = [layerProperties objectForKey:@"scale"];
if (scale) {
if ([scale isKindOfClass:[NSNumber class]]) {
_scale = [scale floatValue];
} }
}
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"]; NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (offsetY) { if (fillColor) {
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue]; // NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","];
if ([components count] == 4) {
// Convert to float since they come in as 0..255
_r = [[components objectAtIndex:0] floatValue] / 255.0;
_g = [[components objectAtIndex:1] floatValue] / 255.0;
_b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue];
} }
}
// These are the paint properties and can be updated each frame or zoom level
// change/etc depending on if they are static or expression based/etc.
NSNumber *scale = [layerProperties objectForKey:@"scale"];
if (scale) {
if ([scale isKindOfClass:[NSNumber class]]) {
_scale = [scale floatValue];
}
}
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (fillColor) {
//NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","];
if ([components count] == 4) {
// Convert to float since they come in as 0..255
_r = [[components objectAtIndex:0] floatValue] / 255.0;
_g = [[components objectAtIndex:1] floatValue] / 255.0;
_b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue];
}
}
} }
@end @end

View File

@@ -9,59 +9,56 @@ namespace platform {
class Collator::Impl { 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 &&
[[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]]; [[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]];
} }
int compare(const std::string& lhs, const std::string& rhs) const { int compare(const std::string& lhs, const std::string& rhs) const {
NSString* nsLhs = @(lhs.c_str()); NSString* nsLhs = @(lhs.c_str());
NSString* nsRhs = @(rhs.c_str()); NSString* nsRhs = @(rhs.c_str());
// Limiting the compare range to the length of the LHS seems weird, but // Limiting the compare range to the length of the LHS seems weird, but
// experimentally we've checked that if LHS is a prefix of RHS compare returns -1 // experimentally we've checked that if LHS is a prefix of RHS compare returns -1
// https://developer.apple.com/documentation/foundation/nsstring/1414561-compare // https://developer.apple.com/documentation/foundation/nsstring/1414561-compare
NSRange compareRange = NSMakeRange(0, nsLhs.length); NSRange compareRange = NSMakeRange(0, nsLhs.length);
return static_cast<int>([nsLhs compare:nsRhs options:options range:compareRange locale:locale]); return static_cast<int>([nsLhs compare:nsRhs options:options range:compareRange locale:locale]);
} }
std::string resolvedLocale() const {
// Following documentation at:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9
// We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format
// may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47
// compliant. Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is
// used by
// `currentLocale`, we don't want to return the underscore.
return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
.UTF8String;
}
std::string resolvedLocale() const {
// Following documentation at:
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9
// We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format
// may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47 compliant.
// Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is used by
// `currentLocale`, we don't want to return the underscore.
return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"].UTF8String;
}
private: 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,422 +26,422 @@ 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_;
async.send(); async.send();
}
} }
}
void cancel() { void cancel() {
std::scoped_lock lock(mutex); std::scoped_lock lock(mutex);
cancelled = true; cancelled = true;
} }
private: 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();
if (task) { if (task) {
[task cancel]; [task cancel];
task = nil; task = nil;
}
} }
}
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 =
session = [NSURLSession sessionWithConfiguration:sessionConfig]; MLNNativeNetworkManager.sharedManager.sessionConfiguration;
session = [NSURLSession sessionWithConfiguration:sessionConfig];
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) { if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
userAgent = getUserAgent(); userAgent = getUserAgent();
} else { } else {
userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"]; userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
}
} }
}
void setResourceOptions(ResourceOptions options); void setResourceOptions(ResourceOptions options);
ResourceOptions getResourceOptions(); ResourceOptions getResourceOptions();
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;
}; };
void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) { 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];
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
appBundle.infoDictionary[@"CFBundleShortVersionString"]]]; stringWithFormat:@"%@/%@",
} else { appName.length
[userAgentComponents addObject:[NSProcessInfo processInfo].processName]; ? appName
: appBundle.infoDictionary[@"CFBundleIdentifier"],
appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
} else {
[userAgentComponents addObject:[NSProcessInfo processInfo].processName];
}
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle();
if (sdkBundle) {
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
if (versionString) {
[userAgentComponents
addObject:[NSString stringWithFormat:@"%@/%@", sdkBundle.infoDictionary[@"CFBundleName"],
versionString]];
} }
}
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle(); // Avoid %s here because it inserts hidden bidirectional markers on macOS when the system
if (sdkBundle) { // language is set to a right-to-left language.
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"]; [userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
if (versionString) { @(mbgl::version::revision)]];
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@",
sdkBundle.infoDictionary[@"CFBundleName"], versionString]];
}
}
// Avoid %s here because it inserts hidden bidirectional markers on macOS when the system NSString *systemName = @"Darwin";
// language is set to a right-to-left language.
[userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
@(mbgl::version::revision)]];
NSString *systemName = @"Darwin";
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
systemName = @"iOS"; systemName = @"iOS";
#elif TARGET_OS_MAC #elif TARGET_OS_MAC
systemName = @"macOS"; systemName = @"macOS";
#elif TARGET_OS_WATCH #elif TARGET_OS_WATCH
systemName = @"watchOS"; systemName = @"watchOS";
#elif TARGET_OS_TV #elif TARGET_OS_TV
systemName = @"tvOS"; systemName = @"tvOS";
#endif #endif
#if TARGET_OS_SIMULATOR #if TARGET_OS_SIMULATOR
systemName = [systemName stringByAppendingString:@" Simulator"]; systemName = [systemName stringByAppendingString:@" Simulator"];
#endif #endif
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) { }
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]]; if (systemVersion) {
} [userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
}
NSString *cpu = nil; NSString *cpu = nil;
#if TARGET_CPU_X86 #if TARGET_CPU_X86
cpu = @"x86"; cpu = @"x86";
#elif TARGET_CPU_X86_64 #elif TARGET_CPU_X86_64
cpu = @"x86_64"; cpu = @"x86_64";
#elif TARGET_CPU_ARM #elif TARGET_CPU_ARM
cpu = @"arm"; cpu = @"arm";
#elif TARGET_CPU_ARM64 #elif TARGET_CPU_ARM64
cpu = @"arm64"; cpu = @"arm64";
#endif #endif
if (cpu) { if (cpu) {
[userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]]; [userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]];
} }
return [userAgentComponents componentsJoinedByString:@" "]; return [userAgentComponents componentsJoinedByString:@" "];
} }
NSBundle *HTTPFileSource::Impl::getSDKBundle() const { NSBundle *HTTPFileSource::Impl::getSDKBundle() const {
NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]]; NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]];
if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the class is contained in the application bundle rather than the // For static frameworks, the class is contained in the application bundle rather than the
// framework bundle. // framework bundle.
bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath
stringByAppendingPathComponent:@"Mapbox.framework"]]; stringByAppendingPathComponent:@"Mapbox.framework"]];
} }
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
NSMutableArray *queryItems = [NSMutableArray array]; resolvingAgainstBaseURL:NO];
NSMutableArray *queryItems = [NSMutableArray array];
if (resource.usage == Resource::Usage::Offline) { if (resource.usage == Resource::Usage::Offline) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]]; [queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]];
}
if (components.queryItems) {
[queryItems addObjectsFromArray:components.queryItems];
}
components.queryItems = queryItems;
url = components.URL;
} }
if (components.queryItems) {
[queryItems addObjectsFromArray:components.queryItems];
}
components.queryItems = queryItems;
url = components.URL;
}
#endif #endif
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,
auto request = std::make_unique<HTTPRequest>(std::move(callback)); std::function<void(Response)> callback) {
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below. 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.
@autoreleasepool { @autoreleasepool {
NSURL *url = resourceURL(resource); NSURL *url = resourceURL(resource);
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath]; [MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath];
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) {
NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld",
static_cast<long long>(resource.dataRange->first),
static_cast<long long>(resource.dataRange->second)];
[req setValue:rangeHeader forHTTPHeaderField:@"Range"];
}
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
const bool isTile = resource.kind == mbgl::Resource::Kind::Tile;
if (isTile) {
[MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"];
}
__block NSURLSession *session = nil;
// Use the delegate's session if there is one, otherwise use the one that
// was created when this class was constructed.
MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager;
if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) {
session = [networkManager.delegate sessionForNetworkManager:networkManager];
}
if (!session) {
session = impl->session;
}
assert(session);
if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) {
req = [networkManager.delegate willSendRequest:req];
}
request->task = [session
dataTaskWithRequest:req
completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) {
session = nil;
if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNInternalNetworkResponse *networkResponse = [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error];
networkResponse = [networkManager.delegate didReceiveResponse:networkResponse];
data = networkResponse.data;
res = networkResponse.response;
error = networkResponse.error;
}
if (error && [error code] == NSURLErrorCancelled) {
[MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res];
return;
}
[MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res];
Response response;
using Error = Response::Error;
if (error) {
[MLNNativeNetworkManager.sharedManager errorLog:@"Requesting: %@ failed with error: %@", req.URL, error.debugDescription];
if (data) {
response.data =
std::make_shared<std::string>((const char*)[data bytes], [data length]);
}
switch ([error code]) {
case NSURLErrorBadServerResponse: // 5xx errors
response.error = std::make_unique<Error>(
Error::Reason::Server, [[error localizedDescription] UTF8String]);
break;
case NSURLErrorNetworkConnectionLost:
case NSURLErrorCannotFindHost:
case NSURLErrorCannotConnectToHost:
case NSURLErrorDNSLookupFailed:
case NSURLErrorNotConnectedToInternet:
case NSURLErrorInternationalRoamingOff:
case NSURLErrorCallIsActive:
case NSURLErrorDataNotAllowed:
case NSURLErrorTimedOut:
response.error = std::make_unique<Error>(
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
break;
default:
response.error = std::make_unique<Error>(
Error::Reason::Other, [[error localizedDescription] UTF8String]);
break;
}
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath, responseCode];
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
if (cache_control) {
const auto cc = http::CacheControl::parse([cache_control UTF8String]);
response.expires = cc.toTimePoint();
response.mustRevalidate = cc.mustRevalidate;
}
NSString *expires = [headers objectForKey:@"Expires"];
if (expires) {
response.expires = util::parseTimestamp([expires UTF8String]);
}
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
if (last_modified) {
response.modified = util::parseTimestamp([last_modified UTF8String]);
}
NSString *etag = [headers objectForKey:@"ETag"];
if (etag) {
response.etag = std::string([etag UTF8String]);
}
if (responseCode == 200 || responseCode == 206) {
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else if (responseCode == 204 || (responseCode == 404 && isTile)) {
response.noContent = true;
} else if (responseCode == 304) {
response.notModified = true;
} else if (responseCode == 404) {
response.error =
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (responseCode == 429) {
// Get the standard header
std::optional<std::string> retryAfter;
NSString *retryAfterHeader = headers[@"Retry-After"];
if (retryAfterHeader) {
retryAfter = std::string([retryAfterHeader UTF8String]);
}
// Fallback mapbox specific header
std::optional<std::string> xRateLimitReset;
NSString *xReset = headers[@"x-rate-limit-reset"];
if (xReset) {
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 =
std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } +
std::to_string(responseCode));
} else {
response.error =
std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } +
std::to_string(responseCode));
}
} else if ([url isFileURL]) {
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else {
// This should never happen.
response.error = std::make_unique<Error>(Error::Reason::Other,
"Response class is not NSHTTPURLResponse");
}
shared->notify(response);
}];
[request->task resume];
} }
return std::move(request); if (resource.dataRange) {
NSString *rangeHeader = [NSString
stringWithFormat:@"bytes=%lld-%lld", static_cast<long long>(resource.dataRange->first),
static_cast<long long>(resource.dataRange->second)];
[req setValue:rangeHeader forHTTPHeaderField:@"Range"];
}
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
const bool isTile = resource.kind == mbgl::Resource::Kind::Tile;
if (isTile) {
[MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"];
}
__block NSURLSession *session = nil;
// Use the delegate's session if there is one, otherwise use the one that
// was created when this class was constructed.
MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager;
if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) {
session = [networkManager.delegate sessionForNetworkManager:networkManager];
}
if (!session) {
session = impl->session;
}
assert(session);
if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) {
req = [networkManager.delegate willSendRequest:req];
}
request->task = [session
dataTaskWithRequest:req
completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
session = nil;
if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNInternalNetworkResponse *networkResponse =
[MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error];
networkResponse = [networkManager.delegate didReceiveResponse:networkResponse];
data = networkResponse.data;
res = networkResponse.response;
error = networkResponse.error;
}
if (error && [error code] == NSURLErrorCancelled) {
[MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res];
return;
}
[MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res];
Response response;
using Error = Response::Error;
if (error) {
[MLNNativeNetworkManager.sharedManager
errorLog:@"Requesting: %@ failed with error: %@", req.URL,
error.debugDescription];
if (data) {
response.data =
std::make_shared<std::string>((const char *)[data bytes], [data length]);
}
switch ([error code]) {
case NSURLErrorBadServerResponse: // 5xx errors
response.error = std::make_unique<Error>(
Error::Reason::Server, [[error localizedDescription] UTF8String]);
break;
case NSURLErrorNetworkConnectionLost:
case NSURLErrorCannotFindHost:
case NSURLErrorCannotConnectToHost:
case NSURLErrorDNSLookupFailed:
case NSURLErrorNotConnectedToInternet:
case NSURLErrorInternationalRoamingOff:
case NSURLErrorCallIsActive:
case NSURLErrorDataNotAllowed:
case NSURLErrorTimedOut:
response.error = std::make_unique<Error>(
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
break;
default:
response.error = std::make_unique<Error>(
Error::Reason::Other, [[error localizedDescription] UTF8String]);
break;
}
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
[MLNNativeNetworkManager.sharedManager
debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath,
responseCode];
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
if (cache_control) {
const auto cc = http::CacheControl::parse([cache_control UTF8String]);
response.expires = cc.toTimePoint();
response.mustRevalidate = cc.mustRevalidate;
}
NSString *expires = [headers objectForKey:@"Expires"];
if (expires) {
response.expires = util::parseTimestamp([expires UTF8String]);
}
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
if (last_modified) {
response.modified = util::parseTimestamp([last_modified UTF8String]);
}
NSString *etag = [headers objectForKey:@"ETag"];
if (etag) {
response.etag = std::string([etag UTF8String]);
}
if (responseCode == 200 || responseCode == 206) {
response.data =
std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else if (responseCode == 204 || (responseCode == 404 && isTile)) {
response.noContent = true;
} else if (responseCode == 304) {
response.notModified = true;
} else if (responseCode == 404) {
response.error =
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (responseCode == 429) {
// Get the standard header
std::optional<std::string> retryAfter;
NSString *retryAfterHeader = headers[@"Retry-After"];
if (retryAfterHeader) {
retryAfter = std::string([retryAfterHeader UTF8String]);
}
// Fallback mapbox specific header
std::optional<std::string> xRateLimitReset;
NSString *xReset = headers[@"x-rate-limit-reset"];
if (xReset) {
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 = std::make_unique<Error>(
Error::Reason::Server,
std::string{"HTTP status code "} + std::to_string(responseCode));
} else {
response.error =
std::make_unique<Error>(Error::Reason::Other, std::string{"HTTP status code "} +
std::to_string(responseCode));
}
} else if ([url isFileURL]) {
response.data =
std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else {
// This should never happen.
response.error = std::make_unique<Error>(Error::Reason::Other,
"Response class is not NSHTTPURLResponse");
}
shared->notify(response);
}];
[request->task resume];
}
return std::move(request);
} }
void HTTPFileSource::setResourceOptions(ResourceOptions options) { 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

@@ -12,85 +12,85 @@ using CGColorSpaceHandle = CFHandle<CGColorSpaceRef, CGColorSpaceRef, CGColorSpa
using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>; using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>;
CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src) { CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src) {
// We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking // We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking
// over ownership of the memory. // over ownership of the memory.
CGDataProviderHandle provider(CGDataProviderCreateWithData( CGDataProviderHandle provider(CGDataProviderCreateWithData(
NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) { NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) {
delete[] reinterpret_cast<const decltype(src.data)::element_type*>(data); delete[] reinterpret_cast<const decltype(src.data)::element_type*>(data);
})); }));
if (!provider) { if (!provider) {
return nil; return nil;
} }
// If we successfully created the provider, it will take over management of the memory segment. // If we successfully created the provider, it will take over management of the memory segment.
src.data.release(); src.data.release();
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) { if (!colorSpace) {
return nil; return nil;
} }
constexpr const size_t bitsPerComponent = 8; constexpr const size_t bitsPerComponent = 8;
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) {
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
} }
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");
} }
CGContextSetBlendMode(*context, kCGBlendModeCopy); CGContextSetBlendMode(*context, kCGBlendModeCopy);
CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src); CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src);
return image; return image;
} }
namespace mbgl { namespace mbgl {
PremultipliedImage decodeImage(const std::string& source) { PremultipliedImage decodeImage(const std::string& source) {
CFDataHandle data(CFDataCreateWithBytesNoCopy( CFDataHandle data(CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault, reinterpret_cast<const unsigned char*>(source.data()), source.size(), kCFAllocatorDefault, reinterpret_cast<const unsigned char*>(source.data()), source.size(),
kCFAllocatorNull)); kCFAllocatorNull));
if (!data) { if (!data) {
throw std::runtime_error("CFDataCreateWithBytesNoCopy failed"); throw std::runtime_error("CFDataCreateWithBytesNoCopy failed");
} }
CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL)); CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL));
if (!imageSource) { if (!imageSource) {
throw std::runtime_error("CGImageSourceCreateWithData failed"); throw std::runtime_error("CGImageSourceCreateWithData failed");
} }
CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL)); CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL));
if (!image) { if (!image) {
throw std::runtime_error("CGImageSourceCreateImageAtIndex failed"); throw std::runtime_error("CGImageSourceCreateImageAtIndex failed");
} }
return MLNPremultipliedImageFromCGImage(*image); return MLNPremultipliedImageFromCGImage(*image);
} }
} // namespace mbgl } // namespace mbgl

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"
@@ -63,114 +63,117 @@ using CTLineRefHandle = CFHandle<CTLineRef, CFTypeRef, CFRelease>;
*/ */
class LocalGlyphRasterizer::Impl { class LocalGlyphRasterizer::Impl {
public: public:
/** /**
Creates a new rasterizer with the given font names as a fallback. Creates a new rasterizer with the given font names as a fallback.
The fallback font names can also be specified in the style as a font stack The fallback font names can also be specified in the style as a font stack
or in the `MLNIdeographicFontFamilyName` key of or in the `MLNIdeographicFontFamilyName` key of
`NSUserDefaults.standardUserDefaults`. The font stack takes precedence, `NSUserDefaults.standardUserDefaults`. The font stack takes precedence,
followed by the `MLNIdeographicFontFamilyName` user default, then finally followed by the `MLNIdeographicFontFamilyName` user default, then finally
the `fallbackFontNames_` parameter as a last resort. the `fallbackFontNames_` parameter as a last resort.
@param fallbackFontNames_ A list of font names, one per line. Each font @param fallbackFontNames_ A list of font names, one per line. Each font
name can be the PostScript name or display name of a specific font face name can be the PostScript name or display name of a specific font face
or a font family name. Set this parameter to `nullptr` to disable local or a font family name. Set this parameter to `nullptr` to disable local
glyph rasterization globally. The system font is a good default value to 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"]];
}
}
/**
Returns whether local glyph rasterization is enabled globally.
The developer can disable local glyph rasterization by specifying no
fallback font names.
*/
bool isEnabled() { return fallbackFontNames; }
/**
Creates a font descriptor representing the given font stack and any global
fallback fonts.
@param fontStack The font stack that takes precedence.
@returns A font descriptor representing the first font in the font stack
with a cascade list representing the rest of the fonts in the font stack
and any fallback fonts. The font descriptor is not cached.
@post The caller is responsible for releasing the font descriptor.
*/
CTFontDescriptorRef createFontDescriptor(const FontStack &fontStack) {
NSMutableArray *fontNames =
[NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count];
for (auto &fontName : fontStack) {
// Per the MapLibre Style Spec, the text-font property comes
// with these last resort fonts by default, but they shouldn’t take
// precedence over any application or system fallback font that may
// be more appropriate to the current device.
if (fontName != util::LAST_RESORT_ALPHABETIC_FONT &&
fontName != util::LAST_RESORT_PAN_UNICODE_FONT) {
[fontNames addObject:@(fontName.c_str())];
}
}
[fontNames addObjectsFromArray:fallbackFontNames];
if (!fontNames.count) {
NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
};
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
} }
/** // Apply the first font name to the returned font descriptor; apply the
Returns whether local glyph rasterization is enabled globally. // rest of the font names to the cascade list.
CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject;
CFMutableArrayRefHandle fallbackDescriptors(
CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks));
for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) {
NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
// This attribute is technically supposed to be a font’s
// PostScript name, but Core Text will fall back to matching
// font display names and font family names.
(NSString *)kCTFontNameAttribute : name,
};
The developer can disable local glyph rasterization by specifying no CTFontDescriptorRefHandle descriptor(
fallback font names. CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
*/ CFArrayAppendValue(*fallbackDescriptors, *descriptor);
bool isEnabled() { return fallbackFontNames; }
/**
Creates a font descriptor representing the given font stack and any global
fallback fonts.
@param fontStack The font stack that takes precedence.
@returns A font descriptor representing the first font in the font stack
with a cascade list representing the rest of the fonts in the font stack
and any fallback fonts. The font descriptor is not cached.
@post The caller is responsible for releasing the font descriptor.
*/
CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack) {
NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count];
for (auto& fontName : fontStack) {
// Per the MapLibre Style Spec, the text-font property comes
// with these last resort fonts by default, but they shouldn’t take
// precedence over any application or system fallback font that may
// be more appropriate to the current device.
if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) {
[fontNames addObject:@(fontName.c_str())];
}
}
[fontNames addObjectsFromArray:fallbackFontNames];
if (!fontNames.count) {
NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM),
};
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
}
// Apply the first font name to the returned font descriptor; apply the
// rest of the font names to the cascade list.
CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject;
CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks));
for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) {
NSDictionary *fontAttributes = @{
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM),
// This attribute is technically supposed to be a font’s
// PostScript name, but Core Text will fall back to matching
// font display names and font family names.
(NSString *)kCTFontNameAttribute: name,
};
CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
CFArrayAppendValue(*fallbackDescriptors, *descriptor);
}
CFStringRef keys[] = {
kCTFontSizeAttribute,
kCTFontNameAttribute,
kCTFontCascadeListAttribute,
};
CFTypeRef values[] = {
(__bridge CFNumberRef)@(util::ONE_EM),
mainFontName,
*fallbackDescriptors,
};
CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
return CTFontDescriptorCreateWithAttributes(*attributes);
} }
CFStringRef keys[] = {
kCTFontSizeAttribute,
kCTFontNameAttribute,
kCTFontCascadeListAttribute,
};
CFTypeRef values[] = {
(__bridge CFNumberRef) @(util::ONE_EM),
mainFontName,
*fallbackDescriptors,
};
CFDictionaryRefHandle attributes(
CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
return CTFontDescriptorCreateWithAttributes(*attributes);
}
private: 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,11 +181,11 @@ 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
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled(); return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled();
#endif #endif
} }
@@ -197,91 +200,90 @@ 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) {
if (!string) { CFStringRefHandle string(
throw std::runtime_error("Unable to create string from codepoint"); CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar *>(&glyphID), 1));
} if (!string) {
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(
if (!boldFont) { CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait));
CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font)); if (!boldFont) {
NSString* familyName = (__bridge NSString *)(*familyNameHandle); CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font));
std::string stdFamilyName(familyName.UTF8String); NSString *familyName = (__bridge NSString *)(*familyNameHandle);
Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName); std::string stdFamilyName(familyName.UTF8String);
} Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName);
}
CTFontRef drawFont = isBold && boldFont ? *boldFont : font; CTFontRef drawFont = isBold && boldFont ? *boldFont : font;
CFStringRef keys[] = { kCTFontAttributeName }; 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(
if (!attrString) { CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
throw std::runtime_error("Unable to create attributed string"); if (!attrString) {
} throw std::runtime_error("Unable to create attributed string");
CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString)); }
if (!line) { CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString));
throw std::runtime_error("Unable to create line from attributed string"); if (!line) {
} throw std::runtime_error("Unable to create line from attributed string");
}
Size size(35, 35); Size size(35, 35);
metrics.width = size.width; metrics.width = size.width;
metrics.height = size.height; metrics.height = size.height;
PremultipliedImage rgbaBitmap(size); PremultipliedImage rgbaBitmap(size);
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB()); CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) { if (!colorSpace) {
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
} }
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, bitmapInfo));
size.height, if (!context) {
bitsPerComponent, throw std::runtime_error("CGBitmapContextCreate failed");
bytesPerRow, }
*colorSpace,
bitmapInfo));
if (!context) {
throw std::runtime_error("CGBitmapContextCreate failed");
}
CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line); CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line);
CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0); CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0);
CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun)); CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun));
std::vector<CGSize> advances(wholeRunRange.length); std::vector<CGSize> advances(wholeRunRange.length);
CTRunGetAdvances(glyphRun, wholeRunRange, advances.data()); CTRunGetAdvances(glyphRun, wholeRunRange, advances.data());
metrics.advance = std::round(advances[0].width); metrics.advance = std::round(advances[0].width);
// Mimic glyph PBF metrics. // Mimic glyph PBF metrics.
metrics.left = Glyph::borderSize; metrics.left = Glyph::borderSize;
metrics.top = -Glyph::borderSize; metrics.top = -Glyph::borderSize;
// Move the text upward to avoid clipping off descenders. // Move the text upward to avoid clipping off descenders.
CGFloat descent; CGFloat descent;
CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL); CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL);
CGContextSetTextPosition(*context, 0.0, descent); CGContextSetTextPosition(*context, 0.0, descent);
CTLineDraw(*line, *context); CTLineDraw(*line, *context);
return rgbaBitmap; return rgbaBitmap;
} }
/** /**
@@ -294,34 +296,36 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics
@returns A glyph representation of the given codepoint with its bitmap data and @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));
if (!font) { if (!font) {
return manufacturedGlyph;
}
manufacturedGlyph.id = glyphID;
BOOL isBold = NO;
// Only check the first font name to detect if the user prefers using bold
if (!fontStack.empty()) {
std::string lowercaseFont = platform::lowercase(fontStack.front());
if (lowercaseFont.find("bold") != std::string::npos && lowercaseFont.find("semibold") == std::string::npos) {
isBold = YES;
}
}
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
// Copy alpha values from RGBA bitmap into the AlphaImage output
manufacturedGlyph.bitmap = AlphaImage(size);
for (uint32_t i = 0; i < size.width * size.height; i++) {
manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
}
return manufacturedGlyph; return manufacturedGlyph;
}
manufacturedGlyph.id = glyphID;
BOOL isBold = NO;
// Only check the first font name to detect if the user prefers using bold
if (!fontStack.empty()) {
std::string lowercaseFont = platform::lowercase(fontStack.front());
if (lowercaseFont.find("bold") != std::string::npos &&
lowercaseFont.find("semibold") == std::string::npos) {
isBold = YES;
}
}
PremultipliedImage rgbaBitmap =
drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
// Copy alpha values from RGBA bitmap into the AlphaImage output
manufacturedGlyph.bitmap = AlphaImage(size);
for (uint32_t i = 0; i < size.width * size.height; i++) {
manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
}
return manufacturedGlyph;
} }
} // namespace mbgl } // namespace mbgl

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()
NSLog(@"[%s] %@", Enum<EventSeverity>::toString(severity), message); encoding:NSUTF8StringEncoding];
} 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>
@@ -10,43 +10,41 @@ namespace mbgl {
namespace platform { namespace platform {
std::string getCurrentThreadName() { std::string getCurrentThreadName() {
char name[32] = "unknown"; char name[32] = "unknown";
pthread_getname_np(pthread_self(), name, sizeof(name)); pthread_getname_np(pthread_self(), name, sizeof(name));
return name; return name;
} }
void setCurrentThreadName(const std::string& name) { void setCurrentThreadName(const std::string& name) {
std::string qualifiedName = "org.maplibre.mbgl." + name; std::string qualifiedName = "org.maplibre.mbgl." + name;
pthread_setname_np(qualifiedName.c_str()); pthread_setname_np(qualifiedName.c_str());
} }
void makeThreadLowPriority() { void makeThreadLowPriority() {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
} }
void setCurrentThreadPriority(double priority) { void setCurrentThreadPriority(double priority) {
if (priority > 1.0 || priority < 0.0) { if (priority > 1.0 || priority < 0.0) {
Log::Warning(Event::General, "Invalid thread priority was provided"); Log::Warning(Event::General, "Invalid thread priority was provided");
return; return;
} }
if (priority < 0.25) { if (priority < 0.25) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceBackground; [NSThread currentThread].qualityOfService = NSQualityOfServiceBackground;
} else if (priority < 0.5) { } else if (priority < 0.5) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility; [NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
} else if (priority < 0.75) { } else if (priority < 0.75) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated; [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated;
} else { } else {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive; [NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive;
} }
} }
void attachThread() { void attachThread() {}
}
void detachThread() { void detachThread() {}
}
} } // namespace platform
} } // namespace mbgl

View File

@@ -7,24 +7,24 @@ 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
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
// Create a new formatter each time to avoid state pollution between calls numberFormatter.locale =
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil; if (currency.empty()) {
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil; numberFormatter.minimumFractionDigits = minFractionDigits;
numberFormatter.maximumFractionDigits = maxFractionDigits;
if (currency.empty()) { numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
numberFormatter.minimumFractionDigits = minFractionDigits; } else {
numberFormatter.maximumFractionDigits = maxFractionDigits; numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; }
} else { NSString* formatted = [numberFormatter stringFromNumber:@(number)];
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; std::string result = std::string([formatted UTF8String]);
} return result;
NSString *formatted = [numberFormatter stringFromNumber:@(number)];
std::string result = std::string([formatted UTF8String]);
return result;
} }
} // namespace platform } // namespace platform
} // namespace mbgl } // namespace mbgl

View File

@@ -6,26 +6,26 @@ namespace mbgl {
namespace platform { namespace platform {
std::string uppercase(const std::string &string) { std::string uppercase(const std::string &string) {
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size() length:string.size()
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;
} }
std::string lowercase(const std::string &string) { std::string lowercase(const std::string &string) {
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data()) NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size() length:string.size()
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;
} }
} // namespace platform } // namespace platform
} // namespace mbgl } // namespace mbgl

View File

@@ -2,61 +2,62 @@
#import <mbgl/util/action_journal_options.hpp> #import <mbgl/util/action_journal_options.hpp>
@implementation MLNActionJournalOptions { @implementation MLNActionJournalOptions {
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal; std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
} }
- (instancetype)init { - (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
_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;
} }
- (mbgl::util::ActionJournalOptions)getCoreOptions { - (mbgl::util::ActionJournalOptions)getCoreOptions {
return *_actionJournalOptionsInternal; return *_actionJournalOptionsInternal;
} }
- (void)setEnabled:(BOOL)value { - (void)setEnabled:(BOOL)value {
_actionJournalOptionsInternal->enable(value); _actionJournalOptionsInternal->enable(value);
} }
- (BOOL)enabled { - (BOOL)enabled {
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()];
} }
- (void)setLogFileSize:(NSInteger)value { - (void)setLogFileSize:(NSInteger)value {
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value)); _actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
} }
- (NSInteger)logFileSize { - (NSInteger)logFileSize {
return _actionJournalOptionsInternal->logFileSize(); return _actionJournalOptionsInternal->logFileSize();
} }
- (void)setLogFileCount:(NSInteger)value { - (void)setLogFileCount:(NSInteger)value {
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value)); _actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
} }
- (NSInteger)logFileCount { - (NSInteger)logFileCount {
return _actionJournalOptionsInternal->logFileCount(); return _actionJournalOptionsInternal->logFileCount();
} }
- (void)setRenderingStatsReportInterval:(NSInteger)value { - (void)setRenderingStatsReportInterval:(NSInteger)value {
_actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value)); _actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value));
} }
- (NSInteger)renderingStatsReportInterval { - (NSInteger)renderingStatsReportInterval {
return _actionJournalOptionsInternal->renderingStatsReportInterval(); return _actionJournalOptionsInternal->renderingStatsReportInterval();
} }
@end @end

View File

@@ -1,186 +1,199 @@
#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
if (!htmlString) { fontSize:(CGFloat)fontSize
return @[]; linkColor:(nullable MLNColor *)linkColor {
} if (!htmlString) {
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];
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) { if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) {
sizeRule = @"font: -webkit-mini-control"; sizeRule = @"font: -webkit-mini-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) { } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) {
sizeRule = @"font: -webkit-small-control"; sizeRule = @"font: -webkit-small-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) { } else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) {
sizeRule = @"font: -webkit-control"; sizeRule = @"font: -webkit-control";
}
#endif
[css appendFormat:@"html { %@ }", sizeRule];
} }
if (linkColor) { #endif
CGFloat red; [css appendFormat:@"html { %@ }", sizeRule];
CGFloat green; }
CGFloat blue; if (linkColor) {
CGFloat alpha; CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
// CSS uses the sRGB color space. // CSS uses the sRGB color space.
if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) { if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) {
linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
} else { } else {
linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]]; linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
}
#endif
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
[css appendFormat:
@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }",
red * 100, green * 100, blue * 100, alpha];
} }
NSString *styledHTML = [NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString]; #endif
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding]; [linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
[css appendFormat:@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", red * 100, green * 100,
blue * 100, alpha];
}
NSString *styledHTML =
[NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
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.
attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData // https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata
options:options attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
documentAttributes:nil options:options
error:NULL]; documentAttributes:nil
}; error:NULL];
};
if (![[NSThread currentThread] isMainThread]) { if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), initialization); dispatch_sync(dispatch_get_main_queue(), initialization);
} else { } else {
initialization(); initialization();
} }
#else #else
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData NSMutableAttributedString *attributedString =
options:options [[NSMutableAttributedString alloc] initWithHTML:htmlData
documentAttributes:nil]; options:options
documentAttributes:nil];
#endif #endif
NSMutableArray *infos = [NSMutableArray array]; NSMutableArray *infos = [NSMutableArray array];
[attributedString enumerateAttribute:NSLinkAttributeName [attributedString
inRange:attributedString.mgl_wholeRange enumerateAttribute:NSLinkAttributeName
options:0 inRange:attributedString.mgl_wholeRange
usingBlock: options:0
^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
MLNCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL."); MLNCAssert(!value || [value isKindOfClass:[NSURL class]],
@"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
atIndex:range.location atIndex:range.location
effectiveRange:NULL]; effectiveRange:NULL];
BOOL isFeedbackLink = NO; BOOL isFeedbackLink = NO;
if ([strokeWidth floatValue] > 100) { if ([strokeWidth floatValue] > 100) {
isFeedbackLink = YES; isFeedbackLink = YES;
[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).
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]]; NSMutableCharacterSet *charset =
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range] [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
mgl_attributedStringByTrimmingCharactersInSet:charset]; [charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
mgl_attributedStringByTrimmingCharactersInSet:charset];
// Omit strings that are empty after cleaning. // Omit strings that are empty after cleaning.
if (!title.length) { if (!title.length) {
return; return;
} }
// 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
info.feedbackLink = isFeedbackLink; URL:value];
[infos addObject:info]; info.feedbackLink = isFeedbackLink;
}]; [infos addObject:info];
return infos; }];
return infos;
} }
+ (NSAttributedString *)attributedStringForAttributionInfos:(NSArray<MLNAttributionInfo *> *)attributionInfos { + (NSAttributedString *)attributedStringForAttributionInfos:
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count]; (NSArray<MLNAttributionInfo *> *)attributionInfos {
for (MLNAttributionInfo *info in attributionInfos) { NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
NSMutableAttributedString *title = info.title.mutableCopy; for (MLNAttributionInfo *info in attributionInfos) {
if (info.URL) { NSMutableAttributedString *title = info.title.mutableCopy;
[title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange]; if (info.URL) {
} [title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange];
[titles addObject:title];
} }
return [titles mgl_attributedComponentsJoinedByString:@" "]; [titles addObject:title];
}
return [titles mgl_attributedComponentsJoinedByString:@" "];
} }
- (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL { - (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL {
if (self = [super init]) { if (self = [super init]) {
_title = title; _title = title;
_URL = URL; _URL = URL;
} }
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 info.feedbackLink = _feedbackLink;
URL:_URL];
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 =
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])]; [[NSMutableAttributedString alloc] initWithAttributedString:self.title];
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort); BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) { if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap [title.mutableString replaceOccurrencesOfString:@"OpenStreetMap"
options:NSCaseInsensitiveSearch withString:isAbbreviated ? OSM : openStreetMap
range:NSMakeRange(0, [title.mutableString length])]; options:NSCaseInsensitiveSearch
} range:NSMakeRange(0, [title.mutableString length])];
}
return title; return title;
} }
- (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 {
return self.title.hash + self.URL.hash; return self.title.hash + self.URL.hash;
} }
/** /**
@@ -192,15 +205,15 @@
if there is no overlap. if there is no overlap.
*/ */
- (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo { - (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo {
NSString *title = self.title.string; NSString *title = self.title.string;
NSString *otherTitle = otherInfo.title.string; NSString *otherTitle = otherInfo.title.string;
if ([title containsString:otherTitle]) { if ([title containsString:otherTitle]) {
return NSOrderedDescending; return NSOrderedDescending;
} }
if ([otherTitle containsString:title]) { if ([otherTitle containsString:title]) {
return NSOrderedAscending; return NSOrderedAscending;
} }
return NSOrderedSame; return NSOrderedSame;
} }
@end @end
@@ -208,43 +221,44 @@
@implementation NSMutableArray (MLNAttributionInfoAdditions) @implementation NSMutableArray (MLNAttributionInfoAdditions)
- (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,
switch ([info subsetCompare:existingInfo]) { BOOL *_Nonnull stop) {
case NSOrderedDescending: switch ([info subsetCompare:existingInfo]) {
// The existing info object is a subset of the one we’re adding. case NSOrderedDescending:
// Replace the existing object the first time we find a subset; // The existing info object is a subset of the one we’re adding.
// remove the existing object every time after that. // Replace the existing object the first time we find a subset;
if (didInsertInfo) { // remove the existing object every time after that.
[self removeObjectAtIndex:idx]; if (didInsertInfo) {
} else { [self removeObjectAtIndex:idx];
[self replaceObjectAtIndex:idx withObject:info]; } else {
didInsertInfo = YES; [self replaceObjectAtIndex:idx withObject:info];
} didInsertInfo = YES;
break;
case NSOrderedAscending:
// The info object we’re adding is a subset of the existing one.
// Don’t add the object and stop looking.
shouldAddInfo = NO;
*stop = YES;
break;
default:
break;
} }
}]; break;
if (shouldAddInfo && !didInsertInfo) {
// No overlapping infos were found, so append the info object. case NSOrderedAscending:
[self addObject:info]; // The info object we’re adding is a subset of the existing one.
// Don’t add the object and stop looking.
shouldAddInfo = NO;
*stop = YES;
break;
default:
break;
} }
}];
if (shouldAddInfo && !didInsertInfo) {
// No overlapping infos were found, so append the info object.
[self addObject:info];
}
} }
- (void)growArrayByAddingAttributionInfosFromArray:(NSArray<MLNAttributionInfo *> *)infos { - (void)growArrayByAddingAttributionInfosFromArray:(NSArray<MLNAttributionInfo *> *)infos {
for (MLNAttributionInfo *info in infos) { for (MLNAttributionInfo *info in infos) {
[self growArrayByAddingAttributionInfo:info]; [self growArrayByAddingAttributionInfo:info];
} }
} }
@end @end

View File

@@ -8,14 +8,14 @@
device:(id<MTLDevice>)device device:(id<MTLDevice>)device
renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
commandBuffer:(id<MTLCommandBuffer>)commandBuffer { commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
self = [super init]; self = [super init];
if (self) { if (self) {
_mtkView = mtkView; _mtkView = mtkView;
_device = device; _device = device;
_renderPassDescriptor = renderPassDescriptor; _renderPassDescriptor = renderPassDescriptor;
_commandBuffer = commandBuffer; _commandBuffer = commandBuffer;
} }
return self; return self;
} }
@end @end
@@ -27,8 +27,8 @@
@implementation MLNBackendResource @implementation MLNBackendResource
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
return self; return self;
} }
@end @end

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,65 +13,68 @@
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(
mbgl::style::CustomGeometrySource::Options sourceOptions; NSDictionary<MLNShapeSourceOption, id> *options) {
mbgl::style::CustomGeometrySource::Options sourceOptions;
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."]; format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.min = value.integerValue;
} }
sourceOptions.zoomRange.min = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.max = value.integerValue;
} }
sourceOptions.zoomRange.max = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
sourceOptions.tileOptions.buffer = value.integerValue;
} }
sourceOptions.tileOptions.buffer = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
sourceOptions.tileOptions.tolerance = value.doubleValue;
} }
sourceOptions.tileOptions.tolerance = value.doubleValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) { if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."]; format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.wrap = value.boolValue;
} }
sourceOptions.tileOptions.wrap = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) { if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."]; format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.clip = value.boolValue;
} }
sourceOptions.tileOptions.clip = value.boolValue;
}
return sourceOptions; return sourceOptions;
} }
@interface MLNComputedShapeSource () { @interface MLNComputedShapeSource () {
std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource; std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource;
} }
@property (nonatomic, readwrite) NSDictionary *options; @property (nonatomic, readwrite) NSDictionary *options;
@@ -90,164 +93,181 @@ 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
self = [super init]; tile:(const mbgl::CanonicalTileID &)tileID {
_z = tileID.z; self = [super init];
_x = tileID.x; _z = tileID.z;
_y = tileID.y; _x = tileID.x;
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; _y = tileID.y;
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
_dataSource = source.dataSource; _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
mbgl::style::CustomGeometrySource *rawSource = static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource); _dataSource = source.dataSource;
_rawSource = rawSource; mbgl::style::CustomGeometrySource *rawSource =
return self; static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
_rawSource = rawSource;
return self;
} }
- (void)main { - (void)main {
if ([self isCancelled]) { if ([self isCancelled]) {
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 } else {
zoomLevel:self.z]; mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
} else { mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); data =
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); [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, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead." @"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once."); @"This will be logged only once.");
}); });
} }
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>(); mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject); featureCollection.push_back(geoJsonObject);
}
const auto geojson = mbgl::GeoJSON{featureCollection};
// Note: potential race condition with `cancel`
if(![self isCancelled]) {
if (auto *rawSource = self.rawSource) {
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
}
}
} }
const auto geojson = mbgl::GeoJSON{featureCollection};
// Note: potential race condition with `cancel`
if (![self isCancelled]) {
if (auto *rawSource = self.rawSource) {
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
}
}
}
} }
- (void)cancel { - (void)cancel {
[super cancel]; [super cancel];
self.rawSource = NULL; self.rawSource = NULL;
} }
@end @end
@implementation MLNComputedShapeSource @implementation MLNComputedShapeSource
- (instancetype)initWithIdentifier:(NSString *)identifier options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init]; options:(NSDictionary<MLNShapeSourceOption, id> *)options {
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier]; NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
requestQueue.qualityOfService = NSQualityOfServiceUtility; requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
requestQueue.maxConcurrentOperationCount = 4; requestQueue.qualityOfService = NSQualityOfServiceUtility;
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
[requestQueue addOperation:operation]; tile:tileID];
}; [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];
} }
}
};
auto source = std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
if (self = [super initWithPendingSource:std::move(source)]) {
_requestQueue = requestQueue;
} }
return self; };
auto source =
std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
if (self = [super initWithPendingSource:std::move(source)]) {
_requestQueue = requestQueue;
}
return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
if (self = [self initWithIdentifier:identifier options:options]) { dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource
[self setDataSource:dataSource]; options:(NSDictionary<MLNShapeSourceOption, id> *)options {
} if (self = [self initWithIdentifier:identifier options:options]) {
return self; [self setDataSource:dataSource];
}
return self;
} }
- (void)dealloc { - (void)dealloc {
[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
MLNAssertStyleSourceIsValid(); inTileAtX:(NSUInteger)x
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); y:(NSUInteger)y
mbgl::FeatureCollection featureCollection; zoomLevel:(NSUInteger)zoomLevel {
featureCollection.reserve(features.count); MLNAssertStyleSourceIsValid();
for (MLNShape <MLNFeature> * feature in features) { mbgl::CanonicalTileID tileID =
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>(); mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
featureCollection.push_back(geoJsonObject); mbgl::FeatureCollection featureCollection;
if ([feature isMemberOfClass:[MLNShapeCollection class]]) { featureCollection.reserve(features.count);
static dispatch_once_t onceToken; for (MLNShape<MLNFeature> *feature in features) {
dispatch_once(&onceToken, ^{ mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." featureCollection.push_back(geoJsonObject);
@"Use MLNShapeCollectionFeature to retain attributes instead." if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
@"This will be logged only once."); static dispatch_once_t onceToken;
}); dispatch_once(&onceToken, ^{
} NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
} }
const auto geojson = mbgl::GeoJSON{featureCollection}; }
static_cast<mbgl::style::CustomGeometrySource *>(self.rawSource)->setTileData(tileID, geojson); const auto geojson = mbgl::GeoJSON{featureCollection};
static_cast<mbgl::style::CustomGeometrySource *>(self.rawSource)->setTileData(tileID, geojson);
} }
- (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
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"]; raise:MLNInvalidDatasourceException
} else if (self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) { format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
[NSException raise:MLNInvalidDatasourceException } else if (self.dataSourceImplementsFeaturesForBounds &&
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"]; self.dataSourceImplementsFeaturesForTile) {
} [NSException raise:MLNInvalidDatasourceException
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
}
_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,12 +20,12 @@
@implementation MLNCustomDrawableStyleLayer @implementation MLNCustomDrawableStyleLayer
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { - (instancetype)initWithRawLayer:(mbgl::style::Layer*)rawLayer {
return [super initWithRawLayer:rawLayer]; return [super initWithRawLayer:rawLayer];
} }
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer { - (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
return [super initWithPendingLayer:std::move(pendingLayer)]; return [super initWithPendingLayer:std::move(pendingLayer)];
} }
@end @end
@@ -33,7 +33,7 @@
namespace mbgl { namespace mbgl {
MLNStyleLayer* CustomDrawableStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) { MLNStyleLayer* CustomDrawableStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer]; return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer];
} }
} // namespace mbgl } // namespace mbgl

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>
@@ -28,44 +28,42 @@ class MLNCustomLayerHost;
@implementation MLNCustomStyleLayer @implementation MLNCustomStyleLayer
- (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)];
} }
- (mbgl::style::CustomLayer *)rawLayer { - (mbgl::style::CustomLayer *)rawLayer {
return (mbgl::style::CustomLayer *)super.rawLayer; return (mbgl::style::CustomLayer *)super.rawLayer;
} }
- (MLNMapView *)mapView { - (MLNMapView *)mapView {
if ([self.style.stylable isKindOfClass:[MLNMapView class]]) { if ([self.style.stylable isKindOfClass:[MLNMapView class]]) {
return (MLNMapView *)self.style.stylable; return (MLNMapView *)self.style.stylable;
} }
return nil; return nil;
} }
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
- (EAGLContext *)context { - (EAGLContext *)context {
return self.mapView.context; return self.mapView.context;
} }
#else #else
- (CGLContextObj)context { - (CGLContextObj)context {
return self.mapView.context; return self.mapView.context;
} }
#endif #endif
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer { - (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
self.style = style; self.style = style;
self.style.customLayers[self.identifier] = self; self.style.customLayers[self.identifier] = self;
[super addToStyle:style belowLayer:otherLayer]; [super addToStyle:style belowLayer:otherLayer];
} }
- (void)removeFromStyle:(MLNStyle *)style { - (void)removeFromStyle:(MLNStyle *)style {
[super removeFromStyle:style]; [super removeFromStyle:style];
self.style.customLayers[self.identifier] = nil; self.style.customLayers[self.identifier] = nil;
self.style = nil; self.style = nil;
} }
- (void)didMoveToMapView:(MLNMapView *)mapView { - (void)didMoveToMapView:(MLNMapView *)mapView {
@@ -78,74 +76,75 @@ class MLNCustomLayerHost;
} }
- (void)setNeedsDisplay { - (void)setNeedsDisplay {
[self.mapView setNeedsRerender]; [self.mapView setNeedsRerender];
} }
@end @end
class MLNCustomLayerHost : public mbgl::style::CustomLayerHost { class MLNCustomLayerHost : public mbgl::style::CustomLayerHost {
public: public:
MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) { MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) {
layerRef = styleLayer; layerRef = styleLayer;
layer = nil; layer = nil;
}
void initialize() {
if (layerRef == nil)
return;
else if (layer == nil)
layer = layerRef;
if (layer.mapView) {
[layer didMoveToMapView:layer.mapView];
} }
}
void initialize() { void render(const mbgl::style::CustomLayerRenderParameters &parameters) {
if (layerRef == nil) return; if (!layer) return;
else if (layer == nil) layer = layerRef;
if (layer.mapView) {
[layer didMoveToMapView:layer.mapView];
}
}
void render(const mbgl::style::CustomLayerRenderParameters& parameters) {
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)
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr; .encoder.get();
layer.renderEncoder = encoder; id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
layer.renderEncoder = encoder;
#endif #endif
MLNStyleLayerDrawingContext drawingContext = { MLNStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(parameters.width, parameters.height), .size = CGSizeMake(parameters.width, parameters.height),
.centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude), .centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude),
.zoomLevel = parameters.zoom, .zoomLevel = parameters.zoom,
.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;
if (layer.mapView) {
if (layer.mapView) { [layer willMoveFromMapView:layer.mapView];
[layer willMoveFromMapView:layer.mapView];
}
layerRef = layer;
layer = nil;
} }
layerRef = layer;
layer = nil;
}
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];
} }
} // namespace mbgl } // namespace mbgl

View File

@@ -3,25 +3,25 @@
CGFloat MLNEffectiveScaleFactorForView(id viewOrNil) { CGFloat MLNEffectiveScaleFactorForView(id viewOrNil) {
#if TARGET_OS_IPHONE || TARGET_OS_TV #if TARGET_OS_IPHONE || TARGET_OS_TV
UIView *view = (UIView *)viewOrNil; UIView *view = (UIView *)viewOrNil;
UIScreen *screen = view.window.screen ?: [UIScreen mainScreen]; UIScreen *screen = view.window.screen ?: [UIScreen mainScreen];
CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width; CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width;
if (logicalToPixelRatio > 0.0) { if (logicalToPixelRatio > 0.0) {
return logicalToPixelRatio; return logicalToPixelRatio;
} }
return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale; return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale;
#elif TARGET_OS_OSX #elif TARGET_OS_OSX
NSView *view = (NSView *)viewOrNil; NSView *view = (NSView *)viewOrNil;
NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen]; NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen];
return screen ? screen.backingScaleFactor : 1.0; return screen ? screen.backingScaleFactor : 1.0;
#else #else
return 1.0; return 1.0;
#endif #endif
} }

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 ()
@@ -40,28 +40,29 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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 {
return mbglFeature({[self geometryObject]}, identifier, self.attributes); return mbglFeature({[self geometryObject]}, identifier, self.attributes);
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
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
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
self.attributes.count ? self.attributes : @"none"]; : self.identifier,
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -80,25 +81,27 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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.coordinate.latitude, self.coordinate.longitude, : self.identifier,
self.attributes.count ? self.attributes : @"none"]; self.coordinate.latitude, self.coordinate.longitude,
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -106,28 +109,28 @@ 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 =
MLNAssert(clusterNumber, @"Clusters should have a cluster_id"); MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
if (!clusterNumber) { if (!clusterNumber) {
return MLNClusterIdentifierInvalid; return MLNClusterIdentifierInvalid;
} }
NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue]; NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue];
MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit"); MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit");
return clusterIdentifier; return clusterIdentifier;
} }
- (NSUInteger)clusterPointCount { - (NSUInteger)clusterPointCount {
NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber); NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber);
MLNAssert(count, @"Clusters should have a point_count"); MLNAssert(count, @"Clusters should have a point_count");
return [count unsignedIntegerValue]; return [count unsignedIntegerValue];
} }
@end @end
@interface MLNPolylineFeature () @interface MLNPolylineFeature ()
@end @end
@@ -142,26 +145,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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]
(unsigned long)[self pointCount], : self.identifier,
MLNStringFromCoordinateBounds(self.overlayBounds), (unsigned long)[self pointCount],
self.attributes.count ? self.attributes : @"none"]; MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -180,26 +185,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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]
(unsigned long)[self pointCount], : self.identifier,
MLNStringFromCoordinateBounds(self.overlayBounds), (unsigned long)[self pointCount],
self.attributes.count ? self.attributes : @"none"]; MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -218,16 +225,17 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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);
} }
@end @end
@@ -246,26 +254,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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]
(unsigned long)self.polylines.count, : self.identifier,
MLNStringFromCoordinateBounds(self.overlayBounds), (unsigned long)self.polylines.count,
self.attributes.count ? self.attributes : @"none"]; MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -284,26 +294,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key); MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key]; return self.attributes[key];
} }
- (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]
(unsigned long)self.polygons.count, : self.identifier,
MLNStringFromCoordinateBounds(self.overlayBounds), (unsigned long)self.polygons.count,
self.attributes.count ? self.attributes : @"none"]; MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
} }
@end @end
@@ -319,7 +331,7 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
@dynamic shapes; @dynamic shapes;
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape<MLNFeature> *> *)shapes { + (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape<MLNFeature> *> *)shapes {
return [super shapeCollectionWithShapes:shapes]; return [super shapeCollectionWithShapes:shapes];
} }
MLN_DEFINE_FEATURE_INIT_WITH_CODER(); MLN_DEFINE_FEATURE_INIT_WITH_CODER();
@@ -328,22 +340,24 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER(); MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key { - (id)attributeForKey:(NSString *)key {
return self.attributes[key]; return self.attributes[key];
} }
- (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>(),
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>()); @"Feature collection must only contain features.");
} featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
return featureCollection; }
return featureCollection;
} }
@end @end
@@ -355,199 +369,211 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
template <typename T> template <typename T>
class GeometryEvaluator { class GeometryEvaluator {
private: private:
const mbgl::PropertyMap *shared_properties; const mbgl::PropertyMap *shared_properties;
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.
// This could be generic and build the subclass at runtime if it turns // This could be generic and build the subclass at runtime if it turns
// out we need to support more than point clusters. // out we need to support more than point clusters.
if (shared_properties) { if (shared_properties) {
auto clusterIt = shared_properties->find("cluster"); auto clusterIt = shared_properties->find("cluster");
if (clusterIt != shared_properties->end()) { if (clusterIt != shared_properties->end()) {
auto clusterValue = clusterIt->second; auto clusterValue = clusterIt->second;
if (clusterValue.template is<bool>()) { if (clusterValue.template is<bool>()) {
if (clusterValue.template get<bool>()) { if (clusterValue.template get<bool>()) {
shapeClass = [MLNPointFeatureCluster class]; shapeClass = [MLNPointFeatureCluster class];
} }
}
}
} }
}
MLNPointAnnotation *shape = [[shapeClass alloc] init];
shape.coordinate = toLocationCoordinate2D(geometry);
return shape;
} }
MLNShape * operator()(const mbgl::LineString<T> &geometry) const { MLNPointAnnotation *shape = [[shapeClass alloc] init];
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); shape.coordinate = toLocationCoordinate2D(geometry);
Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class]; return shape;
return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; }
MLNShape *operator()(const mbgl::LineString<T> &geometry) const {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class];
return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
}
MLNShape *operator()(const mbgl::Polygon<T> &geometry) const {
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature);
}
MLNShape *operator()(const mbgl::MultiPoint<T> &geometry) const {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
Class shapeClass =
is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class];
return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
}
MLNShape *operator()(const mbgl::MultiLineString<T> &geometry) const {
NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()];
for (auto &lineString : geometry) {
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString);
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0]
count:coordinates.size()];
[polylines addObject:polyline];
} }
MLNShape * operator()(const mbgl::Polygon<T> &geometry) const { Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class];
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature); return [shapeClass multiPolylineWithPolylines:polylines];
}
MLNShape *operator()(const mbgl::MultiPolygon<T> &geometry) const {
NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
for (auto &polygon : geometry) {
[polygons addObject:toShape(polygon, false)];
} }
MLNShape * operator()(const mbgl::MultiPoint<T> &geometry) const { Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class];
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry); return [shapeClass multiPolygonWithPolygons:polygons];
Class shapeClass = is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class]; }
return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
} MLNShape *operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
MLNShape * operator()(const mbgl::MultiLineString<T> &geometry) const { for (auto &geometry : collection) {
NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()]; // This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(),
for (auto &lineString : geometry) { // but these are raw geometries with no associated feature IDs or attributes.
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString); MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, *this);
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()]; [shapes addObject:shape];
[polylines addObject:polyline];
}
Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class];
return [shapeClass multiPolylineWithPolylines:polylines];
}
MLNShape * operator()(const mbgl::MultiPolygon<T> &geometry) const {
NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
for (auto &polygon : geometry) {
[polygons addObject:toShape(polygon, false)];
}
Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class];
return [shapeClass multiPolygonWithPolygons:polygons];
}
MLNShape * operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
for (auto &geometry : collection) {
// This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(), but these are raw geometries with no associated feature IDs or attributes.
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, *this);
[shapes addObject:shape];
}
Class shapeClass = is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class];
return [shapeClass shapeCollectionWithShapes:shapes];
} }
Class shapeClass =
is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class];
return [shapeClass shapeCollectionWithShapes:shapes];
}
private: private:
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) { static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
return CLLocationCoordinate2DMake(point.y, point.x); return CLLocationCoordinate2DMake(point.y, point.x);
}
static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(
const std::vector<mbgl::Point<T>> &points) {
std::vector<CLLocationCoordinate2D> coordinates;
coordinates.reserve(points.size());
std::transform(points.begin(), points.end(), std::back_inserter(coordinates),
toLocationCoordinate2D);
return coordinates;
}
template <typename U = MLNPolygon, typename V = MLNPolygonFeature>
static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) {
auto &linearRing = geometry.front();
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing);
NSMutableArray *innerPolygons;
if (geometry.size() > 1) {
innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1];
for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) {
auto &innerRing = *iter;
std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing);
MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0]
count:innerCoordinates.size()];
[innerPolygons addObject:innerPolygon];
}
} }
static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(const std::vector<mbgl::Point<T>> &points) { Class shapeClass = isInFeature ? [V class] : [U class];
std::vector<CLLocationCoordinate2D> coordinates; return [shapeClass polygonWithCoordinates:&coordinates[0]
coordinates.reserve(points.size()); count:coordinates.size()
std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D); interiorPolygons:innerPolygons];
return coordinates; }
}
template<typename U = MLNPolygon, typename V = MLNPolygonFeature>
static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) {
auto &linearRing = geometry.front();
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing);
NSMutableArray *innerPolygons;
if (geometry.size() > 1) {
innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1];
for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) {
auto &innerRing = *iter;
std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing);
MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0] count:innerCoordinates.size()];
[innerPolygons addObject:innerPolygon];
}
}
Class shapeClass = isInFeature ? [V class] : [U class];
return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons];
}
}; };
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)];
}
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
} }
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
}
}; };
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) { NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; const std::vector<mbgl::Feature> &features) {
for (const auto &feature : features) { NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))]; for (const auto &feature : features) {
} [shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
return shapes; }
return shapes;
} }
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features) { NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()]; const std::vector<mbgl::GeoJSONFeature> &features) {
for (const auto &feature : features) { NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
[shapes addObject:MLNFeatureFromMBGLFeature(feature)]; for (const auto &feature : features) {
} [shapes addObject:MLNFeatureFromMBGLFeature(feature)];
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 =
for (auto &pair : feature.properties) { [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
auto &value = pair.second; for (auto &pair : feature.properties) {
ValueEvaluator evaluator; auto &value = pair.second;
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator); ValueEvaluator evaluator;
} attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
GeometryEvaluator<double> evaluator(&feature.properties, true); }
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator); GeometryEvaluator<double> evaluator(&feature.properties, true);
if (!feature.id.is<mapbox::feature::null_value_t>()) { MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)mapbox::geometry::geometry<double>::visit(
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator()); feature.geometry, evaluator);
} if (!feature.id.is<mapbox::feature::null_value_t>()) {
shape.attributes = attributes; shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
}
shape.attributes = attributes;
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];
} }
feature.properties = [attributes mgl_propertyMap]; feature.properties = [attributes mgl_propertyMap];
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 =
feature[@"id"] = identifier; [@{@"type" : @"Feature", @"properties" : attributes, @"geometry" : geometry} mutableCopy];
return [feature copy]; feature[@"id"] = identifier;
return [feature copy];
} }

View File

@@ -4,23 +4,22 @@
@implementation MLNForegroundStyleLayer @implementation MLNForegroundStyleLayer
- (NSString *)sourceIdentifier { - (NSString *)sourceIdentifier {
[NSException raise:MLNAbstractClassException [NSException raise:MLNAbstractClassException
format:@"MLNForegroundStyleLayer is an abstract class"]; format:@"MLNForegroundStyleLayer is an abstract class"];
return nil; return nil;
} }
- (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]; }
}
} }
@end @end

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

@@ -19,113 +19,141 @@ const CLLocationDegrees MLNAngularFieldOfView = 30;
const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0}; const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0};
CGRect MLNExtendRect(CGRect rect, CGPoint point) { CGRect MLNExtendRect(CGRect rect, CGPoint point) {
if (point.x < rect.origin.x) { if (point.x < rect.origin.x) {
rect.size.width += rect.origin.x - point.x; rect.size.width += rect.origin.x - point.x;
rect.origin.x = point.x; rect.origin.x = point.x;
} }
if (point.x > rect.origin.x + rect.size.width) { if (point.x > rect.origin.x + rect.size.width) {
rect.size.width += point.x - (rect.origin.x + rect.size.width); rect.size.width += point.x - (rect.origin.x + rect.size.width);
} }
if (point.y < rect.origin.y) { if (point.y < rect.origin.y) {
rect.size.height += rect.origin.y - point.y; rect.size.height += rect.origin.y - point.y;
rect.origin.y = point.y; rect.origin.y = point.y;
} }
if (point.y > rect.origin.y + rect.size.height) { if (point.y > rect.origin.y + rect.size.height) {
rect.size.height += point.y - (rect.origin.y + rect.size.height); rect.size.height += point.y - (rect.origin.y + rect.size.height);
} }
return rect; return rect;
} }
mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) { mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
try { try {
return mbgl::LatLng(coordinate.latitude, coordinate.longitude); return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
} catch (std::domain_error &error) { } catch (std::domain_error &error) {
[NSException raise:NSInvalidArgumentException format:@"%s", error.what()]; [NSException raise:NSInvalidArgumentException format:@"%s", error.what()];
return {}; return {};
} }
} }
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 metersTall = metersPerPixel * size.height; CLLocationDistance metersPerPixel =
CLLocationDistance altitude = metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.); mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2); CLLocationDistance metersTall = metersPerPixel * size.height;
CLLocationDistance altitude =
metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
} }
double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) { 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 =
CLLocationDistance metersPerPixel = metersTall / size.height; altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel; CLLocationDistance metersTall =
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D); eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
CLLocationDistance metersPerPixel = metersTall / size.height;
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI *
mbgl::util::EARTH_RADIUS_M / metersPerPixel;
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
} }
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) { 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,
double a = sin(to.longitude - from.longitude) * cos(to.latitude); MLNRadianCoordinate2D to) {
double b = cos(from.latitude) * sin(to.latitude) double a = sin(to.longitude - from.longitude) * cos(to.latitude);
- sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude); double b = cos(from.latitude) * sin(to.latitude) -
return atan2(a, b); sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
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 =
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude)); coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude); cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
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) {
return 0; if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) &&
} MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
return 0;
}
// Ported from
// https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
MLNRadianCoordinate2D firstRadianCoordinate =
MLNRadianCoordinateFromLocationCoordinate(firstCoordinate);
MLNRadianCoordinate2D secondRadianCoordinate =
MLNRadianCoordinateFromLocationCoordinate(secondCoordinate);
// Ported from https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31 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) MLNRadianDirection radianDirection = atan2(a, b);
- sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude)); return radianDirection * 180 / M_PI;
MLNRadianDirection radianDirection = atan2(a, b);
return radianDirection * 180 / M_PI;
} }
CGPoint MLNPointRounded(CGPoint point) { CGPoint MLNPointRounded(CGPoint point) {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale; CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
#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(
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel); MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, 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],
return mat4; .m12 = array[6],
.m13 = array[7],
.m20 = array[8],
.m21 = array[9],
.m22 = array[10],
.m23 = array[11],
.m30 = array[12],
.m31 = array[13],
.m32 = array[14],
.m33 = array[15]};
return mat4;
} }
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) 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,90 +24,93 @@
@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.URL = url; self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
return self; self.URL = url;
return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad
image:(MLNImage *)image {
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
self.image = image;
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad image:(MLNImage *)image { return self;
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad];
self.image = image;
return self;
} }
- (NSURL *)URL { - (NSURL *)URL {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL(); auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil; return url ? [NSURL URLWithString:@(url->c_str())] : nil;
} }
- (void)setURL:(NSURL *)url { - (void)setURL:(NSURL *)url {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
if (url) { if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_image = nil; _image = nil;
} else { } else {
self.image = nullptr; self.image = nullptr;
} }
} }
- (void)setImage:(MLNImage *)image { - (void)setImage:(MLNImage *)image {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
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;
} }
- (MLNCoordinateQuad)coordinates { - (MLNCoordinateQuad)coordinates {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
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
NSStringFromClass([self class]), (void *)self, self.identifier, stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
MLNStringFromCoordinateQuad(self.coordinates), NSStringFromClass([self class]), (void *)self, self.identifier,
self.URL, MLNStringFromCoordinateQuad(self.coordinates), self.URL, self.image];
self.image]; } else {
} return [NSString
else { stringWithFormat:
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>", @"<%@: %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];
} }
} }
- (mbgl::style::ImageSource *)rawSource { - (mbgl::style::ImageSource *)rawSource {
return (mbgl::style::ImageSource *)super.rawSource; return (mbgl::style::ImageSource *)super.rawSource;
} }
- (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",
return nil; self.identifier);
} return nil;
}
auto attribution = self.rawSource->getAttribution(); auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil; return attribution ? @(attribution->c_str()) : nil;
} }
@end @end

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,154 +10,155 @@
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
switch (severity) { stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code,
case EventSeverity::Debug: [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
MLNLogDebug(message); switch (severity) {
break; case EventSeverity::Debug:
case EventSeverity::Info: MLNLogDebug(message);
MLNLogInfo(message); break;
break; case EventSeverity::Info:
case EventSeverity::Warning: MLNLogInfo(message);
MLNLogWarning(message); break;
break; case EventSeverity::Warning:
case EventSeverity::Error: MLNLogWarning(message);
MLNLogError(message); break;
break; case EventSeverity::Error:
default: MLNLogError(message);
assert(false); break;
} default:
return true; assert(false);
} }
return true;
}
}; };
} } // namespace mbgl
@implementation MLNLoggingConfiguration @implementation MLNLoggingConfiguration {
{ std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
} }
+ (instancetype)sharedConfiguration { + (instancetype)sharedConfiguration {
static dispatch_once_t once; static dispatch_once_t once;
static id sharedConfiguration; static id sharedConfiguration;
dispatch_once(&once, ^{ dispatch_once(&once, ^{
sharedConfiguration = [[self alloc] init]; sharedConfiguration = [[self alloc] init];
((MLNLoggingConfiguration *)sharedConfiguration).handler = nil; ((MLNLoggingConfiguration *)sharedConfiguration).handler = nil;
}); });
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 { _handler = handler;
_handler = handler; }
}
} }
- (void)logCallingFunction:(const char *)callingFunction functionLine:(NSUInteger)functionLine messageType:(MLNLoggingLevel)type format:(id)messageFormat, ... { - (void)logCallingFunction:(const char *)callingFunction
va_list formatList; functionLine:(NSUInteger)functionLine
va_start(formatList, messageFormat); messageType:(MLNLoggingLevel)type
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList]; format:(id)messageFormat, ... {
va_end(formatList); va_list formatList;
va_start(formatList, messageFormat);
_handler(type, @(callingFunction), functionLine, formattedMessage); NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
va_end(formatList);
_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;
#if MLN_LOGGING_ENABLE_DEBUG #if MLN_LOGGING_ENABLE_DEBUG
static os_log_t debug_log; static os_log_t debug_log;
#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");
#endif #endif
error_log = os_log_create(subsystem, "ERROR"); error_log = os_log_create(subsystem, "ERROR");
fault_log = os_log_create(subsystem, "FAULT"); fault_log = os_log_create(subsystem, "FAULT");
}); });
os_log_t maplibre_log; os_log_t maplibre_log;
switch (level) { switch (level) {
case MLNLoggingLevelInfo: case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning: case MLNLoggingLevelWarning:
maplibre_log = info_log; maplibre_log = info_log;
break; break;
#if MLN_LOGGING_ENABLE_DEBUG #if MLN_LOGGING_ENABLE_DEBUG
case MLNLoggingLevelDebug: case MLNLoggingLevelDebug:
maplibre_log = debug_log; maplibre_log = debug_log;
break; break;
#endif #endif
case MLNLoggingLevelError: case MLNLoggingLevelError:
maplibre_log = error_log; maplibre_log = error_log;
break; break;
case MLNLoggingLevelFault: case MLNLoggingLevelFault:
maplibre_log = fault_log; maplibre_log = fault_log;
break; break;
case MLNLoggingLevelNone: case MLNLoggingLevelNone:
default: default:
break; break;
} }
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) {
case MLNLoggingLevelInfo: case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning: case MLNLoggingLevelWarning:
category = @"INFO"; category = @"INFO";
break; break;
#if MLN_LOGGING_ENABLE_DEBUG #if MLN_LOGGING_ENABLE_DEBUG
case MLNLoggingLevelDebug: case MLNLoggingLevelDebug:
category = @"DEBUG"; category = @"DEBUG";
break; break;
#endif #endif
case MLNLoggingLevelError: case MLNLoggingLevelError:
category = @"ERROR"; category = @"ERROR";
break; break;
case MLNLoggingLevelFault: case MLNLoggingLevelFault:
category = @"FAULT"; category = @"FAULT";
break; break;
case MLNLoggingLevelNone: case MLNLoggingLevelNone:
default: default:
break; break;
} }
NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message); NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message);
} }
}; };
return maplibreHandler; return maplibreHandler;
} }
@end @end

View File

@@ -8,191 +8,179 @@
@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) &&
if (CLLocationCoordinate2DIsValid(centerCoordinate) && CLLocationCoordinate2DIsValid(eyeCoordinate)) { CLLocationCoordinate2DIsValid(eyeCoordinate)) {
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate); heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
longitude:centerCoordinate.longitude]; longitude:centerCoordinate.longitude];
CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude
longitude:eyeCoordinate.longitude]; longitude:eyeCoordinate.longitude];
CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation]; CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation];
CGFloat radianPitch = atan2(eyeAltitude, groundDistance); CGFloat radianPitch = atan2(eyeAltitude, groundDistance);
pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0); pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0);
} }
return [[self alloc] initWithCenterCoordinate:centerCoordinate return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:eyeAltitude altitude:eyeAltitude
pitch:pitch pitch:pitch
heading:heading]; heading:heading];
} }
+ (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; CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
return [[self alloc] initWithCenterCoordinate:centerCoordinate return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude altitude:altitude
pitch:pitch pitch:pitch
heading:heading]; heading:heading];
} }
+ (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 heading:heading];
heading:heading];
} }
+ (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
pitch:pitch pitch:pitch
heading:heading]; heading:heading];
} }
- (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; _heading = heading;
_heading = heading; _roll = 0;
_roll = 0; }
} 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"],
{ [coder decodeDoubleForKey:@"centerLongitude"]);
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"], _altitude = [coder decodeDoubleForKey:@"altitude"];
[coder decodeDoubleForKey:@"centerLongitude"]); _pitch = [coder decodeDoubleForKey:@"pitch"];
_altitude = [coder decodeDoubleForKey:@"altitude"]; _heading = [coder decodeDoubleForKey:@"heading"];
_pitch = [coder decodeDoubleForKey:@"pitch"]; _roll = [coder decodeDoubleForKey:@"roll"];
_heading = [coder decodeDoubleForKey:@"heading"]; }
_roll = [coder decodeDoubleForKey:@"roll"]; 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"]; [coder encodeDouble:_pitch forKey:@"pitch"];
[coder encodeDouble:_pitch forKey:@"pitch"]; [coder encodeDouble:_heading forKey:@"heading"];
[coder encodeDouble:_heading forKey:@"heading"]; [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 heading:_heading];
heading:_heading]; copy.roll = _roll;
copy.roll = _roll; return copy;
return copy;
} }
+ (NSSet<NSString *> *)keyPathsForValuesAffectingViewingDistance { + (NSSet<NSString *> *)keyPathsForValuesAffectingViewingDistance {
return [NSSet setWithObjects:@"altitude", @"pitch", nil]; return [NSSet setWithObjects:@"altitude", @"pitch", nil];
} }
- (CLLocationDistance)viewingDistance { - (CLLocationDistance)viewingDistance {
CLLocationDirection eyeAngle = 90 - self.pitch; CLLocationDirection eyeAngle = 90 - self.pitch;
return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle)); return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle));
} }
- (void)setViewingDistance:(CLLocationDistance)distance { - (void)setViewingDistance:(CLLocationDistance)distance {
MLNLogDebug(@"Setting viewingDistance: %f", distance); MLNLogDebug(@"Setting viewingDistance: %f", distance);
CLLocationDirection eyeAngle = 90 - self.pitch; CLLocationDirection eyeAngle = 90 - self.pitch;
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) {
} return YES;
if (other == self) }
{
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,19 +6,17 @@
@implementation MLNMapOptions @implementation MLNMapOptions
-(instancetype _Nonnull)init - (instancetype _Nonnull)init {
{ self = [super init];
self = [super init]; if (self) {
if (self) _styleURL = nil;
{ _styleJSON = nil;
_styleURL = nil; _pluginLayers = nil;
_styleJSON = nil;
_pluginLayers = nil;
_actionJournalOptions = [[MLNActionJournalOptions alloc] init]; _actionJournalOptions = [[MLNActionJournalOptions alloc] init];
} }
return self; return self;
} }
@end @end

File diff suppressed because it is too large Load Diff

View File

@@ -1,208 +1,201 @@
#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;
} }
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
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"]; }
} 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 {
MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count); MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count);
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."];
} }
[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
if (!count) { atIndex:(NSUInteger)index {
return; MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count,
} (unsigned long)index);
if (!count) {
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"];
_coordinates.insert(_coordinates.begin() + index, count, *coords); _coordinates.insert(_coordinates.begin() + index, count, *coords);
_bounds = {}; _bounds = {};
[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
if (!count && !range.length) { count:(NSUInteger)count {
return; MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count,
} NSStringFromRange(range));
if (!count && !range.length) {
return;
}
if (NSMaxRange(range) > _coordinates.size()) { if (NSMaxRange(range) > _coordinates.size()) {
[NSException raise:NSRangeException [NSException raise:NSRangeException
format:@"Invalid range %@ for existing coordinate count %ld", format:@"Invalid range %@ for existing coordinate count %ld",
NSStringFromRange(range), (unsigned long)[self pointCount]]; NSStringFromRange(range), (unsigned long)[self pointCount]];
} }
[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,
} else { coords + count - range.length);
_coordinates.erase(_coordinates.begin() + range.location + count, _coordinates.begin() + NSMaxRange(range)); } else {
} _coordinates.erase(_coordinates.begin() + range.location + count,
_bounds = {}; _coordinates.begin() + NSMaxRange(range));
[self didChangeValueForKey:@"coordinates"]; }
_bounds = {};
[self didChangeValueForKey:@"coordinates"];
} }
- (void)removeCoordinatesInRange:(NSRange)range { - (void)removeCoordinatesInRange:(NSRange)range {
MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range)); MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range));
CLLocationCoordinate2D coords; CLLocationCoordinate2D coords;
[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) { if (!MLNLocationCoordinate2DIsValid(coordinate)) {
if (!MLNLocationCoordinate2DIsValid(coordinate)) { bounds = mbgl::LatLngBounds::empty();
bounds = mbgl::LatLngBounds::empty(); break;
break; }
} bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
}
_bounds = bounds;
} }
return MLNCoordinateBoundsFromLatLngBounds(*_bounds); _bounds = 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,
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], (unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }
@end @end

View File

@@ -1,244 +1,238 @@
#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;
} }
- (instancetype)init { - (instancetype)init {
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;
} }
+ (instancetype)sharedManager { + (instancetype)sharedManager {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
static MLNNetworkConfiguration *_sharedManager; static MLNNetworkConfiguration *_sharedManager;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
_sharedManager = [[self alloc] init]; _sharedManager = [[self alloc] init];
}); });
// Notice, this is reset for each call. This is primarily for testing purposes. // Notice, this is reset for each call. This is primarily for testing purposes.
// TODO: Consider only calling this for testing? // TODO: Consider only calling this for testing?
[_sharedManager resetNativeNetworkManagerDelegate]; [_sharedManager resetNativeNetworkManagerDelegate];
return _sharedManager; return _sharedManager;
} }
- (void)resetNativeNetworkManagerDelegate { - (void)resetNativeNetworkManagerDelegate {
// Tell core about our network integration. `delegate` here is not (yet) // Tell core about our network integration. `delegate` here is not (yet)
// intended to be set to nil, except for testing. // intended to be set to nil, except for testing.
[MLNNativeNetworkManager sharedManager].delegate = self; [MLNNativeNetworkManager sharedManager].delegate = self;
} }
+ (NSURLSessionConfiguration *)defaultSessionConfiguration { + (NSURLSessionConfiguration *)defaultSessionConfiguration {
NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSessionConfiguration *sessionConfiguration =
[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.timeoutIntervalForResource = 30; sessionConfiguration.timeoutIntervalForResource = 30;
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8; sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfiguration.URLCache = nil; sessionConfiguration.URLCache = nil;
return sessionConfiguration; return sessionConfiguration;
} }
// MARK: - MLNNativeNetworkDelegate // MARK: - MLNNativeNetworkDelegate
- (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager { - (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager {
// Note: this method is NOT called on the main thread. // Note: this method is NOT called on the main thread.
NSURLSession *session; NSURLSession *session;
if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) { if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) {
session = [self.delegate sessionForNetworkConfiguration:self]; session = [self.delegate sessionForNetworkConfiguration:self];
} }
// Check for a background session; string checking is fragile, but this is not // Check for a background session; string checking is fragile, but this is not
// a deal breaker as we're only doing this to provide more clarity to the // a deal breaker as we're only doing this to provide more clarity to the
// developer // developer
NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")], NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")],
@"Background NSURLSessions are not yet supported"); @"Background NSURLSessions are not yet supported");
if (session.delegate) { if (session.delegate) {
NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)], NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)],
@"Session delegates conforming to NSURLSessionDataDelegate are not yet supported"); @"Session delegates conforming to NSURLSessionDataDelegate are not yet supported");
} }
return session; return session;
} }
- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request { - (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request {
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
return [self.delegate willSendRequest:request];
}
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) { return request;
return [self.delegate willSendRequest: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
urlResponse:response.response
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data error:response.error];
urlResponse:response.response if (tempResponse) {
error:response.error]; MLNInternalNetworkResponse *internalResponse =
if (tempResponse) { [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 {
_sessionConfig = sessionConfiguration; _sessionConfig = sessionConfiguration;
}
} }
}
} }
- (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];
} }
} }
- (void)stopDownloadEventForResponse:(NSURLResponse *)response { - (void)stopDownloadEventForResponse:(NSURLResponse *)response {
[self sendEventForURLResponse:response withAction:nil]; [self sendEventForURLResponse:response withAction:nil];
} }
- (void)cancelDownloadEventForResponse:(NSURLResponse *)response { - (void)cancelDownloadEventForResponse:(NSURLResponse *)response {
[self sendEventForURLResponse:response withAction:@"cancel"]; [self sendEventForURLResponse:response withAction:@"cancel"];
} }
- (void)debugLog:(NSString *)message { - (void)debugLog:(NSString *)message {
MLNLogDebug(message); MLNLogDebug(message);
} }
- (void)errorLog:(NSString *)message { - (void)errorLog:(NSString *)message {
MLNLogError(message); MLNLogError(message);
} }
// 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]) { NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action];
NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action]; [self removeEventDictionaryForKey:urlString];
[self removeEventDictionaryForKey:urlString];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes]; [self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes];
}); });
}
} }
}
} }
- (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;
return @{ return @{
@"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, ^{
dictionary = [self.events objectForKey:key]; dictionary = [self.events objectForKey:key];
}); });
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];
}); });
} }
@end @end
@@ -246,11 +240,11 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
@implementation MLNNetworkConfiguration (ForTesting) @implementation MLNNetworkConfiguration (ForTesting)
+ (void)testing_clearNativeNetworkManagerDelegate { + (void)testing_clearNativeNetworkManagerDelegate {
[MLNNativeNetworkManager sharedManager].delegate = nil; [MLNNativeNetworkManager sharedManager].delegate = nil;
} }
+ (id)testing_nativeNetworkManagerDelegate { + (id)testing_nativeNetworkManagerDelegate {
return [MLNNativeNetworkManager sharedManager].delegate; return [MLNNativeNetworkManager sharedManager].delegate;
} }
@end @end

View File

@@ -1,17 +1,16 @@
#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; tempResult.error = error;
tempResult.error = error; return tempResult;
return tempResult;
} }
@end @end

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"
@@ -22,18 +22,19 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
This macro should be used at the beginning of any public-facing instance method This macro should be used at the beginning of any public-facing instance method
of ``MLNOfflinePack``. For private methods, an assertion is more appropriate. of ``MLNOfflinePack``. For private methods, an assertion is more appropriate.
*/ */
#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>
@@ -41,14 +42,14 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver { class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver {
public: public:
MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {} MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {}
void statusChanged(mbgl::OfflineRegionStatus status) override; void statusChanged(mbgl::OfflineRegionStatus status) override;
void responseError(mbgl::Response::Error error) override; void responseError(mbgl::Response::Error error) override;
void mapboxTileCountLimitExceeded(uint64_t limit) override; void mapboxTileCountLimitExceeded(uint64_t limit) override;
private: private:
__weak MLNOfflinePack *pack = nullptr; __weak MLNOfflinePack *pack = nullptr;
}; };
@interface MLNOfflinePack () @interface MLNOfflinePack ()
@@ -59,268 +60,296 @@ private:
@end @end
@implementation MLNOfflinePack { @implementation MLNOfflinePack {
BOOL _isSuspending; BOOL _isSuspending;
std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource; std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource;
} }
- (instancetype)init { - (instancetype)init {
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?",
return self; __PRETTY_FUNCTION__);
}
return self;
} }
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region { - (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region {
if (self = [super init]) { if (self = [super init]) {
_mbglOfflineRegion = region; _mbglOfflineRegion = region;
_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){ },
return (id <MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc] initWithOfflineRegionDefinition:def]; [&](const mbgl::OfflineGeometryRegionDefinition &def) {
} return (id<MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc]
}, regionDefinition); initWithOfflineRegionDefinition:def];
}},
regionDefinition);
} }
- (NSData *)context { - (NSData *)context {
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata(); const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata();
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
MLNAssertOfflinePackIsValid(); completionHandler:(void (^_Nullable)(NSError *_Nullable error))completion {
MLNAssertOfflinePackIsValid();
mbgl::OfflineRegionMetadata metadata(context.length); mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()]; [context getBytes:&metadata[0] length:metadata.size()];
[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) {
completion(error ?: reloadingError); completion(error ?: reloadingError);
} }
}]; }];
}); });
}); });
} }
- (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,
MLNOfflinePack *strongSelf = weakSelf; __unused NSError *_Nullable error) {
for (MLNOfflinePack *pack in packs) { MLNOfflinePack *strongSelf = weakSelf;
if (pack.mbglOfflineRegion->getID() == regionID) { for (MLNOfflinePack *pack in packs) {
strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion; if (pack.mbglOfflineRegion->getID() == regionID) {
} strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion;
[pack invalidate]; }
} [pack invalidate];
completion(error); }
}]; completion(error);
}];
} }
- (void)resume { - (void)resume {
MLNLogInfo(@"Resuming pack download."); MLNLogInfo(@"Resuming pack download.");
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
self.state = MLNOfflinePackStateActive; self.state = MLNOfflinePackStateActive;
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active); _mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion,
mbgl::OfflineRegionDownloadState::Active);
} }
- (void)suspend { - (void)suspend {
MLNLogInfo(@"Suspending pack download."); MLNLogInfo(@"Suspending pack download.");
MLNAssertOfflinePackIsValid(); MLNAssertOfflinePackIsValid();
if (self.state == MLNOfflinePackStateActive) { if (self.state == MLNOfflinePackStateActive) {
self.state = MLNOfflinePackStateInactive; self.state = MLNOfflinePackStateInactive;
_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,
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region"); @"Cannot invalidate an already invalid offline pack.");
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);
}
self.mbglOfflineRegion = nil;
} }
self.mbglOfflineRegion = nil;
}
} }
- (void)setState:(MLNOfflinePackState)state { - (void)setState:(MLNOfflinePackState)state {
MLNLogDebug(@"Setting state: %ld", (long)state); MLNLogDebug(@"Setting state: %ld", (long)state);
if (!self.mbglOfflineRegion) { if (!self.mbglOfflineRegion) {
// 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,
return; @"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
} return;
}
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change the state of an invalid offline pack."); MLNAssert(_state != MLNOfflinePackStateInvalid,
@"Cannot change the state of an invalid offline pack.");
if (!_isSuspending || state != MLNOfflinePackStateActive) { if (!_isSuspending || state != MLNOfflinePackStateActive) {
_isSuspending = NO; _isSuspending = NO;
_state = state; _state = state;
} }
} }
- (void)requestProgress { - (void)requestProgress {
MLNLogInfo(@"Requesting pack progress."); MLNLogInfo(@"Requesting pack progress.");
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(), ^{
MLNOfflinePack *strongSelf = weakSelf; MLNOfflinePack *strongSelf = weakSelf;
[strongSelf offlineRegionStatusDidChange:checkedStatus]; [strongSelf offlineRegionStatusDidChange:checkedStatus];
}); });
} }
}); });
} }
- (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:
self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive; self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive;
break; break;
case mbgl::OfflineRegionDownloadState::Active: case mbgl::OfflineRegionDownloadState::Active:
self.state = MLNOfflinePackStateActive; self.state = MLNOfflinePackStateActive;
break; break;
} }
if (_isSuspending) { if (_isSuspending) {
return; return;
} }
MLNOfflinePackProgress progress; MLNOfflinePackProgress progress;
progress.countOfResourcesCompleted = status.completedResourceCount; progress.countOfResourcesCompleted = status.completedResourceCount;
progress.countOfBytesCompleted = status.completedResourceSize; progress.countOfBytesCompleted = status.completedResourceSize;
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 =
self.progress = progress; status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
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
object:self object:self
userInfo:userInfo]; userInfo:userInfo];
} }
- (void)didReceiveError:(NSError *)error { - (void)didReceiveError:(NSError *)error {
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
userInfo:userInfo]; userInfo:userInfo];
} }
NSError *MLNErrorFromResponseError(mbgl::Response::Error error) { NSError *MLNErrorFromResponseError(mbgl::Response::Error error) {
NSInteger errorCode = MLNErrorCodeUnknown; NSInteger errorCode = MLNErrorCodeUnknown;
switch (error.reason) { switch (error.reason) {
case mbgl::Response::Error::Reason::NotFound: case mbgl::Response::Error::Reason::NotFound:
errorCode = MLNErrorCodeNotFound; errorCode = MLNErrorCodeNotFound;
break; break;
case mbgl::Response::Error::Reason::Server: case mbgl::Response::Error::Reason::Server:
errorCode = MLNErrorCodeBadServerResponse; errorCode = MLNErrorCodeBadServerResponse;
break; break;
case mbgl::Response::Error::Reason::Connection: case mbgl::Response::Error::Reason::Connection:
errorCode = MLNErrorCodeConnectionFailed; errorCode = MLNErrorCodeConnectionFailed;
break; break;
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())
}];
} }
@end @end
void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) { void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) {
__weak MLNOfflinePack *weakPack = pack; __weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[weakPack offlineRegionStatusDidChange:status]; [weakPack offlineRegionStatusDidChange:status];
}); });
} }
void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) { void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) {
__weak MLNOfflinePack *weakPack = pack; __weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveError:MLNErrorFromResponseError(error)]; [weakPack didReceiveError:MLNErrorFromResponseError(error)];
}); });
} }
void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) { void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) {
__weak MLNOfflinePack *weakPack = pack; __weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveMaximumAllowedMapboxTiles:limit]; [weakPack didReceiveMaximumAllowedMapboxTiles:limit];
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2,42 +2,41 @@
@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];
tempResult.propertyName = propertyName; tempResult.propertyName = propertyName;
tempResult.propertyType = propertyType; tempResult.propertyType = propertyType;
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) { if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
if ([defaultValue isKindOfClass:[NSNumber class]]) { if ([defaultValue isKindOfClass:[NSNumber class]]) {
tempResult.singleFloatDefaultValue = [defaultValue floatValue]; tempResult.singleFloatDefaultValue = [defaultValue floatValue];
}
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
#if TARGET_OS_IPHONE
if ([defaultValue isKindOfClass:[UIColor class]]) {
#else
if ([defaultValue isKindOfClass:[NSColor class]]) {
#endif
tempResult.colorDefaultValue = defaultValue;
}
} }
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
#if TARGET_OS_IPHONE
if ([defaultValue isKindOfClass:[UIColor class]]) {
#else
if ([defaultValue isKindOfClass:[NSColor class]]) {
#endif
tempResult.colorDefaultValue = defaultValue;
}
}
return tempResult; return tempResult;
} }
- (id)init {
// Base class implemenation
if (self = [super init]) {
// Default setup
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
self.propertyName = @"unknown";
-(id)init { // Default values for the various types
// Base class implemenation self.singleFloatDefaultValue = 1.0;
if (self = [super init]) { }
// Default setup return self;
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
self.propertyName = @"unknown";
// Default values for the various types
self.singleFloatDefaultValue = 1.0;
}
return self;
} }
@end @end
@@ -45,36 +44,31 @@
@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
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder { renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
// Base class does nothing // Base class does nothing
} }
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext { - (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// Base class does nothing // Base class does nothing
} }
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// Base class does nothing // Base class does nothing
} }
/// Added to a map view /// Added to a map view
- (void)didMoveToMapView:(MLNMapView *)mapView { - (void)didMoveToMapView:(MLNMapView *)mapView {
// Base class does nothing // Base class does nothing
} }
@end @end
@implementation MLNPluginLayerCapabilities @implementation MLNPluginLayerCapabilities
@end @end

View File

@@ -1,35 +1,28 @@
#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; return pl;
return pl; }
}
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,71 +1,66 @@
#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"]; }
} 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]
coordinate.latitude, coordinate.longitude]; : self.subtitle,
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;
} }
@end @end

View File

@@ -1,139 +1,131 @@
#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;
} }
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
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;
} }
- (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 =
NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"]; [NSSet setWithArray:@[ [NSDictionary class], [NSArray class] ]];
_coordinates = [coordinates mgl_coordinates]; NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"];
} _coordinates = [coordinates mgl_coordinates];
return self; }
return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder { - (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"]; [coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (self == other) return YES; if (self == other) return YES;
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 {
if (!_bounds) { if (!_bounds) {
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (auto coordinate : _coordinates) { for (auto coordinate : _coordinates) {
if (!MLNLocationCoordinate2DIsValid(coordinate)) { if (!MLNLocationCoordinate2DIsValid(coordinate)) {
bounds = mbgl::LatLngBounds::empty(); bounds = mbgl::LatLngBounds::empty();
break; break;
} }
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate)); bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
}
_bounds = bounds;
} }
return MLNCoordinateBoundsFromLatLngBounds(*_bounds); _bounds = 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(
{ mbgl::Point<double>(self.coordinates[i].longitude, self.coordinates[i].latitude));
multiPoint.push_back(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,
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount], (unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds)]; MLNStringFromCoordinateBounds(self.overlayBounds)];
} }
@end @end

View File

@@ -1,135 +1,140 @@
#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
@dynamic overlayBounds; @dynamic overlayBounds;
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
return YES; return YES;
} }
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count { + (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords
return [self polygonWithCoordinates:coords count:count interiorPolygons:nil]; count:(NSUInteger)count {
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
return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons]; count:(NSUInteger)count
interiorPolygons:(NSArray<MLNPolygon *> *)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
if (self = [super initWithCoordinates:coords count:count]) { interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
if (interiorPolygons.count) { MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count,
_interiorPolygons = interiorPolygons; (unsigned long)interiorPolygons);
} if (self = [super initWithCoordinates:coords count:count]) {
if (interiorPolygons.count) {
_interiorPolygons = interiorPolygons;
} }
return self; }
return self;
} }
- (instancetype)initWithCoder:(NSCoder *)decoder { - (instancetype)initWithCoder:(NSCoder *)decoder {
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 =
_interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"]; [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
} _interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"];
return self; }
return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder { - (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"]; [coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (self == other) return YES; if (self == other) return YES;
if (![other isKindOfClass:[MLNPolygon class]]) return NO; if (![other isKindOfClass:[MLNPolygon class]]) return NO;
MLNPolygon *otherPolygon = (MLNPolygon *)other; MLNPolygon *otherPolygon = (MLNPolygon *)other;
return ([super isEqual:otherPolygon] && return ([super isEqual:otherPolygon] &&
[[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]); [[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]);
} }
- (NSUInteger)hash { - (NSUInteger)hash {
return [super hash] + [[self geoJSONDictionary] hash]; return [super hash] + [[self geoJSONDictionary] hash];
} }
- (CLLocationCoordinate2D)coordinate { - (CLLocationCoordinate2D)coordinate {
// pole of inaccessibility // pole of inaccessibility
auto poi = mapbox::polylabel([self polygon]); auto poi = mapbox::polylabel([self polygon]);
return MLNLocationCoordinate2DFromPoint(poi); return MLNLocationCoordinate2DFromPoint(poi);
} }
- (mbgl::LinearRing<double>)ring { - (mbgl::LinearRing<double>)ring {
NSUInteger count = self.pointCount; NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates; CLLocationCoordinate2D *coordinates = self.coordinates;
mbgl::LinearRing<double> result; mbgl::LinearRing<double> result;
result.reserve(self.pointCount); result.reserve(self.pointCount);
for (NSUInteger i = 0; i < count; i++) { for (NSUInteger i = 0; i < count; i++) {
result.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude)); result.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
} }
return result; return result;
} }
- (mbgl::Polygon<double>)polygon { - (mbgl::Polygon<double>)polygon {
mbgl::Polygon<double> geometry; mbgl::Polygon<double> geometry;
geometry.push_back(self.ring); geometry.push_back(self.ring);
for (MLNPolygon *polygon in self.interiorPolygons) { for (MLNPolygon *polygon in self.interiorPolygons) {
geometry.push_back(polygon.ring); geometry.push_back(polygon.ring);
} }
return geometry; return geometry;
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
return [self polygon]; return [self polygon];
} }
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate { - (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
mbgl::FillAnnotation annotation{[self polygon]};
annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
annotation.outlineColor = {[delegate strokeColorForShapeAnnotation:self]};
annotation.color = {[delegate fillColorForPolygonAnnotation:self]};
mbgl::FillAnnotation annotation { [self polygon] }; return annotation;
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] };
annotation.color = { [delegate fillColorForPolygonAnnotation:self] };
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 {
NSMutableArray *coordinates = [NSMutableArray array]; NSMutableArray *coordinates = [NSMutableArray array];
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];
for (MLNPolygon *interiorPolygon in self.interiorPolygons) {
NSMutableArray *interiorRing = [NSMutableArray array];
for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) {
CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index];
[interiorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
} }
[coordinates addObject:exteriorRing]; [coordinates addObject:interiorRing];
}
for (MLNPolygon *interiorPolygon in self.interiorPolygons) { return [coordinates copy];
NSMutableArray *interiorRing = [NSMutableArray array];
for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) {
CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index];
[interiorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
}
[coordinates addObject:interiorRing];
}
return [coordinates copy];
} }
@end @end
@@ -141,117 +146,117 @@
@end @end
@implementation MLNMultiPolygon { @implementation MLNMultiPolygon {
MLNCoordinateBounds _overlayBounds; MLNCoordinateBounds _overlayBounds;
} }
@synthesize overlayBounds = _overlayBounds; @synthesize overlayBounds = _overlayBounds;
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
return YES; return YES;
} }
+ (instancetype)multiPolygonWithPolygons:(NSArray<MLNPolygon *> *)polygons { + (instancetype)multiPolygonWithPolygons:(NSArray<MLNPolygon *> *)polygons {
return [[self alloc] initWithPolygons:polygons]; return [[self alloc] initWithPolygons:polygons];
} }
- (instancetype)initWithPolygons:(NSArray<MLNPolygon *> *)polygons { - (instancetype)initWithPolygons:(NSArray<MLNPolygon *> *)polygons {
MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count); MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count);
if (self = [super init]) { if (self = [super init]) {
_polygons = polygons; _polygons = polygons;
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolygon *polygon in _polygons) { for (MLNPolygon *polygon in _polygons) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
} }
return self; _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
} }
- (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 =
_polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"]; [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
_polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"];
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolygon *polygon in _polygons) { for (MLNPolygon *polygon in _polygons) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds)); bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
} }
return self; _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder { - (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder encodeObject:_polygons forKey:@"polygons"]; [coder encodeObject:_polygons forKey:@"polygons"];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (self == other) return YES; if (self == other) return YES;
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 {
NSUInteger hash = [super hash]; NSUInteger hash = [super hash];
for (MLNPolygon *polygon in self.polygons) { for (MLNPolygon *polygon in self.polygons) {
hash += [polygon hash]; hash += [polygon hash];
} }
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate { - (CLLocationCoordinate2D)coordinate {
MLNPolygon *firstPolygon = self.polygons.firstObject; MLNPolygon *firstPolygon = self.polygons.firstObject;
return firstPolygon.coordinate; return firstPolygon.coordinate;
} }
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
} }
- (mbgl::MultiPolygon<double>)multiPolygon { - (mbgl::MultiPolygon<double>)multiPolygon {
mbgl::MultiPolygon<double> multiPolygon; mbgl::MultiPolygon<double> multiPolygon;
multiPolygon.reserve(self.polygons.count); multiPolygon.reserve(self.polygons.count);
for (MLNPolygon *polygon in self.polygons) { for (MLNPolygon *polygon in self.polygons) {
mbgl::Polygon<double> geometry; mbgl::Polygon<double> geometry;
geometry.push_back(polygon.ring); geometry.push_back(polygon.ring);
for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) { for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) {
geometry.push_back(interiorPolygon.ring); geometry.push_back(interiorPolygon.ring);
}
multiPolygon.push_back(geometry);
} }
return multiPolygon; multiPolygon.push_back(geometry);
}
return multiPolygon;
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
return [self multiPolygon]; return [self multiPolygon];
} }
- (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]
(unsigned long)self.polygons.count, : self.subtitle,
MLNStringFromCoordinateBounds(self.overlayBounds)]; (unsigned long)self.polygons.count,
MLNStringFromCoordinateBounds(self.overlayBounds)];
} }
@end @end

View File

@@ -1,128 +1,125 @@
#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];
} }
- (mbgl::LineString<double>)lineString { - (mbgl::LineString<double>)lineString {
NSUInteger count = self.pointCount; NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates; CLLocationCoordinate2D *coordinates = self.coordinates;
mbgl::LineString<double> geometry; mbgl::LineString<double> geometry;
geometry.reserve(self.pointCount); geometry.reserve(self.pointCount);
for (NSUInteger i = 0; i < count; i++) { for (NSUInteger i = 0; i < count; i++) {
geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude)); geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
} }
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;
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
return [self lineString]; return [self lineString];
} }
- (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];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]); return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]);
} }
- (CLLocationCoordinate2D)coordinate { - (CLLocationCoordinate2D)coordinate {
NSUInteger count = self.pointCount; NSUInteger count = self.pointCount;
MLNAssert(count > 0, @"Polyline must have coordinates"); MLNAssert(count > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D *coordinates = self.coordinates; CLLocationCoordinate2D *coordinates = self.coordinates;
CLLocationDistance middle = [self length] / 2.0; CLLocationDistance middle = [self length] / 2.0;
CLLocationDistance traveled = 0.0; CLLocationDistance traveled = 0.0;
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.
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
// Avoid a heap buffer overflow when there are only two coordinates. MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1; MLNRadianCoordinate2D to =
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]); if (traveled >= middle) {
MLNRadianCoordinate2D to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]); double overshoot = middle - traveled;
if (overshoot == 0) {
if (traveled >= middle) { return coordinates[i];
double overshoot = middle - traveled;
if (overshoot == 0) {
return coordinates[i];
}
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180;
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(from,
overshoot/mbgl::util::EARTH_RADIUS_M,
MLNRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
MLNDegreesFromRadians(otherCoordinate.longitude));
}
traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
} }
} to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180;
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(
from, overshoot / mbgl::util::EARTH_RADIUS_M, MLNRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
MLNDegreesFromRadians(otherCoordinate.longitude));
}
return coordinates[count - 1]; traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
}
}
return coordinates[count - 1];
} }
- (CLLocationDistance)length - (CLLocationDistance)length {
{ 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));
} }
@end @end
@@ -134,112 +131,112 @@
@end @end
@implementation MLNMultiPolyline { @implementation MLNMultiPolyline {
MLNCoordinateBounds _overlayBounds; MLNCoordinateBounds _overlayBounds;
} }
@synthesize overlayBounds = _overlayBounds; @synthesize overlayBounds = _overlayBounds;
+ (instancetype)multiPolylineWithPolylines:(NSArray<MLNPolyline *> *)polylines { + (instancetype)multiPolylineWithPolylines:(NSArray<MLNPolyline *> *)polylines {
return [[self alloc] initWithPolylines:polylines]; return [[self alloc] initWithPolylines:polylines];
} }
- (instancetype)initWithPolylines:(NSArray<MLNPolyline *> *)polylines { - (instancetype)initWithPolylines:(NSArray<MLNPolyline *> *)polylines {
MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count); MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count);
if (self = [super init]) { if (self = [super init]) {
_polylines = polylines; _polylines = polylines;
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolyline *polyline in _polylines) { for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
} }
return self; _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
} }
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
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]) {
NSSet<Class> *polylinesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolyline class]]]; NSSet<Class> *polylinesClasses =
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"]; [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolyline class] ]];
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty(); mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolyline *polyline in _polylines) { for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds)); bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
} }
return self; _overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder { - (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[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 {
NSUInteger hash = [super hash]; NSUInteger hash = [super hash];
for (MLNPolyline *polyline in self.polylines) { for (MLNPolyline *polyline in self.polylines) {
hash += [polyline hash]; hash += [polyline hash];
} }
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate { - (CLLocationCoordinate2D)coordinate {
MLNPolyline *polyline = self.polylines.firstObject; MLNPolyline *polyline = self.polylines.firstObject;
CLLocationCoordinate2D *coordinates = polyline.coordinates; CLLocationCoordinate2D *coordinates = polyline.coordinates;
MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates"); MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D firstCoordinate = coordinates[0]; CLLocationCoordinate2D firstCoordinate = coordinates[0];
return firstCoordinate; return firstCoordinate;
} }
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds { - (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds); return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
mbgl::MultiLineString<double> multiLineString; mbgl::MultiLineString<double> multiLineString;
multiLineString.reserve(self.polylines.count); multiLineString.reserve(self.polylines.count);
for (MLNPolyline *polyline in self.polylines) { for (MLNPolyline *polyline in self.polylines) {
multiLineString.push_back([polyline lineString]); multiLineString.push_back([polyline lineString]);
} }
return multiLineString; return multiLineString;
} }
- (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]
(unsigned long)self.polylines.count, : self.subtitle,
MLNStringFromCoordinateBounds(self.overlayBounds)]; (unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds)];
} }
@end @end

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 {
return source; auto source =
std::make_unique<mbgl::style::RasterDEMSource>(identifier.UTF8String, urlOrTileset, tileSize);
return source;
} }
@end @end

View File

@@ -22,64 +22,78 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512;
@implementation MLNRasterTileSource @implementation MLNRasterTileSource
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL { - (instancetype)initWithIdentifier:(NSString *)identifier
// The style specification default is 512, but 256 is the expected value for configurationURL:(NSURL *)configurationURL {
// any tile set that would be accessed through a mapbox: URL and therefore // The style specification default is 512, but 256 is the expected value for
// any tile URL that this option currently affects. // any tile set that would be accessed through a mapbox: URL and therefore
BOOL isMapboxURL = ([configurationURL.scheme isEqualToString:@"mapbox"] // any tile URL that this option currently affects.
&& [configurationURL.host containsString:@"."] BOOL isMapboxURL =
&& (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"])); ([configurationURL.scheme isEqualToString:@"mapbox"] &&
CGFloat tileSize = isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize; [configurationURL.host containsString:@"."] &&
return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize]; (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"]));
CGFloat tileSize =
isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize;
return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL tileSize:(CGFloat)tileSize { - (instancetype)initWithIdentifier:(NSString *)identifier
NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString; configurationURL:(NSURL *)configurationURL
auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:configurationURLString.UTF8String tileSize:uint16_t(round(tileSize))]; tileSize:(CGFloat)tileSize {
return self = [super initWithPendingSource:std::move(source)]; NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString;
auto source = [self pendingSourceWithIdentifier:identifier
urlOrTileset:configurationURLString.UTF8String
tileSize:uint16_t(round(tileSize))];
return self = [super initWithPendingSource:std::move(source)];
} }
- (std::unique_ptr<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 {
return source; auto source =
std::make_unique<mbgl::style::RasterSource>(identifier.UTF8String, urlOrTileset, tileSize);
return source;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates options:(nullable NSDictionary<MLNTileSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
uint16_t tileSize = MLNRasterTileSourceRetinaTileSize; uint16_t tileSize = MLNRasterTileSourceRetinaTileSize;
if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) { if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) {
if (![tileSizeNumber isKindOfClass:[NSNumber class]]) { if (![tileSizeNumber isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."]; format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."];
}
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
return self = [super initWithPendingSource:std::move(source)]; urlOrTileset:tileSet
tileSize:tileSize];
return self = [super initWithPendingSource:std::move(source)];
} }
- (mbgl::style::RasterSource *)rawSource { - (mbgl::style::RasterSource *)rawSource {
return (mbgl::style::RasterSource *)super.rawSource; return (mbgl::style::RasterSource *)super.rawSource;
} }
- (NSURL *)configurationURL { - (NSURL *)configurationURL {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL(); auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil; return url ? [NSURL URLWithString:@(url->c_str())] : nil;
} }
- (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",
return nil; self.identifier);
} return nil;
}
auto attribution = self.rawSource->getAttribution(); auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil; return attribution ? @(attribution->c_str()) : nil;
} }
@end @end

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

@@ -3,36 +3,35 @@
@implementation MLNRenderingStats @implementation MLNRenderingStats
- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats { - (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats {
_encodingTime = stats.encodingTime; _encodingTime = stats.encodingTime;
_renderingTime = stats.renderingTime; _renderingTime = stats.renderingTime;
_numFrames = stats.numFrames; _numFrames = stats.numFrames;
_numDrawCalls = stats.numDrawCalls; _numDrawCalls = stats.numDrawCalls;
_totalDrawCalls = stats.totalDrawCalls; _totalDrawCalls = stats.totalDrawCalls;
_numCreatedTextures = stats.numCreatedTextures; _numCreatedTextures = stats.numCreatedTextures;
_numActiveTextures = stats.numActiveTextures; _numActiveTextures = stats.numActiveTextures;
_numTextureBindings = stats.numTextureBindings; _numTextureBindings = stats.numTextureBindings;
_numTextureUpdates = stats.numTextureUpdates; _numTextureUpdates = stats.numTextureUpdates;
_textureUpdateBytes = stats.textureUpdateBytes; _textureUpdateBytes = stats.textureUpdateBytes;
_totalBuffers = stats.totalBuffers; _totalBuffers = stats.totalBuffers;
_totalBufferObjs = stats.totalBufferObjs; _totalBufferObjs = stats.totalBufferObjs;
_bufferUpdates = stats.bufferUpdates; _bufferUpdates = stats.bufferUpdates;
_bufferObjUpdates = stats.bufferObjUpdates; _bufferObjUpdates = stats.bufferObjUpdates;
_bufferUpdateBytes = stats.bufferUpdateBytes; _bufferUpdateBytes = stats.bufferUpdateBytes;
_numIndexBuffers = stats.numIndexBuffers; _numIndexBuffers = stats.numIndexBuffers;
_indexUpdateBytes = stats.indexUpdateBytes; _indexUpdateBytes = stats.indexUpdateBytes;
_numVertexBuffers = stats.numVertexBuffers; _numVertexBuffers = stats.numVertexBuffers;
_vertexUpdateBytes = stats.vertexUpdateBytes; _vertexUpdateBytes = stats.vertexUpdateBytes;
_numUniformBuffers = stats.numUniformBuffers; _numUniformBuffers = stats.numUniformBuffers;
_numUniformUpdates = stats.numUniformUpdates; _numUniformUpdates = stats.numUniformUpdates;
_uniformUpdateBytes = stats.uniformUpdateBytes; _uniformUpdateBytes = stats.uniformUpdateBytes;
_memTextures = stats.memTextures; _memTextures = stats.memTextures;
_memBuffers = stats.memBuffers; _memBuffers = stats.memBuffers;
_memIndexBuffers = stats.memIndexBuffers; _memIndexBuffers = stats.memIndexBuffers;
_memVertexBuffers = stats.memVertexBuffers; _memVertexBuffers = stats.memVertexBuffers;
_memUniformBuffers = stats.memUniformBuffers; _memUniformBuffers = stats.memUniformBuffers;
_stencilClears = stats.stencilClears; _stencilClears = stats.stencilClears;
_stencilUpdates = stats.stencilUpdates; _stencilUpdates = stats.stencilUpdates;
} }
@end @end

View File

@@ -18,215 +18,231 @@
// MARK: - Internal // MARK: - Internal
- (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;
} }
+ (void)load { + (void)load {
// Read the initial configuration from Info.plist. // Read the initial configuration from Info.plist.
NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"]; NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"];
if (apiKey.length) { if (apiKey.length) {
self.apiKey = apiKey; self.apiKey = apiKey;
} }
NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"]; NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"];
// If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`. // If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`.
if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) { if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) {
[self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]]; [self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]];
[self tileServerOptionsChanged]; [self tileServerOptionsChanged];
} }
} }
+ (instancetype)sharedSettings { + (instancetype)sharedSettings {
#if TARGET_OS_OSX #if TARGET_OS_OSX
if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) { if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
return nil; return nil;
} }
#endif #endif
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
static MLNSettings *_sharedSettings; static MLNSettings *_sharedSettings;
void (^setupBlock)(void) = ^{ void (^setupBlock)(void) = ^{
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
_sharedSettings = [[self alloc] init]; _sharedSettings = [[self alloc] init];
}); });
}; };
if (![[NSThread currentThread] isMainThread]) { if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{ dispatch_sync(dispatch_get_main_queue(), ^{
setupBlock(); setupBlock();
}); });
} else { } else {
setupBlock(); setupBlock();
} }
return _sharedSettings; return _sharedSettings;
} }
+ (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;
} }
[MLNSettings sharedSettings].apiKey = apiKey; [MLNSettings sharedSettings].apiKey = apiKey;
} }
+ (NSString *)apiKey { + (NSString *)apiKey {
return [MLNSettings sharedSettings].apiKey; return [MLNSettings sharedSettings].apiKey;
} }
+ (void)setAPIBaseURL:(NSURL *)apiBaseURL { + (void)setAPIBaseURL:(NSURL *)apiBaseURL {
auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal; auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal;
NSString *baseUrlNSStr = apiBaseURL.absoluteString; NSString *baseUrlNSStr = apiBaseURL.absoluteString;
auto baseUrl = std::string([baseUrlNSStr UTF8String]); auto baseUrl = std::string([baseUrlNSStr UTF8String]);
[MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl); [MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl);
} }
+ (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;
} }
+ (void)setTileServerOptionsInternal:(mbgl::TileServerOptions)options { + (void)setTileServerOptionsInternal:(mbgl::TileServerOptions)options {
[MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions(); [MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions();
*[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone(); *[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone();
[self tileServerOptionsChanged]; [self tileServerOptionsChanged];
} }
+ (mbgl::TileServerOptions)tileServerOptionsInternal { + (mbgl::TileServerOptions)tileServerOptionsInternal {
auto options = [MLNSettings sharedSettings]; auto options = [MLNSettings sharedSettings];
return options.tileServerOptionsInternal->clone(); return options.tileServerOptionsInternal->clone();
} }
+ (void)tileServerOptionsChanged { + (void)tileServerOptionsChanged {
[MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString]; [MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString];
} }
+ (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;
case MLNMapLibre: case MLNMapLibre:
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()]; [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()];
break; break;
case MLNMapbox: case MLNMapbox:
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()]; [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()];
break; break;
default: default:
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()]; [MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()];
} }
} }
+ (void)setTileServerOptions:(MLNTileServerOptions*)options { + (void)setTileServerOptions:(MLNTileServerOptions *)options {
auto opts = mbgl::TileServerOptions()
.withBaseURL(std::string([options.baseURL UTF8String]))
.withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String]))
.withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String]));
auto opts = mbgl::TileServerOptions() opts.withSourceTemplate(std::string([options.sourceTemplate UTF8String]),
.withBaseURL(std::string([options.baseURL UTF8String])) std::string([options.sourceDomainName UTF8String]),
.withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String])) options.sourceVersionPrefix
.withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String])); ? std::string([options.sourceVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withSourceTemplate( opts.withStyleTemplate(std::string([options.styleTemplate UTF8String]),
std::string([options.sourceTemplate UTF8String]), std::string([options.styleDomainName UTF8String]),
std::string([options.sourceDomainName UTF8String]), options.styleVersionPrefix
options.sourceVersionPrefix ? std::string([options.sourceVersionPrefix UTF8String]) : std::optional<std::string>{}); ? std::string([options.styleVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withStyleTemplate( opts.withSpritesTemplate(std::string([options.spritesTemplate UTF8String]),
std::string([options.styleTemplate UTF8String]), std::string([options.spritesDomainName UTF8String]),
std::string([options.styleDomainName UTF8String]), options.spritesVersionPrefix
options.styleVersionPrefix ? std::string([options.styleVersionPrefix UTF8String]) : std::optional<std::string>{}); ? std::string([options.spritesVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withSpritesTemplate( opts.withGlyphsTemplate(std::string([options.glyphsTemplate UTF8String]),
std::string([options.spritesTemplate UTF8String]), std::string([options.glyphsDomainName UTF8String]),
std::string([options.spritesDomainName UTF8String]), options.glyphsVersionPrefix
options.spritesVersionPrefix ? std::string([options.spritesVersionPrefix UTF8String]) : std::optional<std::string>{}); ? std::string([options.glyphsVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withGlyphsTemplate( opts.withTileTemplate(std::string([options.tileTemplate UTF8String]),
std::string([options.glyphsTemplate UTF8String]), std::string([options.tileDomainName UTF8String]),
std::string([options.glyphsDomainName UTF8String]), options.tileVersionPrefix
options.glyphsVersionPrefix ? std::string([options.glyphsVersionPrefix UTF8String]) : std::optional<std::string>{}); ? std::string([options.tileVersionPrefix UTF8String])
: std::optional<std::string>{});
opts.withTileTemplate( std::vector<mbgl::util::DefaultStyle> defaultStyles;
std::string([options.tileTemplate UTF8String]), if (options.defaultStyles) {
std::string([options.tileDomainName UTF8String]), for (MLNDefaultStyle *objCStyle in options.defaultStyles) {
options.tileVersionPrefix ? std::string([options.tileVersionPrefix UTF8String]) : std::optional<std::string>{}); mbgl::util::DefaultStyle cppStyle(std::string([objCStyle.url.absoluteString UTF8String]),
std::string([objCStyle.name UTF8String]),
std::vector<mbgl::util::DefaultStyle> defaultStyles; objCStyle.version);
if (options.defaultStyles) { defaultStyles.push_back(cppStyle);
for (MLNDefaultStyle* objCStyle in options.defaultStyles) {
mbgl::util::DefaultStyle cppStyle(
std::string([objCStyle.url.absoluteString UTF8String]),
std::string([objCStyle.name UTF8String]),
objCStyle.version
);
defaultStyles.push_back(cppStyle);
}
} }
opts.withDefaultStyles(defaultStyles); }
opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String])); opts.withDefaultStyles(defaultStyles);
opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String]));
self.tileServerOptionsInternal = opts; self.tileServerOptionsInternal = opts;
} }
+ (MLNTileServerOptions*)tileServerOptions { + (MLNTileServerOptions *)tileServerOptions {
auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal; auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal;
if (cppOpts) { if (cppOpts) {
MLNTileServerOptions *retVal = [[MLNTileServerOptions alloc] init];
retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()];
retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()];
retVal.apiKeyParameterName =
[NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()];
MLNTileServerOptions* retVal = [[MLNTileServerOptions alloc] init]; retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()];
retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()]; retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()];
retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()]; retVal.sourceVersionPrefix =
retVal.apiKeyParameterName = [NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()]; cppOpts->sourceVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()]
: nil;
retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()]; retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()];
retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()]; retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()];
retVal.sourceVersionPrefix = cppOpts->sourceVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()] : nil; retVal.styleVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()]
: nil;
retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()]; retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()];
retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()]; retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()];
retVal.styleVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()] : nil; retVal.spritesVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()]
: nil;
retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()]; retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()];
retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()]; retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()];
retVal.spritesVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()] : nil; retVal.glyphsVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()]
: nil;
retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()]; retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()];
retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()]; retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()];
retVal.glyphsVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()] : nil; retVal.tileVersionPrefix =
cppOpts->styleVersionPrefix().has_value()
? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()]
: nil;
retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()]; std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles();
retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()];
retVal.tileVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()] : nil;
std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles(); NSMutableArray<MLNDefaultStyle *> *mglStyles = [[NSMutableArray<MLNDefaultStyle *> alloc] init];
for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) {
NSString *url = [NSString stringWithUTF8String:it->getUrl().c_str()];
auto *mglDefaultStyle = [[MLNDefaultStyle alloc] init];
mglDefaultStyle.url = [NSURL URLWithString:url];
mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()];
mglDefaultStyle.version = it->getCurrentVersion();
[mglStyles addObject:mglDefaultStyle];
NSMutableArray<MLNDefaultStyle*>* mglStyles = [[NSMutableArray<MLNDefaultStyle*> alloc] init]; // set default style name
for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) { if (it->getName() == cppOpts->defaultStyle()) {
NSString* url = [NSString stringWithUTF8String:it->getUrl().c_str()]; retVal.defaultStyle = mglDefaultStyle;
}
auto* mglDefaultStyle = [[MLNDefaultStyle alloc] init];
mglDefaultStyle.url = [NSURL URLWithString:url];
mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()];
mglDefaultStyle.version = it->getCurrentVersion();
[mglStyles addObject:mglDefaultStyle];
// set default style name
if (it->getName() == cppOpts->defaultStyle()) {
retVal.defaultStyle = mglDefaultStyle;
}
}
retVal.defaultStyles = mglStyles;
return retVal;
} }
return nil; retVal.defaultStyles = mglStyles;
return retVal;
}
return nil;
} }
@end @end

View File

@@ -2,110 +2,110 @@
#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>
bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) { bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude; return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
} }
@implementation MLNShape @implementation MLNShape
+ (nullable MLNShape *)shapeWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError * _Nullable *)outError { + (nullable MLNShape *)shapeWithData:(NSData *)data
NSString *string = [[NSString alloc] initWithData:data encoding:encoding]; encoding:(NSStringEncoding)encoding
if (!string) { error:(NSError *_Nullable *)outError {
if (outError) { NSString *string = [[NSString alloc] initWithData:data encoding:encoding];
*outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil]; if (!string) {
} if (outError) {
return nil; *outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil];
} }
return nil;
}
try { try {
const auto geojson = mapbox::geojson::parse(string.UTF8String); const auto geojson = mapbox::geojson::parse(string.UTF8String);
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;
}
} }
- (mbgl::GeoJSON)geoJSONObject { - (mbgl::GeoJSON)geoJSONObject {
return self.geometryObject; return self.geometryObject;
} }
- (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>();
} }
- (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding { - (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding {
auto geometry = self.geoJSONObject; auto geometry = self.geoJSONObject;
NSString *string = @(mapbox::geojson::stringify(geometry).c_str()); NSString *string = @(mapbox::geojson::stringify(geometry).c_str());
return [string dataUsingEncoding:NSUTF8StringEncoding]; return [string dataUsingEncoding:NSUTF8StringEncoding];
} }
+ (BOOL)supportsSecureCoding + (BOOL)supportsSecureCoding {
{ return YES;
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super init]) {
_title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"];
_subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
#if !TARGET_OS_IPHONE
_toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"];
#endif
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_title forKey:@"title"];
[coder encodeObject:_subtitle forKey:@"subtitle"];
#if !TARGET_OS_IPHONE
[coder encodeObject:_toolTip forKey:@"toolTip"];
#endif
}
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES; return YES;
} }
id<MLNAnnotation> annotation = other;
- (instancetype)initWithCoder:(NSCoder *)coder
{
if (self = [super init]) {
_title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"];
_subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
#if !TARGET_OS_IPHONE
_toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"];
#endif
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_title forKey:@"title"];
[coder encodeObject:_subtitle forKey:@"subtitle"];
#if !TARGET_OS_IPHONE
[coder encodeObject:_toolTip forKey:@"toolTip"];
#endif
}
- (BOOL)isEqual:(id)other
{
if (other == self) { return YES; }
id <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;
#endif #endif
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate - (CLLocationCoordinate2D)coordinate {
{ [NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"];
[NSException raise:MLNAbstractClassException return kCLLocationCoordinate2DInvalid;
format:@"MLNShape is an abstract class"];
return kCLLocationCoordinate2DInvalid;
} }
@end @end

View File

@@ -1,85 +1,85 @@
#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>
@implementation MLNShapeCollection @implementation MLNShapeCollection
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
return YES; return YES;
} }
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape *> *)shapes { + (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape *> *)shapes {
return [[self alloc] initWithShapes:shapes]; return [[self alloc] initWithShapes:shapes];
} }
- (instancetype)initWithShapes:(NSArray<MLNShape *> *)shapes { - (instancetype)initWithShapes:(NSArray<MLNShape *> *)shapes {
MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count); MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count);
if (self = [super init]) { if (self = [super init]) {
_shapes = shapes.copy; _shapes = shapes.copy;
} }
return self; return self;
} }
- (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 =
_shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"]; [NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNShape class] ]];
} _shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"];
return self; }
return self;
} }
- (void)encodeWithCoder:(NSCoder *)coder { - (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder]; [super encodeWithCoder:coder];
[coder encodeObject:_shapes forKey:@"shapes"]; [coder encodeObject:_shapes forKey:@"shapes"];
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (self == other) return YES; if (self == other) return YES;
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 {
NSUInteger hash = [super hash]; NSUInteger hash = [super hash];
for (MLNShape *shape in _shapes) { for (MLNShape *shape in _shapes) {
hash += [shape hash]; hash += [shape hash];
} }
return hash; return hash;
} }
- (CLLocationCoordinate2D)coordinate { - (CLLocationCoordinate2D)coordinate {
return _shapes.firstObject.coordinate; return _shapes.firstObject.coordinate;
} }
- (NSDictionary *)geoJSONDictionary { - (NSDictionary *)geoJSONDictionary {
return @{@"type": @"GeometryCollection", return @{@"type" : @"GeometryCollection", @"geometries" : [self geometryCollection]};
@"geometries": [self geometryCollection]};
} }
- (NSArray *)geometryCollection { - (NSArray *)geometryCollection {
NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count]; NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count];
for (id shape in self.shapes) { for (id shape in self.shapes) {
NSDictionary *geometry = [shape geoJSONDictionary]; NSDictionary *geometry = [shape geoJSONDictionary];
[geometries addObject:geometry]; [geometries addObject:geometry];
} }
return [geometries copy]; return [geometries copy];
} }
- (mbgl::Geometry<double>)geometryObject { - (mbgl::Geometry<double>)geometryObject {
mapbox::geojson::geometry_collection collection; mapbox::geojson::geometry_collection collection;
collection.reserve(self.shapes.count); collection.reserve(self.shapes.count);
for (MLNShape *shape in self.shapes) { for (MLNShape *shape in self.shapes) {
collection.push_back([shape geometryObject]); collection.push_back([shape geometryObject]);
} }
return collection; return collection;
} }
@end @end

View File

@@ -1,139 +1,151 @@
#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>
@end @end
@implementation MLNShapeOfflineRegion { @implementation MLNShapeOfflineRegion {
NSURL *_styleURL; NSURL *_styleURL;
} }
@synthesize styleURL = _styleURL; @synthesize styleURL = _styleURL;
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
-(NSDictionary *)offlineStartEventAttributes { - (NSDictionary *)offlineStartEventAttributes {
return @{}; return @{};
} }
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
return YES; return YES;
} }
- (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
if (self = [super init]) { fromZoomLevel:(double)minimumZoomLevel
if (!styleURL) { toZoomLevel:(double)maximumZoomLevel {
styleURL = [MLNStyle defaultStyleURL]; MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL,
} shape, minimumZoomLevel, maximumZoomLevel);
if (self = [super init]) {
if (!styleURL.scheme) { if (!styleURL) {
[NSException raise:@"Invalid style URL" format: styleURL = [MLNStyle defaultStyleURL];
@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.",
NSStringFromClass([self class])];
}
_styleURL = styleURL;
_shape = shape;
_minimumZoomLevel = minimumZoomLevel;
_maximumZoomLevel = maximumZoomLevel;
_includesIdeographicGlyphs = NO;
} }
return self;
if (!styleURL.scheme) {
[NSException raise:@"Invalid style URL"
format:@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.",
NSStringFromClass([self class])];
}
_styleURL = styleURL;
_shape = shape;
_minimumZoomLevel = minimumZoomLevel;
_maximumZoomLevel = maximumZoomLevel;
_includesIdeographicGlyphs = NO;
}
return self;
} }
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineGeometryRegionDefinition &)definition { - (instancetype)initWithOfflineRegionDefinition:
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; (const mbgl::OfflineGeometryRegionDefinition &)definition {
MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry); NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry);
result.includesIdeographicGlyphs = definition.includeIdeographs; MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL
return result; shape:shape
fromZoomLevel:definition.minZoom
toZoomLevel:definition.maxZoom];
result.includesIdeographicGlyphs = definition.includeIdeographs;
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
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; shape:shape
return result; fromZoomLevel:minimumZoomLevel
toZoomLevel:maximumZoomLevel];
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
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"]; [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
} }
- (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 =
result.includesIdeographicGlyphs = _includesIdeographicGlyphs; [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
return result; shape:_shape
fromZoomLevel:_minimumZoomLevel
toZoomLevel:_maximumZoomLevel];
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
return result;
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (other == self) { if (other == self) {
return YES; return YES;
} }
if (![other isKindOfClass:[self class]]) { if (![other isKindOfClass:[self class]]) {
return NO; return NO;
} }
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,169 +1,188 @@
#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(
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>(); NSDictionary<MLNShapeSourceOption, id> *options) {
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) { if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
} }
geoJSONOptions->minzoom = value.integerValue; geoJSONOptions->minzoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
geoJSONOptions->maxzoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
geoJSONOptions->buffer = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
geoJSONOptions->tolerance = value.doubleValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
}
geoJSONOptions->clusterRadius = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
}
geoJSONOptions->clusterMinPoints = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
}
geoJSONOptions->clusterMaxZoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
}
geoJSONOptions->cluster = value.boolValue;
}
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
[NSException
raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString "
@"as a key and an array containing two NSExpression objects as a value."];
} }
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) { NSEnumerator *stringEnumerator = [value keyEnumerator];
if (![value isKindOfClass:[NSNumber class]]) { NSString *key;
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; while (key = [stringEnumerator nextObject]) {
} NSArray *expressionsArray = value[key];
geoJSONOptions->maxzoom = value.integerValue; if (![expressionsArray isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be "
@"an array containing two objects."];
}
// Check that the array has 2 values. One should be a the reduce expression and one should be
// the map expression.
if ([expressionsArray count] != 2) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value requires array of "
@"two objects."];
}
// reduceExpression should be a valid NSExpression
NSExpression *reduceExpression = expressionsArray[0];
if (![reduceExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties array value requires two "
@"expression objects."];
}
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
if (!reduce) {
[NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
}
// mapExpression should be a valid NSExpression
NSExpression *mapExpression = expressionsArray[1];
if (![mapExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value must contain a "
@"valid NSExpression."];
}
auto map = MLNClusterPropertyFromNSExpression(mapExpression);
if (!map) {
[NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
}
std::string keyString = std::string([key UTF8String]);
geoJSONOptions->clusterProperties.emplace(keyString,
std::make_pair(std::move(map), std::move(reduce)));
} }
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) { if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."]; format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."];
}
geoJSONOptions->buffer = value.integerValue;
} }
geoJSONOptions->lineMetrics = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) { if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) {
if (![value isKindOfClass:[NSNumber class]]) { if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."]; format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."];
}
geoJSONOptions->tolerance = value.doubleValue;
} }
geoJSONOptions->synchronousUpdate = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) { return geoJSONOptions;
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
}
geoJSONOptions->clusterRadius = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
}
geoJSONOptions->clusterMinPoints = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
}
geoJSONOptions->clusterMaxZoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
}
geoJSONOptions->cluster = value.boolValue;
}
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."];
}
NSEnumerator *stringEnumerator = [value keyEnumerator];
NSString *key;
while (key = [stringEnumerator nextObject]) {
NSArray *expressionsArray = value[key];
if (![expressionsArray isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."];
}
// Check that the array has 2 values. One should be a the reduce expression and one should be the map expression.
if ([expressionsArray count] != 2) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value requires array of two objects."];
}
// reduceExpression should be a valid NSExpression
NSExpression *reduceExpression = expressionsArray[0];
if (![reduceExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties array value requires two expression objects."];
}
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
if (!reduce) {
[NSException raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
}
// mapExpression should be a valid NSExpression
NSExpression *mapExpression = expressionsArray[1];
if (![mapExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value must contain a valid NSExpression."];
}
auto map = MLNClusterPropertyFromNSExpression(mapExpression);
if (!map) {
[NSException raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
}
std::string keyString = std::string([key UTF8String]);
geoJSONOptions->clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce)));
}
}
if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."];
}
geoJSONOptions->lineMetrics = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."];
}
geoJSONOptions->synchronousUpdate = value.boolValue;
}
return geoJSONOptions;
} }
@interface MLNShapeSource () @interface MLNShapeSource ()
@@ -175,203 +194,225 @@ 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
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); URL:(NSURL *)url
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions)); options:(NSDictionary<NSString *, id> *)options {
if (self = [super initWithPendingSource:std::move(source)]) { auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
self.URL = url; auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
} std::move(geoJSONOptions));
return self; if (self = [super initWithPendingSource:std::move(source)]) {
self.URL = url;
}
return self;
} }
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MLNShape *)shape options:(NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options); shape:(nullable MLNShape *)shape
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions)); options:(NSDictionary<MLNShapeSourceOption, id> *)options {
if (self = [super initWithPendingSource:std::move(source)]) { auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
if ([shape isMemberOfClass:[MLNShapeCollection class]]) { auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
static dispatch_once_t onceToken; std::move(geoJSONOptions));
dispatch_once(&onceToken, ^{ if (self = [super initWithPendingSource:std::move(source)]) {
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes." if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
@"Use MLNShapeCollectionFeature to retain attributes instead." static dispatch_once_t onceToken;
@"This will be logged only once."); dispatch_once(&onceToken, ^{
}); NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
} @"Use MLNShapeCollectionFeature to retain attributes instead."
self.shape = shape; @"This will be logged only once.");
});
} }
return self; self.shape = shape;
}
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
if (![feature conformsToProtocol:@protocol(MLNFeature)]) { options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
[NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MLNFeature protocol.", feature]; for (id<MLNFeature> feature in features) {
} if (![feature conformsToProtocol:@protocol(MLNFeature)]) {
[NSException raise:NSInvalidArgumentException
format:@"The object %@ included in the features argument does not conform to the "
@"MLNFeature protocol.",
feature];
} }
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:features]; }
return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options]; MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:features];
return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options];
} }
- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NSArray<MLNShape *> *)shapes options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options { - (instancetype)initWithIdentifier:(NSString *)identifier
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes]; shapes:(NSArray<MLNShape *> *)shapes
return [self initWithIdentifier:identifier shape:shapeCollection options:options]; options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
return [self initWithIdentifier:identifier shape:shapeCollection options:options];
} }
- (mbgl::style::GeoJSONSource *)rawSource { - (mbgl::style::GeoJSONSource *)rawSource {
return (mbgl::style::GeoJSONSource *)super.rawSource; return (mbgl::style::GeoJSONSource *)super.rawSource;
} }
- (NSURL *)URL { - (NSURL *)URL {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL(); auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil; return url ? [NSURL URLWithString:@(url->c_str())] : nil;
} }
- (void)setURL:(NSURL *)url { - (void)setURL:(NSURL *)url {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
if (url) { if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_shape = nil; _shape = nil;
} else { } else {
self.shape = nil; self.shape = nil;
} }
} }
- (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) {
optionalFilter = predicate.mgl_filter; optionalFilter = predicate.mgl_filter;
} }
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>)
MLNAssertStyleSourceIsValid(); featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster
std::optional<mbgl::FeatureExtensionValue> extensionValue; extension:(std::string)extension
options:(const std::map<std::string, mbgl::Value>)options {
MLNAssertStyleSourceIsValid();
std::optional<mbgl::FeatureExtensionValue> extensionValue;
// Check parameters // Check parameters
if (!self.rawSource || !self.stylable || !cluster) { if (!self.rawSource || !self.stylable || !cluster) {
return extensionValue;
}
auto geoJSON = [cluster geoJSONObject];
if (!geoJSON.is<mbgl::GeoJSONFeature>()) {
MLNAssert(0, @"cluster geoJSON object is not a feature.");
return extensionValue;
}
auto clusterFeature = geoJSON.get<mbgl::GeoJSONFeature>();
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable;
extensionValue = mapView.renderer->queryFeatureExtensions(self.rawSource->getID(),
clusterFeature,
"supercluster",
extension,
options);
}
return extensionValue; return extensionValue;
}
auto geoJSON = [cluster geoJSONObject];
if (!geoJSON.is<mbgl::GeoJSONFeature>()) {
MLNAssert(0, @"cluster geoJSON object is not a feature.");
return extensionValue;
}
auto clusterFeature = geoJSON.get<mbgl::GeoJSONFeature>();
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable;
extensionValue = mapView.renderer->queryFeatureExtensions(
self.rawSource->getID(), clusterFeature, "supercluster", extension, options);
}
return extensionValue;
} }
- (NSArray<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 @[];
} }
if (!featureExtension->is<mbgl::FeatureCollection>()) { if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[]; return @[];
} }
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>(); std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
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 @[];
} }
if (!featureExtension->is<mbgl::FeatureCollection>()) { if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[]; return @[];
} }
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>(); std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MLNFeaturesFromMBGLFeatures(leaves); return MLNFeaturesFromMBGLFeatures(leaves);
} }
- (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"
if (!featureExtension) { options:{}];
return -1.0;
}
if (!featureExtension->is<mbgl::Value>()) {
return -1.0;
}
auto value = featureExtension->get<mbgl::Value>();
if (value.is<uint64_t>()) {
auto zoom = value.get<uint64_t>();
return static_cast<double>(zoom);
}
if (!featureExtension) {
return -1.0; return -1.0;
}
if (!featureExtension->is<mbgl::Value>()) {
return -1.0;
}
auto value = featureExtension->get<mbgl::Value>();
if (value.is<uint64_t>()) {
auto zoom = value.get<uint64_t>();
return static_cast<double>(zoom);
}
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 =
withString:@" " [description stringByReplacingOccurrencesOfString:@"\\s+"
options:NSRegularExpressionSearch withString:@" "
range:NSMakeRange(0, description.length)]; options:NSRegularExpressionSearch
range:NSMakeRange(0, description.length)];
printf("%*s%s\n", (int)indent, "", log.UTF8String); printf("%*s%s\n", (int)indent, "", log.UTF8String);
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster); MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
if (cluster) { 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];
}
} }
}
} }
@end @end

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,95 +15,102 @@ 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
@implementation MLNSource { @implementation MLNSource {
std::unique_ptr<mbgl::style::Source> _pendingSource; std::unique_ptr<mbgl::style::Source> _pendingSource;
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]) {
{ _identifier = [identifier copy];
if (self = [super init]) { }
_identifier = [identifier copy]; return self;
}
return self;
} }
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource stylable:(id <MLNStylable>)stylable { - (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource
NSString *identifier = @(rawSource->getID().c_str()); stylable:(id<MLNStylable>)stylable {
if (self = [self initWithIdentifier:identifier]) { NSString *identifier = @(rawSource->getID().c_str());
_weakSource = rawSource->makeWeakPtr(); if (self = [self initWithIdentifier:identifier]) {
rawSource->peer = SourceWrapper { self }; _weakSource = rawSource->makeWeakPtr();
_stylable = stylable; rawSource->peer = SourceWrapper{self};
} _stylable = stylable;
return self; }
return self;
} }
- (mbgl::style::Source *)rawSource - (mbgl::style::Source *)rawSource {
{ return _weakSource.get();
return _weakSource.get();
} }
- (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource { - (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource {
if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) { if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) {
_pendingSource = std::move(pendingSource); _pendingSource = std::move(pendingSource);
} }
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
MLNAssertStyleSourceIsValid(); error:(NSError *__nullable *__nullable)outError {
BOOL removed = NO; MLNAssertStyleSourceIsValid();
BOOL removed = NO;
if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) { if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) {
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String);
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String); if (removedSource) {
removed = YES;
if (removedSource) { _pendingSource = std::move(removedSource);
removed = YES; _stylable = nil;
_pendingSource = std::move(removedSource);
_stylable = nil;
} else if (outError) {
NSString *localizedDescription = [NSString stringWithFormat:
NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil, @"The source “%@” can’t be removed while it is in use.", @"User-friendly error description; first placeholder is the source’s identifier"),
self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIsInUseCannotRemove
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }];
}
} else if (outError) { } else if (outError) {
// TODO: Consider raising an exception here NSString *localizedDescription = [NSString
NSString *localizedDescription = [NSString stringWithFormat: 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"), NSLocalizedStringWithDefaultValue(
self.identifier]; @"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil,
@"The source “%@” can’t be removed while it is in use.",
@"User-friendly error description; first placeholder is the source’s identifier"),
self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIdentifierMismatch code:MLNErrorCodeSourceIsInUseCannotRemove
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }]; userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
} }
} else if (outError) {
// TODO: Consider raising an exception here
NSString *localizedDescription =
[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(
@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil,
@"The source can’t be removed because its identifier, “%@”, "
@"belongs to a different source in this style.",
@"User-friendly error description"),
self.identifier];
return removed; *outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIdentifierMismatch
userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
}
return removed;
} }
- (NSString *)description { - (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

File diff suppressed because it is too large Load Diff

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";
@@ -13,111 +13,101 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc
@end @end
@implementation MLNStyleLayer { @implementation MLNStyleLayer {
std::unique_ptr<mbgl::style::Layer> _pendingLayer; std::unique_ptr<mbgl::style::Layer> _pendingLayer;
mapbox::base::WeakPtr<mbgl::style::Layer> _weakLayer; mapbox::base::WeakPtr<mbgl::style::Layer> _weakLayer;
} }
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer { - (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer {
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;
} }
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer { - (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
if (self = [self initWithRawLayer:pendingLayer.get()]) { if (self = [self initWithRawLayer:pendingLayer.get()]) {
_pendingLayer = std::move(pendingLayer); _pendingLayer = std::move(pendingLayer);
} }
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.",
"to the style more than once is invalid.", self, style]; self, style];
} }
if (otherLayer) { if (otherLayer) {
const std::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String}; const std::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String};
style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId); style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId);
} else { } else {
style.rawStyle->addLayer(std::move(_pendingLayer)); style.rawStyle->addLayer(std::move(_pendingLayer));
} }
} }
- (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,
NSStringFromClass([self class]), (void *)self, self.identifier, self.identifier, self.visible ? @"YES" : @"NO"];
self.visible ? @"YES" : @"NO"]; } else {
} return
else { [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>",
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>", NSStringFromClass([self class]), (void *)self, self.identifier];
NSStringFromClass([self class]), (void *)self, self.identifier]; }
}
} }
@end @end

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"
@@ -19,138 +19,138 @@ namespace mbgl {
LayerManagerDarwin::LayerManagerDarwin() { LayerManagerDarwin::LayerManagerDarwin() {
#if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME) #if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>());
#elif !defined(MBGL_LAYER_FILL_DISABLE_ALL) #elif !defined(MBGL_LAYER_FILL_DISABLE_ALL)
addLayerType(std::make_unique<FillStyleLayerPeerFactory>()); addLayerType(std::make_unique<FillStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_LINE_DISABLE_RUNTIME) #if defined(MBGL_LAYER_LINE_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<LineLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<LineLayerFactory>());
#elif !defined(MBGL_LAYER_LINE_DISABLE_ALL) #elif !defined(MBGL_LAYER_LINE_DISABLE_ALL)
addLayerType(std::make_unique<LineStyleLayerPeerFactory>()); addLayerType(std::make_unique<LineStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_CIRCLE_DISABLE_RUNTIME) #if defined(MBGL_LAYER_CIRCLE_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<CircleLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<CircleLayerFactory>());
#elif !defined(MBGL_LAYER_CIRCLE_DISABLE_ALL) #elif !defined(MBGL_LAYER_CIRCLE_DISABLE_ALL)
addLayerType(std::make_unique<CircleStyleLayerPeerFactory>()); addLayerType(std::make_unique<CircleStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_SYMBOL_DISABLE_RUNTIME) #if defined(MBGL_LAYER_SYMBOL_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<SymbolLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<SymbolLayerFactory>());
#elif !defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) #elif !defined(MBGL_LAYER_SYMBOL_DISABLE_ALL)
addLayerType(std::make_unique<SymbolStyleLayerPeerFactory>()); addLayerType(std::make_unique<SymbolStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_RASTER_DISABLE_RUNTIME) #if defined(MBGL_LAYER_RASTER_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<RasterLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<RasterLayerFactory>());
#elif !defined(MBGL_LAYER_RASTER_DISABLE_ALL) #elif !defined(MBGL_LAYER_RASTER_DISABLE_ALL)
addLayerType(std::make_unique<RasterStyleLayerPeerFactory>()); addLayerType(std::make_unique<RasterStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_BACKGROUND_DISABLE_RUNTIME) #if defined(MBGL_LAYER_BACKGROUND_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<BackgroundLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<BackgroundLayerFactory>());
#elif !defined(MBGL_LAYER_BACKGROUND_DISABLE_ALL) #elif !defined(MBGL_LAYER_BACKGROUND_DISABLE_ALL)
addLayerType(std::make_unique<BackgroundStyleLayerPeerFactory>()); addLayerType(std::make_unique<BackgroundStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_HILLSHADE_DISABLE_RUNTIME) #if defined(MBGL_LAYER_HILLSHADE_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<HillshadeLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<HillshadeLayerFactory>());
#elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL) #elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL)
addLayerType(std::make_unique<HillshadeStyleLayerPeerFactory>()); addLayerType(std::make_unique<HillshadeStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME) #if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<FillExtrusionLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<FillExtrusionLayerFactory>());
#elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL) #elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL)
addLayerType(std::make_unique<FillExtrusionStyleLayerPeerFactory>()); addLayerType(std::make_unique<FillExtrusionStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_HEATMAP_DISABLE_RUNTIME) #if defined(MBGL_LAYER_HEATMAP_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<HeatmapLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<HeatmapLayerFactory>());
#elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL) #elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL)
addLayerType(std::make_unique<HeatmapStyleLayerPeerFactory>()); addLayerType(std::make_unique<HeatmapStyleLayerPeerFactory>());
#endif #endif
#if defined(MBGL_LAYER_CUSTOM_DISABLE_RUNTIME) #if defined(MBGL_LAYER_CUSTOM_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<CustomLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<CustomLayerFactory>());
#elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) #elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL)
addLayerType(std::make_unique<CustomStyleLayerPeerFactory>()); addLayerType(std::make_unique<CustomStyleLayerPeerFactory>());
#endif #endif
#if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME) #if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME)
addLayerTypeCoreOnly(std::make_unique<CustomDrawableLayerFactory>()); addLayerTypeCoreOnly(std::make_unique<CustomDrawableLayerFactory>());
#elif !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL) #elif !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL)
addLayerType(std::make_unique<CustomDrawableStyleLayerPeerFactory>()); addLayerType(std::make_unique<CustomDrawableStyleLayerPeerFactory>());
#endif #endif
} }
LayerManagerDarwin::~LayerManagerDarwin() = default; LayerManagerDarwin::~LayerManagerDarwin() = default;
MLNStyleLayer* LayerManagerDarwin::createPeer(style::Layer* layer) { MLNStyleLayer* LayerManagerDarwin::createPeer(style::Layer* layer) {
if (auto* factory = getPeerFactory(layer->getTypeInfo())) { if (auto* factory = getPeerFactory(layer->getTypeInfo())) {
return factory->createPeer(layer); return factory->createPeer(layer);
} }
return nullptr; return nullptr;
} }
void LayerManagerDarwin::addLayerType(std::unique_ptr<LayerPeerFactory> factory) { void LayerManagerDarwin::addLayerType(std::unique_ptr<LayerPeerFactory> factory) {
NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr, NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr,
@"A layer factory with the given info is already added."); @"A layer factory with the given info is already added.");
registerCoreFactory(factory->getCoreLayerFactory()); registerCoreFactory(factory->getCoreLayerFactory());
peerFactories.emplace_back(std::move(factory)); peerFactories.emplace_back(std::move(factory));
} }
void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr<LayerFactory> factory) { void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr<LayerFactory> factory) {
NSCAssert(getFactory(factory->getTypeInfo()) == nullptr, NSCAssert(getFactory(factory->getTypeInfo()) == nullptr,
@"A layer factory with the given info is already added."); @"A layer factory with the given info is already added.");
registerCoreFactory(factory.get()); registerCoreFactory(factory.get());
coreFactories.emplace_back(std::move(factory)); coreFactories.emplace_back(std::move(factory));
} }
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(),
typeToFactory.emplace(std::make_pair(std::move(type), factory)); @"A layer type can be registered only once.");
} typeToFactory.emplace(std::make_pair(std::move(type), factory));
}
} }
LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) { 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();
}
} }
return nullptr; }
return nullptr;
} }
LayerFactory* LayerManagerDarwin::getFactory(const std::string& type) noexcept { LayerFactory* LayerManagerDarwin::getFactory(const std::string& type) noexcept {
auto search = typeToFactory.find(type); auto search = typeToFactory.find(type);
return (search != typeToFactory.end()) ? search->second : nullptr; return (search != typeToFactory.end()) ? search->second : nullptr;
} }
LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* info) noexcept { LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* info) noexcept {
if (LayerPeerFactory* peerFactory = getPeerFactory(info)) { if (LayerPeerFactory* peerFactory = getPeerFactory(info)) {
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();
}
} }
}
return nullptr; return nullptr;
} }
// static // static
LayerManagerDarwin* LayerManagerDarwin::get() noexcept { LayerManagerDarwin* LayerManagerDarwin::get() noexcept {
static LayerManagerDarwin impl; static LayerManagerDarwin impl;
return &impl; return &impl;
} }
// 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;
#endif #endif
} // namespace mbgl } // namespace mbgl

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(
NSExpression *expression) {
if (!expression) {
return nullptr;
}
std::unique_ptr<mbgl::style::expression::Expression> MLNClusterPropertyFromNSExpression(NSExpression *expression) { NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
if (!expression) {
return nullptr;
}
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); }
@@ -390,7 +390,7 @@ class MLNStyleValueTransformer {
/// Converts all types of mbgl property values into an equivalent NSExpression. /// Converts all types of mbgl property values into an equivalent NSExpression.
class PropertyExpressionEvaluator { class PropertyExpressionEvaluator {
public: public:
NSExpression *operator()(const mbgl::style::Undefined) const { return nil; } NSExpression *operator()(const mbgl::style::Undefined) const { return nil; }
NSExpression *operator()(const MBGLType &value) const { NSExpression *operator()(const MBGLType &value) const {

View File

@@ -1,143 +1,159 @@
#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
@implementation MLNTilePyramidOfflineRegion { @implementation MLNTilePyramidOfflineRegion {
NSURL *_styleURL; NSURL *_styleURL;
} }
@synthesize styleURL = _styleURL; @synthesize styleURL = _styleURL;
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs; @synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
-(NSDictionary *)offlineStartEventAttributes { - (NSDictionary *)offlineStartEventAttributes {
return @{}; return @{};
} }
+ (BOOL)supportsSecureCoding { + (BOOL)supportsSecureCoding {
return YES; return YES;
} }
- (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
if (self = [super init]) { fromZoomLevel:(double)minimumZoomLevel
if (!styleURL) { toZoomLevel:(double)maximumZoomLevel {
styleURL = [MLNStyle defaultStyleURL]; MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL,
} MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel);
if (self = [super init]) {
if (!styleURL.scheme) { if (!styleURL) {
[NSException raise:MLNInvalidStyleURLException format: styleURL = [MLNStyle defaultStyleURL];
@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.",
NSStringFromClass([self class])];
}
_styleURL = styleURL;
_bounds = bounds;
_minimumZoomLevel = minimumZoomLevel;
_maximumZoomLevel = maximumZoomLevel;
_includesIdeographicGlyphs = NO;
} }
return self;
if (!styleURL.scheme) {
[NSException raise:MLNInvalidStyleURLException
format:@"%@ does not support setting a relative file URL as the style URL. "
@"To download the online resources required by this style, "
@"specify a URL to an online copy of this style. "
@"For Mapbox-hosted styles, use the mapbox: scheme.",
NSStringFromClass([self class])];
}
_styleURL = styleURL;
_bounds = bounds;
_minimumZoomLevel = minimumZoomLevel;
_maximumZoomLevel = maximumZoomLevel;
_includesIdeographicGlyphs = NO;
}
return self;
} }
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineTilePyramidRegionDefinition &)definition { - (instancetype)initWithOfflineRegionDefinition:
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())]; (const mbgl::OfflineTilePyramidRegionDefinition &)definition {
MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds); NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom]; MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds);
result.includesIdeographicGlyphs = definition.includeIdeographs; MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
return result; bounds:bounds
fromZoomLevel:definition.minZoom
toZoomLevel:definition.maxZoom];
result.includesIdeographicGlyphs = definition.includeIdeographs;
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 =
[coder decodeDoubleForKey:@"southWestLongitude"]); CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"],
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"], [coder decodeDoubleForKey:@"southWestLongitude"]);
[coder decodeDoubleForKey:@"northEastLongitude"]); CLLocationCoordinate2D ne =
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne); CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"],
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"]; [coder decodeDoubleForKey:@"northEastLongitude"]);
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"]; MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne);
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel]; MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"]; bounds:bounds
return result; fromZoomLevel:minimumZoomLevel
toZoomLevel:maximumZoomLevel];
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
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"]; [coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"];
[coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"]; [coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"];
[coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"]; [coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"]; [coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"]; [coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
} }
- (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 =
result.includesIdeographicGlyphs = _includesIdeographicGlyphs; [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
return result; bounds:_bounds
fromZoomLevel:_minimumZoomLevel
toZoomLevel:_maximumZoomLevel];
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
return result;
} }
- (BOOL)isEqual:(id)other { - (BOOL)isEqual:(id)other {
if (other == self) { if (other == self) {
return YES; return YES;
} }
if (![other isKindOfClass:[self class]]) { if (![other isKindOfClass:[self class]]) {
return NO; return NO;
} }
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,164 +8,180 @@
#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;
} }
- (NSArray<MLNAttributionInfo *> *)attributionInfos { - (NSArray<MLNAttributionInfo *> *)attributionInfos {
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
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString linkColor:(nullable MLNColor *)linkColor {
fontSize:fontSize return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
linkColor:linkColor]; fontSize:fontSize
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(
mbgl::Tileset tileSet; NSArray<NSString *> *tileURLTemplates,
NSDictionary<MLNTileSourceOption, id> *_Nullable options) {
mbgl::Tileset tileSet;
for (NSString *tileURLTemplate in tileURLTemplates) { for (NSString *tileURLTemplate in tileURLTemplates) {
tileSet.tiles.push_back(tileURLTemplate.UTF8String); tileSet.tiles.push_back(tileURLTemplate.UTF8String);
}
// set the minimum / maximum zoom range to the values specified by this class if they
// were set. otherwise, use the core objects default values
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
}
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
}
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than "
@"MLNTileSourceOptionMaximumZoomLevel."];
}
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
if (![coordinateBounds isKindOfClass:[NSValue class]] &&
strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue "
@"containing an MLNCoordinateBounds."];
}
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
}
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
}
tileSet.attribution = attribution.UTF8String;
}
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
if (![attributionInfos isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
} }
// set the minimum / maximum zoom range to the values specified by this class if they NSAttributedString *attributedString =
// were set. otherwise, use the core objects default values [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
}
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
}
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than MLNTileSourceOptionMaximumZoomLevel."];
}
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
if (![coordinateBounds isKindOfClass:[NSValue class]]
&& strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue containing an MLNCoordinateBounds."];
}
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
}
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
}
tileSet.attribution = attribution.UTF8String;
}
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
if (![attributionInfos isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
}
NSAttributedString *attributedString = [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
#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 :
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange documentAttributes:documentAttributes error:NULL]; @[ @"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p" ],
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; };
tileSet.attribution = html.UTF8String; NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange
} documentAttributes:documentAttributes
error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
tileSet.attribution = html.UTF8String;
}
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) { if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) { if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException [NSException
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."]; raise:NSInvalidArgumentException
} format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
MLNTileCoordinateSystem tileCoordinateSystem;
[tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
switch (tileCoordinateSystem) {
case MLNTileCoordinateSystemXYZ:
tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
break;
case MLNTileCoordinateSystemTMS:
tileSet.scheme = mbgl::Tileset::Scheme::TMS;
break;
}
} }
MLNTileCoordinateSystem tileCoordinateSystem;
NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding]; [tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
if (encodingNumber) { switch (tileCoordinateSystem) {
if (![encodingNumber isKindOfClass:[NSValue class]]) { case MLNTileCoordinateSystemXYZ:
[NSException raise:NSInvalidArgumentException tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."]; break;
} case MLNTileCoordinateSystemTMS:
MLNDEMEncoding encoding; tileSet.scheme = mbgl::Tileset::Scheme::TMS;
[encodingNumber getValue:&encoding]; break;
switch (encoding) {
case MLNDEMEncodingMapbox:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
break;
case MLNDEMEncodingTerrarium:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
break;
}
} }
}
encodingNumber = options[MLNVectorTileSourceOptionEncoding]; NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding];
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:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
}
MLNVectorTileSourceEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNVectorTileSourceEncodingMapbox:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
break;
case MLNVectorTileSourceEncodingMLT:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
break;
}
} }
MLNDEMEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNDEMEncodingMapbox:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
break;
case MLNDEMEncodingTerrarium:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
break;
}
}
return tileSet; encodingNumber = options[MLNVectorTileSourceOptionEncoding];
if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException
raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
}
MLNVectorTileSourceEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNVectorTileSourceEncodingMapbox:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
break;
case MLNVectorTileSourceEncodingMLT:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
break;
}
}
return tileSet;
} }

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,31 +4,29 @@
@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];
} }
} }
@end @end

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,72 +24,78 @@ 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 {
configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String); auto source = std::make_unique<mbgl::style::VectorSource>(
return self = [super initWithPendingSource:std::move(source)]; identifier.UTF8String,
configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
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
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options); tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet); options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
return self = [super initWithPendingSource:std::move(source)]; mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet);
return self = [super initWithPendingSource:std::move(source)];
} }
- (mbgl::style::VectorSource *)rawSource { - (mbgl::style::VectorSource *)rawSource {
return (mbgl::style::VectorSource *)super.rawSource; return (mbgl::style::VectorSource *)super.rawSource;
} }
- (NSURL *)configurationURL { - (NSURL *)configurationURL {
MLNAssertStyleSourceIsValid(); MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL(); auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil; return url ? [NSURL URLWithString:@(url->c_str())] : nil;
} }
- (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",
return nil; self.identifier);
} return nil;
}
auto attribution = self.rawSource->getAttribution(); auto attribution = self.rawSource->getAttribution();
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>> *)
MLNAssertStyleSourceIsValid(); featuresInSourceLayersWithIdentifiers:(NSSet<NSString *> *)sourceLayerIdentifiers
std::optional<std::vector<std::string>> optionalSourceLayerIDs; predicate:(nullable NSPredicate *)predicate {
if (sourceLayerIdentifiers) { MLNAssertStyleSourceIsValid();
__block std::vector<std::string> layerIDs; std::optional<std::vector<std::string>> optionalSourceLayerIDs;
layerIDs.reserve(sourceLayerIdentifiers.count); if (sourceLayerIdentifiers) {
[sourceLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) { __block std::vector<std::string> layerIDs;
layerIDs.push_back(identifier.UTF8String); layerIDs.reserve(sourceLayerIdentifiers.count);
[sourceLayerIdentifiers
enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) {
layerIDs.push_back(identifier.UTF8String);
}]; }];
optionalSourceLayerIDs = layerIDs; optionalSourceLayerIDs = layerIDs;
} }
std::optional<mbgl::style::Filter> optionalFilter; std::optional<mbgl::style::Filter> optionalFilter;
if (predicate) { if (predicate) {
optionalFilter = predicate.mgl_filter; optionalFilter = predicate.mgl_filter;
} }
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);
} }
@end @end
@@ -102,67 +108,94 @@ 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 {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
static NSSet<NSString *> *mapboxStreetsLanguages; static NSSet<NSString *> *mapboxStreetsLanguages;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages]; mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages];
}); });
return mapboxStreetsLanguages; return mapboxStreetsLanguages;
} }
+ (NSString *)preferredMapboxStreetsLanguage { + (NSString *)preferredMapboxStreetsLanguage {
return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]]; return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]];
} }
+ (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 *languageCode; NSString *_Nullable language,
NSDictionary<NSString *, id>
*_Nullable bindings) {
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 =
NSString *mostSpecificLanguage; [NSBundle preferredLocalizationsFromArray:availableLanguages forPreferences:preferencesArray];
for (NSString *language in preferredLanguages) { NSString *mostSpecificLanguage;
if (language.length > mostSpecificLanguage.length) { for (NSString *language in preferredLanguages) {
mostSpecificLanguage = language; if (language.length > mostSpecificLanguage.length) {
} mostSpecificLanguage = language;
} }
return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage; }
return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage;
} }
- (BOOL)isMapboxStreets { - (BOOL)isMapboxStreets {
NSURL *url = self.configurationURL; NSURL *url = self.configurationURL;
if (![url.scheme isEqualToString:@"mapbox"]) { if (![url.scheme isEqualToString:@"mapbox"]) {
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,66 +1,67 @@
#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)
- (std::vector<mbgl::Value>)mgl_vector { - (std::vector<mbgl::Value>)mgl_vector {
std::vector<mbgl::Value> vector; std::vector<mbgl::Value> vector;
vector.reserve(self.count); vector.reserve(self.count);
for (id value in self) { for (id value in self) {
if ([value isKindOfClass:[NSArray class]]) { if ([value isKindOfClass:[NSArray class]]) {
std::vector<mbgl::Value> innerVector = [value mgl_vector]; std::vector<mbgl::Value> innerVector = [value mgl_vector];
vector.push_back(innerVector); vector.push_back(innerVector);
} else if ([value isKindOfClass:[NSDictionary class]]) { } else if ([value isKindOfClass:[NSDictionary class]]) {
mbgl::PropertyMap propertyMap = [value mgl_propertyMap]; mbgl::PropertyMap propertyMap = [value mgl_propertyMap];
vector.push_back(propertyMap); vector.push_back(propertyMap);
} else { } else {
NSExpression *expression = [NSExpression expressionForConstantValue:value]; NSExpression *expression = [NSExpression expressionForConstantValue:value];
vector.push_back(expression.mgl_constantMBGLValue); vector.push_back(expression.mgl_constantMBGLValue);
}
} }
return vector; }
return vector;
} }
- (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator { - (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator {
NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator]; NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator];
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]],
if (isSubsequentItem) { @"Items in array must be attributed strings.");
[attributedString appendAttributedString:attributedSeparator]; if (isSubsequentItem) {
} [attributedString appendAttributedString:attributedSeparator];
isSubsequentItem = YES;
[attributedString appendAttributedString:component];
} }
return attributedString; isSubsequentItem = YES;
[attributedString appendAttributedString:component];
}
return attributedString;
} }
+ (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;
} }
@end @end

View File

@@ -7,46 +7,48 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException
@implementation NSBundle (MLNAdditions) @implementation NSBundle (MLNAdditions)
+ (instancetype)mgl_frameworkBundle { + (instancetype)mgl_frameworkBundle {
NSBundle *bundle = [self bundleForClass:[MLNSettings class]]; NSBundle *bundle = [self bundleForClass:[MLNSettings class]];
#ifndef MLN_CUSTOM_COMBINED_BUNDLE #ifndef MLN_CUSTOM_COMBINED_BUNDLE
if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) { if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the bundle is the containing application // For static frameworks, the bundle is the containing application
// bundle but the resources are in Mapbox.bundle. // bundle but the resources are in Mapbox.bundle.
NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"]; NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"];
if (bundlePath) { if (bundlePath) {
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
return bundle; return bundle;
} }
+ (nullable NSString *)mgl_frameworkBundleIdentifier { + (nullable NSString *)mgl_frameworkBundleIdentifier {
return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"]; return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"];
} }
+ (nullable NSDictionary<NSString *, id> *)mgl_frameworkInfoDictionary { + (nullable NSDictionary<NSString *, id> *)mgl_frameworkInfoDictionary {
NSBundle *bundle = self.mgl_frameworkBundle; NSBundle *bundle = self.mgl_frameworkBundle;
return bundle.infoDictionary; return bundle.infoDictionary;
} }
+ (nullable NSString *)mgl_applicationBundleIdentifier { + (nullable NSString *)mgl_applicationBundleIdentifier {
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \ #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;
} }
#endif #endif
return bundleIdentifier; return bundleIdentifier;
} }
@end @end

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
[self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key]; forKey:(NSString *)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,76 +2,80 @@
#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)
- (NSString *)mgl_keyPath { - (NSString *)mgl_keyPath {
NSExpression *leftExpression = self.leftExpression; NSExpression *leftExpression = self.leftExpression;
NSExpression *rightExpression = self.rightExpression; NSExpression *rightExpression = self.rightExpression;
NSExpressionType leftType = leftExpression.expressionType; NSExpressionType leftType = leftExpression.expressionType;
NSExpressionType rightType = rightExpression.expressionType; NSExpressionType rightType = rightExpression.expressionType;
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
return leftExpression.keyPath; return leftExpression.keyPath;
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
return rightExpression.keyPath; return rightExpression.keyPath;
} }
[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 "
return nil; @"constant or vice versa."];
return nil;
} }
- (mbgl::Value)mgl_constantValue { - (mbgl::Value)mgl_constantValue {
NSExpression *leftExpression = self.leftExpression; NSExpression *leftExpression = self.leftExpression;
NSExpression *rightExpression = self.rightExpression; NSExpression *rightExpression = self.rightExpression;
NSExpressionType leftType = leftExpression.expressionType; NSExpressionType leftType = leftExpression.expressionType;
NSExpressionType rightType = rightExpression.expressionType; NSExpressionType rightType = rightExpression.expressionType;
mbgl::Value value; mbgl::Value value;
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
value = rightExpression.mgl_constantMBGLValue; value = rightExpression.mgl_constantMBGLValue;
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
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;
} }
- (mbgl::FeatureType)mgl_featureType { - (mbgl::FeatureType)mgl_featureType {
NSExpression *leftExpression = self.leftExpression; NSExpression *leftExpression = self.leftExpression;
NSExpression *rightExpression = self.rightExpression; NSExpression *rightExpression = self.rightExpression;
NSExpressionType leftType = leftExpression.expressionType; NSExpressionType leftType = leftExpression.expressionType;
NSExpressionType rightType = rightExpression.expressionType; NSExpressionType rightType = rightExpression.expressionType;
mbgl::FeatureType type; mbgl::FeatureType type;
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
type = rightExpression.mgl_featureType; type = rightExpression.mgl_featureType;
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
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;
} }
- (mbgl::FeatureIdentifier)mgl_featureIdentifier { - (mbgl::FeatureIdentifier)mgl_featureIdentifier {
NSExpression *leftExpression = self.leftExpression; NSExpression *leftExpression = self.leftExpression;
NSExpression *rightExpression = self.rightExpression; NSExpression *rightExpression = self.rightExpression;
NSExpressionType leftType = leftExpression.expressionType; NSExpressionType leftType = leftExpression.expressionType;
NSExpressionType rightType = rightExpression.expressionType; NSExpressionType rightType = rightExpression.expressionType;
mbgl::FeatureIdentifier identifier; mbgl::FeatureIdentifier identifier;
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) { if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
identifier = rightExpression.mgl_featureIdentifier; identifier = rightExpression.mgl_featureIdentifier;
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) { } else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
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;
} }
@end @end
@@ -79,138 +83,158 @@
@implementation NSComparisonPredicate (MLNExpressionAdditions) @implementation NSComparisonPredicate (MLNExpressionAdditions)
- (id)mgl_jsonExpressionObject { - (id)mgl_jsonExpressionObject {
switch (self.comparisonPredicateModifier) { switch (self.comparisonPredicateModifier) {
case NSDirectPredicateModifier: case NSDirectPredicateModifier:
break; break;
case NSAllPredicateModifier: case NSAllPredicateModifier:
// “ALL x != y” is logically equivalent to “NOT y IN x”. // “ALL x != y” is logically equivalent to “NOT y IN x”.
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
rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType
options:self.options];
NSPredicate *invertedPredicate = [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate];
return invertedPredicate.mgl_jsonExpressionObject;
} else {
[NSException raise:NSInvalidArgumentException format:@"“ALL” is only supported for the “!=” operator."];
}
case NSAnyPredicateModifier:
// “ANY x = y” is logically equivalent to “y IN x”.
if (self.predicateOperatorType == NSEqualToPredicateOperatorType) {
// https://github.com/mapbox/mapbox-gl-js/issues/9339
if (self.options) {
[NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."];
}
NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType
options:self.options];
return directPredicate.mgl_jsonExpressionObject;
} else {
[NSException raise:NSInvalidArgumentException format:@"“ANY” or “SOME” is only supported for the “=” operator."];
}
default:
[NSException raise:NSInvalidArgumentException
format:@"NSComparisonPredicateModifier:%lu is not supported.", (unsigned long)self.comparisonPredicateModifier];
}
NSString *op;
switch (self.predicateOperatorType) {
case NSLessThanPredicateOperatorType:
op = @"<";
break;
case NSLessThanOrEqualToPredicateOperatorType:
op = @"<=";
break;
case NSGreaterThanPredicateOperatorType:
op = @">";
break;
case NSGreaterThanOrEqualToPredicateOperatorType:
op = @">=";
break;
case NSEqualToPredicateOperatorType:
op = @"==";
break;
case NSNotEqualToPredicateOperatorType:
op = @"!=";
break;
case NSBetweenPredicateOperatorType: {
op = @"all";
NSArray *limits = self.rightExpression.constantValue;
NSPredicate *leftHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:limits[0]
rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType
options:0];
NSPredicate *rightHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
rightExpression:limits[1]
modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType
options:0];
return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject];
} }
case NSInPredicateOperatorType: {
if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) { NSPredicate *directPredicate =
return @[@"within", self.rightExpression.mgl_jsonExpressionObject]; [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
} rightExpression:self.leftExpression
// An “in” expression comparing two string literals is unfortunately modifier:NSDirectPredicateModifier
// misinterpreted as a legacy “in” filter due to ambiguity. Wrap one type:NSInPredicateOperatorType
// argument in a “literal” expression to force an expression. options:self.options];
// https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077 NSPredicate *invertedPredicate =
if (self.leftExpression.expressionType == NSConstantValueExpressionType && [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate];
self.rightExpression.expressionType == NSConstantValueExpressionType && return invertedPredicate.mgl_jsonExpressionObject;
[self.leftExpression.constantValue isKindOfClass:[NSString class]] && } else {
[self.rightExpression.constantValue isKindOfClass:[NSString class]]) { [NSException raise:NSInvalidArgumentException
NSExpression *rightLiteralExpression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression]; format:@"“ALL” is only supported for the “!=” operator."];
NSPredicate *literalPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression }
rightExpression:rightLiteralExpression
modifier:NSDirectPredicateModifier case NSAnyPredicateModifier:
type:NSInPredicateOperatorType // “ANY x = y” is logically equivalent to “y IN x”.
options:self.options]; if (self.predicateOperatorType == NSEqualToPredicateOperatorType) {
return literalPredicate.mgl_jsonExpressionObject; // https://github.com/mapbox/mapbox-gl-js/issues/9339
} if (self.options) {
op = @"in"; [NSException
break; raise:NSInvalidArgumentException
format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."];
} }
case NSContainsPredicateOperatorType: {
NSPredicate *inPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression NSPredicate *directPredicate =
rightExpression:self.leftExpression [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
modifier:self.comparisonPredicateModifier rightExpression:self.leftExpression
type:NSInPredicateOperatorType modifier:NSDirectPredicateModifier
options:self.options]; type:NSInPredicateOperatorType
return inPredicate.mgl_jsonExpressionObject; options:self.options];
} return directPredicate.mgl_jsonExpressionObject;
case NSMatchesPredicateOperatorType: } else {
case NSLikePredicateOperatorType: [NSException raise:NSInvalidArgumentException
case NSBeginsWithPredicateOperatorType: format:@"“ANY” or “SOME” is only supported for the “=” operator."];
case NSEndsWithPredicateOperatorType: }
case NSCustomSelectorPredicateOperatorType:
[NSException raise:NSInvalidArgumentException default:
format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType]; [NSException raise:NSInvalidArgumentException
format:@"NSComparisonPredicateModifier:%lu is not supported.",
(unsigned long)self.comparisonPredicateModifier];
}
NSString *op;
switch (self.predicateOperatorType) {
case NSLessThanPredicateOperatorType:
op = @"<";
break;
case NSLessThanOrEqualToPredicateOperatorType:
op = @"<=";
break;
case NSGreaterThanPredicateOperatorType:
op = @">";
break;
case NSGreaterThanOrEqualToPredicateOperatorType:
op = @">=";
break;
case NSEqualToPredicateOperatorType:
op = @"==";
break;
case NSNotEqualToPredicateOperatorType:
op = @"!=";
break;
case NSBetweenPredicateOperatorType: {
op = @"all";
NSArray *limits = self.rightExpression.constantValue;
NSPredicate *leftHandPredicate = [NSComparisonPredicate
predicateWithLeftExpression:limits[0]
rightExpression:self.leftExpression
modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType
options:0];
NSPredicate *rightHandPredicate = [NSComparisonPredicate
predicateWithLeftExpression:self.leftExpression
rightExpression:limits[1]
modifier:NSDirectPredicateModifier
type:NSLessThanOrEqualToPredicateOperatorType
options:0];
return @[
op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject
];
} }
if (!op) { case NSInPredicateOperatorType: {
return nil; if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) {
return @[ @"within", self.rightExpression.mgl_jsonExpressionObject ];
}
// An “in” expression comparing two string literals is unfortunately
// misinterpreted as a legacy “in” filter due to ambiguity. Wrap one
// argument in a “literal” expression to force an expression.
// https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077
if (self.leftExpression.expressionType == NSConstantValueExpressionType &&
self.rightExpression.expressionType == NSConstantValueExpressionType &&
[self.leftExpression.constantValue isKindOfClass:[NSString class]] &&
[self.rightExpression.constantValue isKindOfClass:[NSString class]]) {
NSExpression *rightLiteralExpression = [NSExpression
expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression];
NSPredicate *literalPredicate =
[NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
rightExpression:rightLiteralExpression
modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType
options:self.options];
return literalPredicate.mgl_jsonExpressionObject;
}
op = @"in";
break;
} }
NSArray *comparisonArray = @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject]; case NSContainsPredicateOperatorType: {
if (self.options) { NSPredicate *inPredicate =
NSDictionary *collatorObject = @{ [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
@"case-sensitive": @(!(self.options & NSCaseInsensitivePredicateOption)), rightExpression:self.leftExpression
@"diacritic-sensitive": @(!(self.options & NSDiacriticInsensitivePredicateOption)), modifier:self.comparisonPredicateModifier
}; type:NSInPredicateOperatorType
return [comparisonArray arrayByAddingObject:@[@"collator", collatorObject]]; options:self.options];
return inPredicate.mgl_jsonExpressionObject;
} }
return comparisonArray; case NSMatchesPredicateOperatorType:
case NSLikePredicateOperatorType:
case NSBeginsWithPredicateOperatorType:
case NSEndsWithPredicateOperatorType:
case NSCustomSelectorPredicateOperatorType:
[NSException raise:NSInvalidArgumentException
format:@"NSPredicateOperatorType:%lu is not supported.",
(unsigned long)self.predicateOperatorType];
}
if (!op) {
return nil;
}
NSArray *comparisonArray = @[
op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject
];
if (self.options) {
NSDictionary *collatorObject = @{
@"case-sensitive" : @(!(self.options & NSCaseInsensitivePredicateOption)),
@"diacritic-sensitive" : @(!(self.options & NSDiacriticInsensitivePredicateOption)),
};
return [comparisonArray arrayByAddingObject:@[ @"collator", collatorObject ]];
}
return comparisonArray;
} }
@end @end

View File

@@ -2,21 +2,20 @@
#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); }
} return filters;
return filters;
} }
@end @end
@@ -24,27 +23,27 @@
@implementation NSCompoundPredicate (MLNExpressionAdditions) @implementation NSCompoundPredicate (MLNExpressionAdditions)
- (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,
NSPredicate *subpredicate = self.subpredicates.firstObject; @"NOT predicate cannot have multiple subpredicates.");
return @[@"!", subpredicate.mgl_jsonExpressionObject]; NSPredicate *subpredicate = self.subpredicates.firstObject;
} return @[ @"!", subpredicate.mgl_jsonExpressionObject ];
case NSAndPredicateType: {
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return [@[@"all"] arrayByAddingObjectsFromArray:subarrays];
}
case NSOrPredicateType: {
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return [@[@"any"] arrayByAddingObjectsFromArray:subarrays];
}
} }
[NSException raise:@"Compound predicate type not handled" case NSAndPredicateType: {
format:@""]; NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return nil; return [@[ @"all" ] arrayByAddingObjectsFromArray:subarrays];
}
case NSOrPredicateType: {
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
return [@[ @"any" ] arrayByAddingObjectsFromArray:subarrays];
}
}
[NSException raise:@"Compound predicate type not handled" format:@""];
return nil;
} }
@end @end

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,24 +1,24 @@
#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)
- (mbgl::PropertyMap)mgl_propertyMap { - (mbgl::PropertyMap)mgl_propertyMap {
mbgl::PropertyMap propertyMap; mbgl::PropertyMap propertyMap;
for (NSString *key in self.allKeys) { for (NSString *key in self.allKeys) {
if ([self[key] isKindOfClass:[NSDictionary class]]) { if ([self[key] isKindOfClass:[NSDictionary class]]) {
propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap]; propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap];
} else if ([self[key] isKindOfClass:[NSArray class]]) { } else if ([self[key] isKindOfClass:[NSArray class]]) {
NSArray *array = self[key]; NSArray *array = self[key];
propertyMap[[key UTF8String]] = [array mgl_vector]; propertyMap[[key UTF8String]] = [array mgl_vector];
} else { } else {
NSExpression *expression = [NSExpression expressionForConstantValue:self[key]]; NSExpression *expression = [NSExpression expressionForConstantValue:self[key]];
propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue; propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue;
}
} }
return propertyMap; }
return propertyMap;
} }
@end @end

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,36 @@
#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>(
auto value = mbgl::style::conversion::convert<mbgl::style::Filter>(mbgl::style::conversion::makeConvertible(jsonObject), valueError); mbgl::style::conversion::makeConvertible(jsonObject), valueError);
if (!value) { if (!value) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Invalid filter value: %@", @(valueError.message.c_str())]; format:@"Invalid filter value: %@", @(valueError.message.c_str())];
return {}; return {};
} }
mbgl::style::Filter filter = std::move(*value); mbgl::style::Filter filter = std::move(*value);
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]; } else {
} else { return nil;
return nil; }
}
} }
@end @end
@@ -39,156 +38,173 @@
@implementation NSPredicate (MLNAdditions) @implementation NSPredicate (MLNAdditions)
NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) { NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) {
NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count]; NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count];
for (id object in objects) { for (id object in objects) {
NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object]; NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object];
[subpredicates addObject:predicate]; [subpredicates addObject:predicate];
} }
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 {
if ([object isEqual:@YES]) { if ([object isEqual:@YES]) {
return [NSPredicate predicateWithValue:YES]; return [NSPredicate predicateWithValue:YES];
} }
if ([object isEqual:@NO]) { if ([object isEqual:@NO]) {
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]],
NSArray *objects = (NSArray *)object; @"Condition for case expression should be an expression.");
NSString *op = objects.firstObject; NSArray *objects = (NSArray *)object;
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]],
MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression"); @"Collators must be dictionaries.");
NSDictionary *collator = collatorExpression[1]; MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression");
MLNCAssert([collator isKindOfClass:[NSDictionary class]], @"Malformed collator in collator expression"); NSDictionary *collator = collatorExpression[1];
MLNCAssert([collator isKindOfClass:[NSDictionary class]],
@"Malformed collator in collator expression");
// Predicate options can’t express specific locales as collators can. // Predicate options can’t express specific locales as collators can.
if (!collator[@"locale"]) { if (!collator[@"locale"]) {
if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) { if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) {
options |= NSCaseInsensitivePredicateOption; options |= NSCaseInsensitivePredicateOption;
}
if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) {
options |= NSDiacriticInsensitivePredicateOption;
}
}
} }
if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) {
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]); options |= NSDiacriticInsensitivePredicateOption;
return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
rightExpression:subexpressions[1]
modifier:NSDirectPredicateModifier
type:operatorType
options:options];
}
if ([op isEqualToString:@"!"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count > 1) {
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
} }
if (subpredicates.count) { }
return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
}
return [NSPredicate predicateWithValue:YES];
}
if ([op isEqualToString:@"all"]) {
NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count == 2) {
// Determine if the expression is of BETWEEN type
if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] &&
[subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) {
NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0];
NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1];
NSArray *limits;
NSExpression *leftConditionExpression;
if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
limits = @[leftCondition.rightExpression, rightCondition.rightExpression];
leftConditionExpression = leftCondition.leftExpression;
} else if (leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
limits = @[leftCondition.leftExpression, rightCondition.rightExpression];
leftConditionExpression = leftCondition.rightExpression;
} else if(leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[leftCondition.leftExpression, rightCondition.leftExpression];
leftConditionExpression = leftCondition.rightExpression;
} else if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[leftCondition.rightExpression, rightCondition.leftExpression];
leftConditionExpression = leftCondition.leftExpression;
}
if (limits && leftConditionExpression) {
return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression, [NSExpression expressionForAggregate:limits]];
}
}
}
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}
if ([op isEqualToString:@"any"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
}
if ([op isEqualToString:@"within"]) {
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
rightExpression:subexpressions[0]
modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType
options:0];
} }
NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object]; NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
return [NSComparisonPredicate predicateWithLeftExpression:expression [objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
rightExpression:[NSExpression expressionForConstantValue:@YES] return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
rightExpression:subexpressions[1]
modifier:NSDirectPredicateModifier modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType type:operatorType
options:0]; options:options];
}
if ([op isEqualToString:@"!"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count > 1) {
NSCompoundPredicate *predicate =
[NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
}
if (subpredicates.count) {
return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
}
return [NSPredicate predicateWithValue:YES];
}
if ([op isEqualToString:@"all"]) {
NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
if (subpredicates.count == 2) {
// Determine if the expression is of BETWEEN type
if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] &&
[subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) {
NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0];
NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1];
NSArray *limits;
NSExpression *leftConditionExpression;
if (leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.rightExpression, rightCondition.rightExpression ];
leftConditionExpression = leftCondition.leftExpression;
} else if (leftCondition.predicateOperatorType ==
NSLessThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType ==
NSLessThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.leftExpression, rightCondition.rightExpression ];
leftConditionExpression = leftCondition.rightExpression;
} else if (leftCondition.predicateOperatorType ==
NSLessThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType ==
NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.leftExpression, rightCondition.leftExpression ];
leftConditionExpression = leftCondition.rightExpression;
} else if (leftCondition.predicateOperatorType ==
NSGreaterThanOrEqualToPredicateOperatorType &&
rightCondition.predicateOperatorType ==
NSGreaterThanOrEqualToPredicateOperatorType) {
limits = @[ leftCondition.rightExpression, rightCondition.leftExpression ];
leftConditionExpression = leftCondition.leftExpression;
}
if (limits && leftConditionExpression) {
return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression,
[NSExpression expressionForAggregate:limits]];
}
}
}
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
}
if ([op isEqualToString:@"any"]) {
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
}
if ([op isEqualToString:@"within"]) {
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
return [NSComparisonPredicate
predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
rightExpression:subexpressions[0]
modifier:NSDirectPredicateModifier
type:NSInPredicateOperatorType
options:0];
}
NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object];
return [NSComparisonPredicate
predicateWithLeftExpression:expression
rightExpression:[NSExpression expressionForConstantValue:@YES]
modifier:NSDirectPredicateModifier
type:NSEqualToPredicateOperatorType
options:0];
} }
- (id)mgl_jsonExpressionObject { - (id)mgl_jsonExpressionObject {
if ([self isEqual:[NSPredicate predicateWithValue:YES]]) { if ([self isEqual:[NSPredicate predicateWithValue:YES]]) {
return @YES; return @YES;
} }
if ([self isEqual:[NSPredicate predicateWithValue:NO]]) { if ([self isEqual:[NSPredicate predicateWithValue:NO]]) {
return @NO; return @NO;
} }
if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) {
[NSException raise:NSInvalidArgumentException
format:@"Block-based predicates are not supported."];
}
if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Unrecognized predicate type."]; format:@"Block-based predicates are not supported."];
return nil; }
[NSException raise:NSInvalidArgumentException format:@"Unrecognized predicate type."];
return nil;
} }
@end @end
@@ -196,34 +212,33 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
@implementation NSPredicate (MLNExpressionAdditions) @implementation NSPredicate (MLNExpressionAdditions)
- (id)mgl_if:(id)firstValue, ... { - (id)mgl_if:(id)firstValue, ... {
if ([self evaluateWithObject:nil]) {
return firstValue;
}
if ([self evaluateWithObject:nil]) { id eachExpression;
return firstValue; va_list argumentList;
} va_start(argumentList, firstValue);
id eachExpression; while ((eachExpression = va_arg(argumentList, id))) {
va_list argumentList; if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) {
va_start(argumentList, firstValue); id valueExpression = va_arg(argumentList, id);
if ([eachExpression evaluateWithObject:nil]) {
return valueExpression;
}
} else {
return eachExpression;
}
}
va_end(argumentList);
while ((eachExpression = va_arg(argumentList, id))) { return nil;
if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) {
id valueExpression = va_arg(argumentList, id);
if ([eachExpression evaluateWithObject:nil]) {
return valueExpression;
}
} else {
return eachExpression;
}
}
va_end(argumentList);
return nil;
} }
- (id)mgl_match:(NSExpression *)firstCase, ... { - (id)mgl_match:(NSExpression *)firstCase, ... {
[NSException raise:NSInvalidArgumentException [NSException raise:NSInvalidArgumentException
format:@"Match expressions lack underlying Objective-C implementations."]; format:@"Match expressions lack underlying Objective-C implementations."];
return nil; return nil;
} }
@end @end

View File

@@ -1,76 +1,73 @@
#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.
// macOS defines an origin at the lower-left corner. // macOS defines an origin at the lower-left corner.
vector.dy *= -1; vector.dy *= -1;
#endif #endif
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 = { .top = paddingArray[0],
.top = paddingArray[0], .right = paddingArray[1],
.right = paddingArray[1], .bottom = paddingArray[2],
.bottom = paddingArray[2], .left = paddingArray[3],
.left = paddingArray[3], };
}; 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];
#if !TARGET_OS_IPHONE #if !TARGET_OS_IPHONE
vector.dy *= -1; vector.dy *= -1;
#endif #endif
return { return {
static_cast<float>(vector.dx), static_cast<float>(vector.dx),
static_cast<float>(vector.dy), static_cast<float>(vector.dy),
}; };
} }
- (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.
return { return {
static_cast<float>(insets.top), static_cast<float>(insets.top),
static_cast<float>(insets.right), static_cast<float>(insets.right),
static_cast<float>(insets.bottom), static_cast<float>(insets.bottom),
static_cast<float>(insets.left), static_cast<float>(insets.left),
}; };
} }
- (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.
return { return {
static_cast<float>(lightPosition.radial), static_cast<float>(lightPosition.radial),
static_cast<float>(lightPosition.azimuthal), static_cast<float>(lightPosition.azimuthal),
static_cast<float>(lightPosition.polar), static_cast<float>(lightPosition.polar),
}; };
} }
@end @end

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 {
@@ -15,113 +15,113 @@ namespace gl {
// instantiating new headless rendering contexts. // instantiating new headless rendering contexts.
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) {
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported // TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension. // 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) {
throw std::runtime_error("No pixel formats found.");
}
} }
if (num <= 0) {
throw std::runtime_error("No pixel formats found.");
}
}
~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));
} }
}
static std::shared_ptr<const CGLDisplayConfig> create() { static std::shared_ptr<const CGLDisplayConfig> create() {
static std::weak_ptr<const CGLDisplayConfig> instance; static std::weak_ptr<const CGLDisplayConfig> instance;
auto shared = instance.lock(); auto shared = instance.lock();
if (!shared) { if (!shared) {
instance = shared = std::make_shared<CGLDisplayConfig>(Key{}); instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
}
return shared;
} }
return shared;
}
public: public:
CGLPixelFormatObj pixelFormat = nullptr; CGLPixelFormatObj pixelFormat = nullptr;
}; };
class CGLBackendImpl final : public HeadlessBackend::Impl { class CGLBackendImpl final : public HeadlessBackend::Impl {
public: public:
CGLBackendImpl() { CGLBackendImpl() {
CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext); CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
if (error != kCGLNoError) { if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error creating GL context object:") + throw std::runtime_error(std::string("Error creating GL context object:") +
CGLErrorString(error) + "\n"); CGLErrorString(error) + "\n");
}
error = CGLEnable(glContext, kCGLCEMPEngine);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
CGLErrorString(error) + "\n");
}
} }
~CGLBackendImpl() final { error = CGLEnable(glContext, kCGLCEMPEngine);
CGLDestroyContext(glContext); if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
CGLErrorString(error) + "\n");
}
}
~CGLBackendImpl() final { CGLDestroyContext(glContext); }
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
} }
gl::ProcAddress getExtensionFunctionPointer(const char* name) final { return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
if (!framework) { }
throw std::runtime_error("Failed to load OpenGL framework.");
}
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName( void activateContext() final {
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name])); CGLError error = CGLSetCurrentContext(glContext);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Switching OpenGL context failed:") +
CGLErrorString(error) + "\n");
} }
}
void activateContext() final { void deactivateContext() final {
CGLError error = CGLSetCurrentContext(glContext); CGLError error = CGLSetCurrentContext(nullptr);
if (error != kCGLNoError) { if (error != kCGLNoError) {
throw std::runtime_error(std::string("Switching OpenGL context failed:") + throw std::runtime_error(std::string("Removing OpenGL context failed:") +
CGLErrorString(error) + "\n"); CGLErrorString(error) + "\n");
}
}
void deactivateContext() final {
CGLError error = CGLSetCurrentContext(nullptr);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Removing OpenGL context failed:") +
CGLErrorString(error) + "\n");
}
} }
}
private: private:
const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create(); const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
CGLContextObj glContext = nullptr; CGLContextObj glContext = nullptr;
}; };
void HeadlessBackend::createImpl() { void HeadlessBackend::createImpl() {
assert(!impl); assert(!impl);
impl = std::make_unique<CGLBackendImpl>(); impl = std::make_unique<CGLBackendImpl>();
} }
} // namespace gl } // namespace gl
} // namespace mbgl } // namespace mbgl

View File

@@ -9,43 +9,39 @@ namespace gl {
class EAGLBackendImpl : public HeadlessBackend::Impl { class EAGLBackendImpl : public HeadlessBackend::Impl {
public: public:
EAGLBackendImpl() { EAGLBackendImpl() {
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]; glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (glContext == nil) { if (glContext == nil) {
throw std::runtime_error("Error creating GL context object"); throw std::runtime_error("Error creating GL context object");
} }
glContext.multiThreaded = YES; glContext.multiThreaded = YES;
}
// Required for ARC to deallocate correctly.
~EAGLBackendImpl() final = default;
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
} }
// Required for ARC to deallocate correctly. return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
~EAGLBackendImpl() final = default; framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
}
gl::ProcAddress getExtensionFunctionPointer(const char* name) final { void activateContext() final { [EAGLContext setCurrentContext:glContext]; }
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
}
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName( void deactivateContext() final { [EAGLContext setCurrentContext:nil]; }
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
}
void activateContext() final {
[EAGLContext setCurrentContext:glContext];
}
void deactivateContext() final {
[EAGLContext setCurrentContext:nil];
}
private: private:
EAGLContext* glContext = nullptr; EAGLContext* glContext = nullptr;
}; };
void HeadlessBackend::createImpl() { void HeadlessBackend::createImpl() {
assert(!impl); assert(!impl);
impl = std::make_unique<EAGLBackendImpl>(); impl = std::make_unique<EAGLBackendImpl>();
} }
} // namespace gl } // namespace gl
} // namespace mbgl } // namespace mbgl

File diff suppressed because it is too large Load Diff

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
@@ -12,349 +12,390 @@
@implementation MLNFeatureTests @implementation MLNFeatureTests
- (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:CLLocationCoordinate2DMake(29.95, -90.066667)]); [NSValue valueWithMLNCoordinate:pointShape.coordinate],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1]; MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]); XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]);
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:CLLocationCoordinate2DMake(39.1, -84.516667)]); [NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[1]], [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]); XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2]; MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]); XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]);
XCTAssertEqual(polygonShape.pointCount, 4UL); XCTAssertEqual(polygonShape.pointCount, 4UL);
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates; CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
XCTAssertNotEqual(polygonCoordinates, nil); XCTAssertNotEqual(polygonCoordinates, nil);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]);
NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons; NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons;
XCTAssertEqual(interiorPolygons.count, 1UL); XCTAssertEqual(interiorPolygons.count, 1UL);
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
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]], range:NSMakeRange(0, interiorPolygon.pointCount)];
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]); XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]], [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]); XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]], [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]);
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]); XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]],
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]], [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]);
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]); XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]);
} }
- (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;
id<MLNFeature> feature = MLNFeatureFromMBGLFeature(pointFeature); id<MLNFeature> feature = MLNFeatureFromMBGLFeature(pointFeature);
XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]); XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]);
id<MLNCluster> cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster); id<MLNCluster> cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster);
XCTAssert(cluster); XCTAssert(cluster);
XCTAssert(cluster.clusterIdentifier == 1); XCTAssert(cluster.clusterIdentifier == 1);
XCTAssert(cluster.clusterPointCount == 5); XCTAssert(cluster.clusterPointCount == 5);
XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]); XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]);
} }
- (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;
pointFeature.properties["int"] = INT64_MIN; pointFeature.properties["int"] = INT64_MIN;
pointFeature.properties["double"] = DBL_MAX; pointFeature.properties["double"] = DBL_MAX;
pointFeature.properties["string"] = std::string("🚏"); pointFeature.properties["string"] = std::string("🚏");
std::vector<bool> vector; std::vector<bool> vector;
vector.push_back(true); vector.push_back(true);
vector.push_back(false); vector.push_back(false);
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);
XCTAssertTrue([null isKindOfClass:[NSNull class]]); XCTAssertTrue([null isKindOfClass:[NSNull class]]);
XCTAssertEqual(null, shape.attributes[@"null"]); XCTAssertEqual(null, shape.attributes[@"null"]);
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,
XCTAssertTrue(boolean.boolValue); @"Boolean property should be converted to bool NSNumber");
XCTAssertEqual(boolean, shape.attributes[@"bool"]); XCTAssertTrue(boolean.boolValue);
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,
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX); @"Unsigned integer property should be converted to unsigned long long NSNumber");
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]); XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
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,
XCTAssertEqual(integer.longLongValue, INT64_MIN); @"Integer property should be converted to long long NSNumber");
XCTAssertEqual(integer, shape.attributes[@"int"]); XCTAssertEqual(integer.longLongValue, INT64_MIN);
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,
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX); @"Floating-point number property should be converted to double NSNumber");
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]); XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
NSString *string = [shape attributeForKey:@"string"]; NSString *string = [shape attributeForKey:@"string"];
XCTAssertNotNil(string); XCTAssertNotNil(string);
XCTAssertTrue([string isKindOfClass:[NSString class]]); XCTAssertTrue([string isKindOfClass:[NSString class]]);
XCTAssertEqualObjects(string, @"🚏"); XCTAssertEqualObjects(string, @"🚏");
XCTAssertEqual(string, shape.attributes[@"string"]); XCTAssertEqual(string, shape.attributes[@"string"]);
} }
- (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
// when there are no identifier or properties // when there are no identifier or properties
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary]; NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
// 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"]);
// it has a null representation of the properties object // it has a null representation of the properties object
XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{}); XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{});
// when there is a string identifier // when there is a string identifier
pointFeature.identifier = @"string-id"; pointFeature.identifier = @"string-id";
// it has the identifier in the result // it has the identifier in the result
geoJSONFeature = [pointFeature geoJSONDictionary]; geoJSONFeature = [pointFeature geoJSONDictionary];
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];
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes); XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
} }
- (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" : @[
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ]
]
};
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) ],
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); @[ @(coord3.longitude), @(coord3.latitude) ]
] ]
};
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)]]]}; ],
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
]
};
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)]]]]}; @[ @[
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); @[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
] ]
]
};
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
NSDictionary *expectedGeometry = @{@"type": @"MultiPoint",
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"MultiPoint",
@"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
};
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", },
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)], @"properties" : @{},
@[@(coord2.longitude), @(coord2.latitude)]]}, @"type" : @"Feature",
@"properties": @{}, },
@"type": @"Feature", @{
} @"geometry" : @{
] @"type" : @"LineString",
}; @"coordinates" : @[
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry); @[ @(coord1.longitude), @(coord1.latitude) ],
@[ @(coord2.longitude), @(coord2.latitude) ]
]
},
@"properties" : @{},
@"type" : @"Feature",
}
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// When the shape collection is created with an empty array of shapes // When the shape collection is created with an empty array of shapes
shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
// 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);
} }
@end @end

View File

@@ -9,203 +9,237 @@
@implementation MLNGeometryTests @implementation MLNGeometryTests
- (void)testCoordinateBoundsIsEmpty { - (void)testCoordinateBoundsIsEmpty {
MLNCoordinateBounds emptyBounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0)); MLNCoordinateBounds emptyBounds =
XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds)); MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0));
XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds))); XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds));
XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(
MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds)));
} }
- (void)testAngleConversions { - (void)testAngleConversions {
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5); XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5);
XCTAssertEqual(0, MLNDegreesFromRadians(0)); XCTAssertEqual(0, MLNDegreesFromRadians(0));
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5); XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5);
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5); XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5);
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5); XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5);
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5); XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5);
XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5); XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5);
XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4); XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4);
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5); XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5);
XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5); XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5);
XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5); XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5);
XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5); XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5);
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5); XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5);
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5); XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5);
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5); XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5);
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4); XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4);
} }
- (void)testAltitudeConversions { - (void)testAltitudeConversions {
CGSize tallSize = CGSizeMake(600, 1200); CGSize tallSize = CGSizeMake(600, 1200);
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 =
CLLocationCoordinate2DMake(37.7757368, -122.4135302)); MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239),
MLNCoordinateBounds roundTrippedBounds = [NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue; CLLocationCoordinate2DMake(37.7757368, -122.4135302));
MLNCoordinateBounds roundTrippedBounds =
[NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue;
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
[NSValue valueWithMLNCoordinate:roundTrippedBounds.sw], [NSValue valueWithMLNCoordinate:roundTrippedBounds.sw],
@"Southwest should round-trip."); @"Southwest should round-trip.");
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
[NSValue valueWithMLNCoordinate:roundTrippedBounds.ne], [NSValue valueWithMLNCoordinate:roundTrippedBounds.ne],
@"Northeast should round-trip."); @"Northeast should round-trip.");
} }
- (void)testCoordinateInCoordinateBounds { - (void)testCoordinateInCoordinateBounds {
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104); CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104);
CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111); CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111);
MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne); MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne);
CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5); CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5);
XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming)); XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming));
XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming)); XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming));
XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming)); XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming));
CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135); CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135);
CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50); CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50);
CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100); CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100);
XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming)); XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming));
XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming)); XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming));
XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming)); XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming));
XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming)); XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming));
} }
- (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\": "
NSError *error; @"[0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding];
MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; NSError *error;
XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization."); MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data
XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization."); encoding:NSUTF8StringEncoding
XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]], @"Valid GeoJSON point feature data should produce an MLNPointFeature."); error:&error];
XCTAssertEqual(feature.attributes.count, 0UL); XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization.");
XCTAssertEqual(feature.coordinate.latitude, 0); XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization.");
XCTAssertEqual(feature.coordinate.longitude, 0); XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]],
@"Valid GeoJSON point feature data should produce an MLNPointFeature.");
XCTAssertEqual(feature.attributes.count, 0UL);
XCTAssertEqual(feature.coordinate.latitude, 0);
XCTAssertEqual(feature.coordinate.longitude, 0);
data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}" dataUsingEncoding:NSUTF8StringEncoding]; data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}"
error = nil; dataUsingEncoding:NSUTF8StringEncoding];
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; error = nil;
XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization."); MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization."); XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization.");
XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization.");
} }
- (void)testGeoJSONSerialization { - (void)testGeoJSONSerialization {
MLNPointFeature *feature = [[MLNPointFeature alloc] init]; MLNPointFeature *feature = [[MLNPointFeature alloc] init];
feature.identifier = @504; feature.identifier = @504;
feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667); feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667);
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
XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON."); options:0
XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON."); error:&error];
XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]], @"Serialized GeoJSON data should be a JSON object."); XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON.");
NSDictionary *geoJSON = @{ XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON.");
@"type": @"Feature", XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]],
@"id": @504, @"Serialized GeoJSON data should be a JSON object.");
@"geometry": @{ NSDictionary *geoJSON = @{
@"type": @"Point", @"type" : @"Feature",
@"coordinates": @[ @"id" : @504,
@(-90.066667), @"geometry" : @{
@29.95, @"type" : @"Point",
], @"coordinates" : @[
}, @(-90.066667),
@"properties": @{}, @29.95,
}; ],
XCTAssertEqualObjects(serializedGeoJSON, geoJSON, @"MLNPointFeature should serialize as a GeoJSON point feature."); },
@"properties" : @{},
};
XCTAssertEqualObjects(serializedGeoJSON, geoJSON,
@"MLNPointFeature should serialize as a GeoJSON point feature.");
} }
- (void)testMLNCoordinateBoundsToMLNCoordinateQuad { - (void)testMLNCoordinateBoundsToMLNCoordinateQuad {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425), MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425),
CLLocationCoordinate2DMake(46.437, -71.516)); CLLocationCoordinate2DMake(46.437, -71.516));
MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds); MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
[NSValue valueWithMLNCoordinate:quad.bottomLeft], [NSValue valueWithMLNCoordinate:quad.bottomLeft],
@"Bounds southwest should be bottom left of quad."); @"Bounds southwest should be bottom left of quad.");
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne], XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
[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:quad.topLeft], [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)],
@"Quad top left should be computed correctly."); [NSValue valueWithMLNCoordinate:quad.topLeft],
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)], @"Quad top left should be computed correctly.");
[NSValue valueWithMLNCoordinate:quad.bottomRight], XCTAssertEqualObjects(
@"Quad bottom right should be computed correctly."); [NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)],
[NSValue valueWithMLNCoordinate:quad.bottomRight],
@"Quad bottom right should be computed correctly.");
} }
- (void)testMLNMapPoint { - (void)testMLNMapPoint {
MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0); MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0);
MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue; MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue;
XCTAssertEqual(point.x, roundTrippedPoint.x); XCTAssertEqual(point.x, roundTrippedPoint.x);
XCTAssertEqual(point.y, roundTrippedPoint.y); XCTAssertEqual(point.y, roundTrippedPoint.y);
XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel); XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel);
} }
- (void)testMLNLocationCoordinate2DIsValid { - (void)testMLNLocationCoordinate2DIsValid {
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323);
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361);
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
} }
{ {
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361); CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361);
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate)); XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
} }
} }
@end @end

View File

@@ -11,56 +11,83 @@
@implementation MLNHeatmapColorTests @implementation MLNHeatmapColorTests
- (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.");
NSExpression *defaultExpression = layer.heatmapColor; NSExpression *defaultExpression = layer.heatmapColor;
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]]; NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
layer.heatmapColor = constantExpression; layer.heatmapColor = constantExpression;
mbgl::style::PropertyValue<float> propertyValue = {0xff};
XCTAssertEqual(
rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
@"Setting heatmapColor to a constant value expression should update heatmap-color.");
XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
@"heatmapColor should round-trip constant value expressions.");
mbgl::style::PropertyValue<float> propertyValue = { 0xff }; constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), NSExpression *constantExpression2 =
@"Setting heatmapColor to a constant value expression should update heatmap-color."); [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
@"heatmapColor should round-trip constant value expressions.");
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
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 =
layer.heatmapColor = functionExpression; [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)",
constantExpression, @{@12 : constantExpression2}];
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 "
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), @"update heatmap-color.");
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331"); @"Setting heatmapColor to an expression depending on $heatmapDensity should "
XCTAssertEqualObjects(layer.heatmapColor, functionExpression, @"update heatmap-color.");
@"heatmapColor should round-trip expressions depending on $heatmapDensity."); XCTExpectFailure(
@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
XCTAssertEqualObjects(
layer.heatmapColor, functionExpression,
@"heatmapColor should round-trip expressions depending on $heatmapDensity.");
layer.heatmapColor = nil; layer.heatmapColor = nil;
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"Unsetting heatmapColor should return heatmap-color to the default value."); @"Unsetting heatmapColor should return heatmap-color to the default value.");
// The contained colors aren’t object equal, even though their descriptions are. // The contained colors aren’t object equal, even though their descriptions are.
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, %@, %@)",
functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; constantExpression, @{@18 : constantExpression}];
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 camera expression "
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."); @"is applied to heatmapColor.");
functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
NSInvalidArgumentException,
@"MLNHeatmapLayer should raise an exception if a data expression is "
@"applied to heatmapColor.");
functionExpression =
[NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression,
@{@18 : constantExpression}];
functionExpression = [NSExpression
expressionWithFormat:
@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
@{@10 : functionExpression}];
XCTAssertThrowsSpecificNamed(
layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException,
@"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a "
@"property that does not support key paths to feature attributes.");
} }
@end @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
@@ -19,115 +19,122 @@
- (void)testStretching { - (void)testStretching {
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
CGRect rect = CGRectMake(0, 0, 24, 24); CGRect rect = CGRectMake(0, 0, 24, 24);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale); UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale);
CGContextRef context = UIGraphicsGetCurrentContext(); CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor); CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor);
CGContextStrokeRectWithWidth(context, rect, 2); CGContextStrokeRectWithWidth(context, rect, 2);
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;
}]; }];
image.resizingMode = NSImageResizingModeTile; image.resizingMode = NSImageResizingModeTile;
#endif #endif
{ {
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage); XCTAssert(styleImage);
if (styleImage) { if (styleImage) {
XCTAssert(!styleImage->getContent()); XCTAssert(!styleImage->getContent());
XCTAssertFalse(styleImage->isSdf()); XCTAssertFalse(styleImage->isSdf());
XCTAssertTrue(styleImage->getStretchX().empty()); XCTAssertTrue(styleImage->getStretchX().empty());
XCTAssertTrue(styleImage->getStretchY().empty()); XCTAssertTrue(styleImage->getStretchY().empty());
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile); XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile);
XCTAssertEqual(imageAfter.capInsets.top, 0); XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0); XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0); XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0); XCTAssertEqual(imageAfter.capInsets.right, 0);
}
} }
}
#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;
#endif #endif
{ {
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage); XCTAssert(styleImage);
if (styleImage) { if (styleImage) {
auto scale = styleImage->getPixelRatio(); auto scale = styleImage->getPixelRatio();
XCTAssert(!styleImage->getContent()); XCTAssert(!styleImage->getContent());
auto stretchX = styleImage->getStretchX(); auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL); XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) { if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale)); XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale));
} }
auto stretchY = styleImage->getStretchY(); auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL); XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) { if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale)); XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale));
} }
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 0); XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0); XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0); XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0); XCTAssertEqual(imageAfter.capInsets.right, 0);
}
} }
}
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)]; image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)];
#else #else
image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4); image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4);
#endif #endif
{ {
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage); XCTAssert(styleImage);
if (styleImage) { if (styleImage) {
auto scale = styleImage->getPixelRatio(); auto scale = styleImage->getPixelRatio();
auto content = styleImage->getContent(); auto content = styleImage->getContent();
XCTAssert(content); XCTAssert(content);
if (content) { if (content) {
XCTAssertEqual(content->top, 1 * scale); XCTAssertEqual(content->top, 1 * scale);
XCTAssertEqual(content->left, 2 * scale); XCTAssertEqual(content->left, 2 * scale);
XCTAssertEqual(content->bottom, 21 * scale); XCTAssertEqual(content->bottom, 21 * scale);
XCTAssertEqual(content->right, 20 * scale); XCTAssertEqual(content->right, 20 * scale);
} }
auto stretchX = styleImage->getStretchX(); auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL); XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) { if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale)); XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale));
} }
auto stretchY = styleImage->getStretchY(); auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL); XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) { if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale)); XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale));
} }
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage]; MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch); XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 1); XCTAssertEqual(imageAfter.capInsets.top, 1);
XCTAssertEqual(imageAfter.capInsets.left, 2); XCTAssertEqual(imageAfter.capInsets.left, 2);
XCTAssertEqual(imageAfter.capInsets.bottom, 3); XCTAssertEqual(imageAfter.capInsets.bottom, 3);
XCTAssertEqual(imageAfter.capInsets.right, 4); XCTAssertEqual(imageAfter.capInsets.right, 4);
}
} }
}
} }
@end @end

View File

@@ -11,30 +11,28 @@ using namespace std::chrono_literals;
@implementation MLNNSDateAdditionsTests @implementation MLNNSDateAdditionsTests
- (void)testDurationToNSTimeInterval { - (void)testDurationToNSTimeInterval {
NSTimeInterval timeInterval = 5;
mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval);
NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration);
NSTimeInterval timeInterval = 5; mbgl::Duration expectedDuration = 5s;
mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval); mbgl::Duration expectedDurationMiliSeconds = 5000ms;
NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration); mbgl::Duration expectedDurationMicroSeconds = 5000000us;
mbgl::Duration expectedDurationNanoSeconds = 5000000000ns;
mbgl::Duration expectedDuration = 5s; XCTAssertEqual(timeInterval, durationTimeInterval);
mbgl::Duration expectedDurationMiliSeconds = 5000ms; XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration));
mbgl::Duration expectedDurationMicroSeconds = 5000000us; XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds));
mbgl::Duration expectedDurationNanoSeconds = 5000000000ns; XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds));
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds));
XCTAssertEqual(timeInterval, durationTimeInterval); mbgl::Duration durationMiliSeconds = 2500ms;
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration)); mbgl::Duration durationMicroSeconds = 2500000us;
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds)); mbgl::Duration durationNanoSeconds = 2500000000ns;
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds));
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds));
mbgl::Duration durationMiliSeconds = 2500ms;
mbgl::Duration durationMicroSeconds = 2500000us;
mbgl::Duration durationNanoSeconds = 2500000000ns;
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds));
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds));
} }
@end @end

View File

@@ -10,49 +10,68 @@
@implementation MLNOfflinePackTests @implementation MLNOfflinePackTests
- (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];
MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self]; MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self];
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey]; [[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
// Make sure this doesn't crash without asserts // Make sure this doesn't crash without asserts
[invalidPack invalidate]; [invalidPack invalidate];
[[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey]; [[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
} }
- (void)testProgressBoxing { - (void)testProgressBoxing {
MLNOfflinePackProgress progress = { MLNOfflinePackProgress progress = {
.countOfResourcesCompleted = 3, .countOfResourcesCompleted = 3,
.countOfResourcesExpected = 2, .countOfResourcesExpected = 2,
.countOfBytesCompleted = 7, .countOfBytesCompleted = 7,
.countOfTilesCompleted = 1, .countOfTilesCompleted = 1,
.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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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
@@ -13,77 +13,78 @@ namespace mbgl {
@implementation MLNResourceTests @implementation MLNResourceTests
- (void)testValidEndpoints { - (void)testValidEndpoints {
using namespace mbgl; using namespace mbgl;
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"])); XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"]));
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"])); XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"]));
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"])); XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"]));
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"])); XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"]));
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"])); XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"]));
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"])); XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"]));
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"])); XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"]));
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"])); XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"]));
} }
- (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL { - (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL {
using namespace mbgl;
using namespace mbgl; // Is our test URL "correct" for subsequent checks?
{
NSURL *url = [NSURL URLWithString:@(testURL.c_str())];
NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
NSArray<NSURLQueryItem *> *items = components.queryItems;
XCTAssert(items.count == 2);
}
// Is our test URL "correct" for subsequent checks? Resource resource(Resource::Kind::Unknown, testURL);
{
NSURL *url = [NSURL URLWithString:@(testURL.c_str())];
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSArray<NSURLQueryItem *> *items = components.queryItems;
XCTAssert(items.count == 2);
}
Resource resource(Resource::Kind::Unknown, testURL); // Now check offline
resource.setUsage(Resource::Usage::Offline);
// Now check offline {
resource.setUsage(Resource::Usage::Offline); NSURL *url = resourceURL(resource);
NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
{ // For offline, we expect a single offline query item
NSURL *url = resourceURL(resource); NSInteger foundCount = 0;
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
// For offline, we expect a single offline query item
NSInteger foundCount = 0;
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR #if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
for (NSURLQueryItem *item in components.queryItems) { for (NSURLQueryItem *item in components.queryItems) {
if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) || if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) ||
([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) || ([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) { ([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
foundCount++; foundCount++;
} }
XCTAssertFalse([item.name isEqualToString:@"sku"]); XCTAssertFalse([item.name isEqualToString:@"sku"]);
}
XCTAssert(foundCount == 3);
#else
// NOTE: Currently the macOS SDK does not supply the sku or offline query parameters
for (NSURLQueryItem *item in components.queryItems) {
if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
foundCount++;
}
XCTAssertFalse([item.name isEqualToString:@"sku"]);
}
XCTAssert(foundCount == 2);
#endif
} }
XCTAssert(foundCount == 3);
#else
// NOTE: Currently the macOS SDK does not supply the sku or offline query parameters
for (NSURLQueryItem *item in components.queryItems) {
if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
foundCount++;
}
XCTAssertFalse([item.name isEqualToString:@"sku"]);
}
XCTAssert(foundCount == 2);
#endif
}
} }
- (void)testOfflineQueryParameterIsAddedForOfflineResource { - (void)testOfflineQueryParameterIsAddedForOfflineResource {
std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two"; std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two";
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
} }
- (void)testOfflineQueryParameterIsAddedForOfflineResourceForChina { - (void)testOfflineQueryParameterIsAddedForOfflineResourceForChina {
std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two"; std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two";
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL]; [self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
} }
@end @end

View File

@@ -13,322 +13,390 @@
@implementation MLNShapeSourceTests @implementation MLNShapeSourceTests
- (void)testGeoJSONOptionsFromDictionary { - (void)testGeoJSONOptionsFromDictionary {
NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]]; NSExpression *reduceExpression =
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"]; [NSExpression expressionForFunction:@"sum:"
NSArray *clusterPropertyArray = @[reduceExpression, mapExpression]; arguments:@[
NSDictionary *options = @{MLNShapeSourceOptionClustered: @YES, [NSExpression expressionForKeyPath:@"featureAccumulated"],
MLNShapeSourceOptionClusterRadius: @42, [NSExpression expressionForKeyPath:@"sumValue"]
MLNShapeSourceOptionClusterMinPoints: @3, ]];
MLNShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray}, NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
MLNShapeSourceOptionMaximumZoomLevelForClustering: @98, NSArray *clusterPropertyArray = @[ reduceExpression, mapExpression ];
MLNShapeSourceOptionMaximumZoomLevel: @99, NSDictionary *options = @{
MLNShapeSourceOptionBuffer: @1976, MLNShapeSourceOptionClustered : @YES,
MLNShapeSourceOptionSimplificationTolerance: @0.42, MLNShapeSourceOptionClusterRadius : @42,
MLNShapeSourceOptionLineDistanceMetrics: @YES, MLNShapeSourceOptionClusterMinPoints : @3,
MLNShapeSourceOptionSynchronousUpdate: @YES}; MLNShapeSourceOptionClusterProperties : @{@"sumValue" : clusterPropertyArray},
MLNShapeSourceOptionMaximumZoomLevelForClustering : @98,
MLNShapeSourceOptionMaximumZoomLevel : @99,
MLNShapeSourceOptionBuffer : @1976,
MLNShapeSourceOptionSimplificationTolerance : @0.42,
MLNShapeSourceOptionLineDistanceMetrics : @YES,
MLNShapeSourceOptionSynchronousUpdate : @YES
};
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options); auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
XCTAssertTrue(mbglOptions->cluster); XCTAssertTrue(mbglOptions->cluster);
XCTAssertEqual(mbglOptions->clusterRadius, 42); XCTAssertEqual(mbglOptions->clusterRadius, 42);
XCTAssertEqual(mbglOptions->clusterMaxZoom, 98); XCTAssertEqual(mbglOptions->clusterMaxZoom, 98);
XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL); XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL);
XCTAssertEqual(mbglOptions->maxzoom, 99); XCTAssertEqual(mbglOptions->maxzoom, 99);
XCTAssertEqual(mbglOptions->buffer, 1976); XCTAssertEqual(mbglOptions->buffer, 1976);
XCTAssertEqual(mbglOptions->tolerance, 0.42); XCTAssertEqual(mbglOptions->tolerance, 0.42);
XCTAssertTrue(mbglOptions->lineMetrics); XCTAssertTrue(mbglOptions->lineMetrics);
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));
} }
- (void)testNilShape { - (void)testNilShape {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil];
XCTAssertNil(source.shape); XCTAssertNil(source.shape);
} }
- (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"
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]); shape:[[MLNPointFeature alloc] init]
options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]]; MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options]; source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]); XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]);
} }
- (void)testMLNShapeSourceWithDataMultipleFeatures { - (void)testMLNShapeSourceWithDataMultipleFeatures {
NSString *geoJSON =
@"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": "
@"{},\"geometry\": {\"type\": \"LineString\",\"coordinates\": "
@"[[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}"; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:shape
options:nil];
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
NSError *error; XCTAssertNotNil(collection);
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; XCTAssertEqual(collection.shapes.count, 1UL);
XCTAssertNil(error); XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shape options:nil];
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
XCTAssertNotNil(collection);
XCTAssertEqual(collection.shapes.count, 1UL);
XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
} }
- (void)testMLNShapeSourceWithSingleGeometry { - (void)testMLNShapeSourceWithSingleGeometry {
NSData *data = [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding]; NSData *data =
NSError *error; [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; NSError *error;
XCTAssertNil(error); MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNotNil(shape); XCTAssertNil(error);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; XCTAssertNotNil(shape);
XCTAssertNotNil(source.shape); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]); shape:shape
options:nil];
XCTAssertNotNil(source.shape);
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 =
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding]; @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": "
NSError *error; @"\"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}";
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error]; NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
XCTAssertNil(error); NSError *error;
XCTAssertNotNil(shape); MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil]; XCTAssertNil(error);
XCTAssertNotNil(source.shape); XCTAssertNotNil(shape);
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]); MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
MLNPointFeature *feature = (MLNPointFeature *)source.shape; shape:shape
XCTAssert([feature.attributes.allKeys containsObject:@"color"]); options:nil];
XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNPointFeature *feature = (MLNPointFeature *)source.shape;
XCTAssert([feature.attributes.allKeys containsObject:@"color"]);
} }
- (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]]);
} }
- (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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
polygonFeature.identifier = @"feature-id"; count:5];
NSString *stringAttribute = @"string"; polygonFeature.identifier = @"feature-id";
NSNumber *boolAttribute = [NSNumber numberWithBool:YES]; NSString *stringAttribute = @"string";
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23]; NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
NSDictionary *nestedDictionaryValue = @{@"nested-key-1": @"nested-string-value"}; NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
NSArray *arrayValue = @[@"string-value", @2]; NSDictionary *nestedDictionaryValue = @{@"nested-key-1" : @"nested-string-value"};
NSDictionary *dictionaryValue = @{@"key-1": @"string-value", NSArray *arrayValue = @[ @"string-value", @2 ];
@"key-2": @1, NSDictionary *dictionaryValue = @{
@"key-3": nestedDictionaryValue, @"key-1" : @"string-value",
@"key-4": arrayValue}; @"key-2" : @1,
NSArray *arrayOfArrays = @[@[@1, @"string-value", @[@"jagged"]]]; @"key-3" : nestedDictionaryValue,
NSArray *arrayOfDictionaries = @[@{@"key": @"value"}]; @"key-4" : arrayValue
};
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;
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier); XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]); XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]);
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier); XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
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"],
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue); dictionaryValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"], arrayOfArrays); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"], arrayOfDictionaries); XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"],
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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.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.2, 100.2)};
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:polygonFeature
options:nil];
XCTAssertNotNil(source.shape); 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]]);
} }
- (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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.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.2, 100.2)};
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; MLNPolygonFeature *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]]);
} }
- (void)testMLNShapeSourceWithPointFeature { - (void)testMLNShapeSourceWithPointFeature {
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]]);
} }
- (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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.0, 100.0), MLNPointCollectionFeature *pointCollectionFeature =
CLLocationCoordinate2DMake(0.0, 100.0)}; [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointCollectionFeature options:nil]; shape:pointCollectionFeature
options:nil];
XCTAssertNotNil(source.shape); XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]); XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
} }
- (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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.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.2, 100.2)};
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5]; MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]]; MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
CLLocationCoordinate2D coordinates_2[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)}; 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);
XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes"); XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes");
} }
- (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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; MLNPolygonFeature *polygonFeature =
[MLNPolygonFeature polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
interiorPolygons:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" features:@[polygonFeature] options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; features:@[ polygonFeature ]
options:nil];
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(0.0, 100.0)};
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil]; MLNPolygon *polygon =
[MLNPolygon polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
interiorPolygons:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shapes:@[polygon] options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape; shapes:@[ polygon ]
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]); XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape"); XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
} }
@end @end

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>
@@ -19,422 +19,543 @@
@end @end
@implementation MLNStyleTests { @implementation MLNStyleTests {
XCTestExpectation *_styleLoadingExpectation; XCTestExpectation *_styleLoadingExpectation;
} }
- (void)setUp { - (void)setUp {
[super setUp]; [super setUp];
[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"
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL]; withExtension:@"json"];
self.mapView.delegate = self; self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
if (!self.mapView.style) { self.mapView.delegate = self;
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; if (!self.mapView.style) {
[self waitForExpectationsWithTimeout:10 handler:nil]; _styleLoadingExpectation =
} [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
} }
- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style { - (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style {
XCTAssertNotNil(mapView.style); XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style); XCTAssertEqual(mapView.style, style);
[_styleLoadingExpectation fulfill]; [_styleLoadingExpectation fulfill];
} }
- (void)tearDown { - (void)tearDown {
_styleLoadingExpectation = nil; _styleLoadingExpectation = nil;
self.mapView = nil; self.mapView = nil;
[super tearDown]; [super tearDown];
} }
// TODO: remove backed property _style // TODO: remove backed property _style
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-property-ivar" #pragma clang diagnostic ignored "-Wunused-property-ivar"
- (MLNStyle *)style { - (MLNStyle *)style {
return self.mapView.style; return self.mapView.style;
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop
- (void)testName { - (void)testName {
XCTAssertNil(self.style.name); XCTAssertNil(self.style.name);
} }
- (void)testSources { - (void)testSources {
NSSet<MLNSource *> *initialSources = self.style.sources; NSSet<MLNSource *> *initialSources = self.style.sources;
if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) { if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) {
XCTAssertEqual(self.style.sources.count, 1UL); XCTAssertEqual(self.style.sources.count, 1UL);
} 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"
[self.style addSource:shapeSource]; shape:nil
XCTAssertEqual(self.style.sources.count, initialSources.count + 1); options:nil];
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]); [self.style addSource:shapeSource];
[self.style removeSource:shapeSource]; XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
XCTAssertEqual(self.style.sources.count, initialSources.count); XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
[self.style removeSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count);
} }
- (void)testAddingSourcesTwice { - (void)testAddingSourcesTwice {
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
[self.style addSource:shapeSource]; shape:nil
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, MLNRedundantSourceException); options:nil];
[self.style addSource:shapeSource];
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException,
MLNRedundantSourceException);
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42]; MLNRasterTileSource *rasterTileSource =
[self.style addSource:rasterTileSource]; [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource"
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, MLNRedundantSourceException); configurationURL:[NSURL URLWithString:@".json"]
tileSize:42];
[self.style addSource:rasterTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException,
MLNRedundantSourceException);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]]; MLNVectorTileSource *vectorTileSource =
[self.style addSource:vectorTileSource]; [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource"
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, MLNRedundantSourceException); configurationURL:[NSURL URLWithString:@".json"]];
[self.style addSource:vectorTileSource];
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 =
[self.style removeSource:rasterTileSource]; [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
[self.style addSource:rasterTileSource]; tileURLTemplates:@[]
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]); options:nil];
[self.style removeSource:rasterTileSource];
[self.style addSource:rasterTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil]; MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source"
[self.style removeSource:shapeSource]; shape:nil
[self.style addSource:shapeSource]; options:nil];
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]); [self.style removeSource:shapeSource];
[self.style addSource:shapeSource];
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil]; MLNVectorTileSource *vectorTileSource =
[self.style removeSource:vectorTileSource]; [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source"
[self.style addSource:vectorTileSource]; tileURLTemplates:@[]
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]); options:nil];
[self.style removeSource:vectorTileSource];
[self.style addSource:vectorTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
} }
- (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 =
[self.style addSource:rasterTileSource]; [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterTileSource];
// Attempt to remove an image source with the same identifier as the raster tile source // 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 =
[self.style removeSource:imageSource]; [[MLNImageSource alloc] initWithIdentifier:@"some-identifier"
// The raster tile source should still be added coordinateQuad:{}
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MLNRasterTileSource class]]); URL:[NSURL URLWithString:@"http://host/image.png"]];
[self.style removeSource:imageSource];
// The raster tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier]
isMemberOfClass:[MLNRasterTileSource class]]);
// Remove the raster tile source // Remove the raster tile source
[self.style removeSource:rasterTileSource]; [self.style removeSource:rasterTileSource];
// Add the shape source // Add the shape source
[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 =
[self.style removeSource:vectorTileSource]; [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
// The image source should still be added tileURLTemplates:@[]
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNImageSource class]]); options:nil];
[self.style removeSource:vectorTileSource];
// The image source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
isMemberOfClass:[MLNImageSource class]]);
// Remove the image source // Remove the image source
[self.style removeSource:imageSource]; [self.style removeSource:imageSource];
// 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
[self.style removeSource:rasterTileSource]; // shape source
// The vector tile source should still be added [self.style removeSource:rasterTileSource];
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]); // The vector tile source should still be added
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 =
[self.style addSource:vectorTileSource]; [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[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"
[self.style addLayer:fillLayer]; source:vectorTileSource];
[self.style addLayer:fillLayer];
// Attempt to remove the raster tile source // Attempt to remove the raster tile source
NSError *error; NSError *error;
BOOL result = [self.style removeSource:vectorTileSource error:&error]; BOOL result = [self.style removeSource:vectorTileSource error:&error];
XCTAssertFalse(result); XCTAssertFalse(result);
XCTAssertEqualObjects(error.domain, MLNErrorDomain); XCTAssertEqualObjects(error.domain, MLNErrorDomain);
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 {
NSArray<MLNStyleLayer *> *initialLayers = self.style.layers; NSArray<MLNStyleLayer *> *initialLayers = self.style.layers;
if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
XCTAssertEqual(self.style.layers.count, 1UL); XCTAssertEqual(self.style.layers.count, 1UL);
} 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"
[self.style addSource:shapeSource]; shape:nil
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:shapeSource]; options:nil];
[self.style addLayer:fillLayer]; [self.style addSource:shapeSource];
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1); MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]); source:shapeSource];
[self.style removeLayer:fillLayer]; [self.style addLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count); XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
[self.style removeLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count);
} }
- (void)testAddingLayersTwice { - (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 =
[self.style addLayer:backgroundLayer]; [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, MLNRedundantLayerException); [self.style addLayer:backgroundLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException,
MLNRedundantLayerException);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" source:source]; MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer"
[self.style addLayer:circleLayer]; source:source];
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, MLNRedundantLayerException); [self.style addLayer:circleLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException,
MLNRedundantLayerException);
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:source]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
[self.style addLayer:fillLayer]; source:source];
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, MLNRedundantLayerException); [self.style addLayer:fillLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException,
MLNRedundantLayerException);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer"
[self.style addLayer:lineLayer]; source:source];
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, MLNRedundantLayerException); [self.style addLayer:lineLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException,
MLNRedundantLayerException);
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" source:source]; MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer"
[self.style addLayer:rasterLayer]; source:source];
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, MLNRedundantLayerException); [self.style addLayer:rasterLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException,
MLNRedundantLayerException);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" source:source]; MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer"
[self.style addLayer:symbolLayer]; source:source];
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, MLNRedundantLayerException); [self.style addLayer:symbolLayer];
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"
[self.style addLayer:initial]; source:source];
[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
[self.style addSource:source]; // with the layer is not added
[self.style addSource:source];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:source]; MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer"
[self.style removeLayer:fillLayer]; source:source];
[self.style addLayer:fillLayer]; [self.style removeLayer:fillLayer];
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]); [self.style addLayer:fillLayer];
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source]; MLNSymbolStyleLayer *symbolLayer =
[self.style removeLayer:symbolLayer]; [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
[self.style addLayer:symbolLayer]; [self.style removeLayer:symbolLayer];
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]); [self.style addLayer:symbolLayer];
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source]; MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer"
[self.style removeLayer:lineLayer]; source:source];
[self.style addLayer:lineLayer]; [self.style removeLayer:lineLayer];
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]); [self.style addLayer:lineLayer];
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source]; MLNCircleStyleLayer *circleLayer =
[self.style removeLayer:circleLayer]; [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
[self.style addLayer:circleLayer]; [self.style removeLayer:circleLayer];
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]); [self.style addLayer:circleLayer];
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"]; MLNBackgroundStyleLayer *backgroundLayer =
[self.style removeLayer:backgroundLayer]; [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
[self.style addLayer:backgroundLayer]; [self.style removeLayer:backgroundLayer];
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]); [self.style addLayer:backgroundLayer];
} XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
}
{ {
MLNRasterTileSource *rasterSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil]; MLNRasterTileSource *rasterSource =
[self.style addSource:rasterSource]; [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterSource];
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource]; MLNRasterStyleLayer *rasterLayer =
[self.style removeLayer:rasterLayer]; [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
[self.style addLayer:rasterLayer]; [self.style removeLayer:rasterLayer];
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]); [self.style addLayer:rasterLayer];
} XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
}
} }
- (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier { - (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" shape:nil options:nil]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier"
[self.style addSource:source]; shape:nil
options:nil];
[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"
[self.style addLayer:fillLayer]; source:source];
[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
[self.style removeLayer:lineLayer]; source:source];
[self.style removeLayer:lineLayer];
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] isMemberOfClass:[MLNFillStyleLayer class]]); XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier]
isMemberOfClass:[MLNFillStyleLayer class]]);
} }
- (NSString *)stringWithContentsOfStyleHeader { - (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
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h."); usedEncoding:nil
return styleHeader; error:&styleHeaderError];
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
return styleHeader;
} }
- (void)testImages { - (void)testImages {
NSString *imageName = @"TrackingLocationMask"; NSString *imageName = @"TrackingLocationMask";
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
MLNImage *image = [MLNImage imageNamed:imageName MLNImage *image = [MLNImage imageNamed:imageName
inBundle:[NSBundle bundleForClass:[self class]] inBundle:[NSBundle bundleForClass:[self class]]
compatibleWithTraitCollection:nil]; compatibleWithTraitCollection:nil];
#else #else
MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName]; MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
#endif #endif
XCTAssertNotNil(image); XCTAssertNotNil(image);
[self.style setImage:image forName:imageName]; [self.style setImage:image forName:imageName];
MLNImage *styleImage = [self.style imageForName:imageName]; MLNImage *styleImage = [self.style imageForName:imageName];
XCTAssertNotNil(styleImage); XCTAssertNotNil(styleImage);
XCTAssertEqual(image.size.width, styleImage.size.width); XCTAssertEqual(image.size.width, styleImage.size.width);
XCTAssertEqual(image.size.height, styleImage.size.height); XCTAssertEqual(image.size.height, styleImage.size.height);
} }
- (void)testLayersOrder { - (void)testLayersOrder {
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam"
NSURL *url = [NSURL fileURLWithPath:filePath]; ofType:@"geojson"];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" URL:url options:nil]; NSURL *url = [NSURL fileURLWithPath:filePath];
[self.style addSource:source]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
URL:url
options:nil];
[self.style addSource:source];
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" source:source]; MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1"
[self.style addLayer:layer1]; source:source];
[self.style addLayer:layer1];
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" source:source]; MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3"
[self.style addLayer:layer3]; source:source];
[self.style addLayer:layer3];
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" source:source]; MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2"
[self.style insertLayer:layer2 aboveLayer:layer1]; source:source];
[self.style insertLayer:layer2 aboveLayer:layer1];
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" source:source]; MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4"
[self.style insertLayer:layer4 aboveLayer:layer3]; source:source];
[self.style insertLayer:layer4 aboveLayer:layer3];
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" source:source]; MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0"
[self.style insertLayer:layer0 belowLayer:layer1]; source:source];
[self.style insertLayer:layer0 belowLayer:layer1];
NSArray<MLNStyleLayer *> *layers = [self.style layers]; NSArray<MLNStyleLayer *> *layers = [self.style layers];
NSUInteger startIndex = 0; NSUInteger startIndex = 0;
if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) { if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
startIndex++; startIndex++;
} }
XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier); XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier); XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier); XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier); XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier); XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
} }
// MARK: Localization tests // MARK: Localization tests
- (void)testLanguageMatching { - (void)testLanguageMatching {
{ {
NSArray *preferences = @[@"en"]; NSArray *preferences = @[ @"en" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); XCTAssertEqualObjects(
} [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
{ }
NSArray *preferences = @[@"en-US"]; {
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); NSArray *preferences = @[ @"en-US" ];
} XCTAssertEqualObjects(
{ [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
NSArray *preferences = @[@"fr"]; }
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr"); {
} NSArray *preferences = @[ @"fr" ];
{ XCTAssertEqualObjects(
NSArray *preferences = @[@"zh-Hans"]; [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); }
} {
{ NSArray *preferences = @[ @"zh-Hans" ];
NSArray *preferences = @[@"zh-Hans", @"en"]; XCTAssertEqualObjects(
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans"); [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
} }
{ {
NSArray *preferences = @[@"zh-Hant"]; NSArray *preferences = @[ @"zh-Hans", @"en" ];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant"); XCTAssertEqualObjects(
} [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
{ }
NSArray *preferences = @[@"en", @"fr", @"el"]; {
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); NSArray *preferences = @[ @"zh-Hant" ];
} XCTAssertEqualObjects(
{ [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
NSArray *preferences = @[@"tlh"]; }
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); {
} NSArray *preferences = @[ @"en", @"fr", @"el" ];
{ XCTAssertEqualObjects(
NSArray *preferences = @[@"tlh", @"en"]; [MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en"); }
} {
{ NSArray *preferences = @[ @"tlh" ];
NSArray *preferences = @[@"mul"]; XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]); }
} {
NSArray *preferences = @[ @"tlh", @"en" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[ @"mul" ];
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;
XCTAssert(self.style.transition.delay == transitionTest.delay); XCTAssert(self.style.transition.delay == transitionTest.delay);
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 {
// Test getting style JSON // Test getting style JSON
NSString *styleJSON = self.style.styleJSON; NSString *styleJSON = self.style.styleJSON;
XCTAssertNotNil(styleJSON, @"Style JSON should not be nil"); XCTAssertNotNil(styleJSON, @"Style JSON should not be nil");
// 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 =
options:0 [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
error:&error]; options:0
XCTAssertNil(error, @"Style JSON should be valid JSON"); error:&error];
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object"); XCTAssertNil(error, @"Style JSON should be valid JSON");
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], @"Style JSON should represent a dictionary"); XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
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,180 +14,194 @@
@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);
// has the correct URL templates // has the correct URL templates
XCTAssertEqual(tileSet.tiles.size(), 3UL); XCTAssertEqual(tileSet.tiles.size(), 3UL);
XCTAssertEqual(tileSet.tiles[0], "tile.1"); XCTAssertEqual(tileSet.tiles[0], "tile.1");
XCTAssertEqual(tileSet.tiles[1], "tile.2"); XCTAssertEqual(tileSet.tiles[1], "tile.2");
XCTAssertEqual(tileSet.tiles[2], "tile.3"); XCTAssertEqual(tileSet.tiles[2], "tile.3");
// has the default scheme // has the default scheme
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ); XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ);
// when the tile set has no min or max zoom level set // when the tile set has no min or max zoom level set
// the mbgl object has default values for min and max zoom level // the mbgl object has default values for min and max zoom level
XCTAssertEqual(tileSet.zoomRange.min, 0); XCTAssertEqual(tileSet.zoomRange.min, 0);
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
XCTAssertEqual(tileSet.zoomRange.min, 1); XCTAssertEqual(tileSet.zoomRange.min, 1);
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
XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds"); XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
if (tileSet.bounds) { if (tileSet.bounds) {
MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds); MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip"); XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
} }
// 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]
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]]; initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
UIColor *redColor = [UIColor redColor]; UIColor *redColor = [UIColor redColor];
#else #else
// CSS uses the sRGB color space. // CSS uses the sRGB color space.
// AppKit incorrectly uses calibrated RGB when exporting HTML, so input // AppKit incorrectly uses calibrated RGB when exporting HTML, so input
// calibrated RGB to ensure round-tripping. // calibrated RGB to ensure round-tripping.
// <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:@{
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil]; NSBackgroundColorAttributeName : redColor,
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{ }];
MLNTileSourceOptionAttributionInfos: @[mapboxInfo, glInfo], MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
}); tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionInfos : @[ mapboxInfo, glInfo ],
});
// the attribution is reflected by the mbgl tileset // the attribution is reflected by the mbgl tileset
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
NSString *html; NSString *html;
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; "
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" @"font-size: 12.00px\">"
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px; background-color: #ff0000\">GL</font>\n"); @"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
} else { @"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt\">" @"font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>" } else {
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt; background-color: #ff0000\">GL</font>\n"); 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>"
@"<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
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
// 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
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS); XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
} }
- (void)testTileSetFromTileURLTemplatesRasterEncodings { - (void)testTileSetFromTileURLTemplatesRasterEncodings {
mbgl::Tileset tileSet; mbgl::Tileset tileSet;
// 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
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding); XCTAssertFalse(tileSet.vectorEncoding);
// 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
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium); XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding); XCTAssertFalse(tileSet.vectorEncoding);
} }
- (void)testTileSetFromTileURLTemplatesVectorEncodings { - (void)testTileSetFromTileURLTemplatesVectorEncodings {
mbgl::Tileset tileSet; mbgl::Tileset tileSet;
// 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
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox); XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox);
XCTAssertFalse(tileSet.rasterEncoding); XCTAssertFalse(tileSet.rasterEncoding);
// 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
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding); XCTAssertFalse(tileSet.rasterEncoding);
// 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
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT); XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding); XCTAssertFalse(tileSet.rasterEncoding);
} }
- (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,129 +10,130 @@ 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_) {
size = size_;
swapchain->setDrawableSize(
{static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)});
buffersInvalid = true;
}
mbgl::Size getSize() const { return size; }
void bind() override {
surface = NS::TransferPtr(swapchain->nextDrawable());
auto texSize = mbgl::Size{static_cast<uint32_t>(swapchain->drawableSize().width),
static_cast<uint32_t>(swapchain->drawableSize().height)};
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer());
renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor());
renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture());
if (buffersInvalid || !depthTexture || !stencilTexture) {
buffersInvalid = false;
depthTexture = rendererBackend.getContext().createTexture2D();
depthTexture->setSize(texSize);
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
depthTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
gfx::TextureWrapType::Clamp,
gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(depthTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
MTL::TextureUsageRenderTarget);
stencilTexture = rendererBackend.getContext().createTexture2D();
stencilTexture->setSize(texSize);
stencilTexture->setFormat(gfx::TexturePixelType::Stencil,
gfx::TextureChannelDataType::UnsignedByte);
stencilTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
gfx::TextureWrapType::Clamp,
gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(stencilTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
MTL::TextureUsageRenderTarget);
} }
void setBackendSize(mbgl::Size size_) { if (depthTexture) {
size = size_; depthTexture->create();
swapchain->setDrawableSize({static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)}); if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
buffersInvalid = true; depthTarget->setTexture(
static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
}
} }
if (stencilTexture) {
mbgl::Size getSize() const { stencilTexture->create();
return size; if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(
static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
}
} }
}
void bind() override { void swap() override {
surface = NS::TransferPtr(swapchain->nextDrawable()); commandBuffer->presentDrawable(surface.get());
auto texSize = mbgl::Size{ commandBuffer->commit();
static_cast<uint32_t>(swapchain->drawableSize().width), commandBuffer.reset();
static_cast<uint32_t>(swapchain->drawableSize().height)}; renderPassDescriptor.reset();
}
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer()); const mtl::RendererBackend& getBackend() const override { return rendererBackend; }
renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor());
renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture());
if (buffersInvalid || !depthTexture || !stencilTexture) { const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; }
buffersInvalid = false;
depthTexture = rendererBackend.getContext().createTexture2D();
depthTexture->setSize(texSize);
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
depthTexture->setSamplerConfiguration(
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(depthTexture.get())->setUsage(
MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
stencilTexture = rendererBackend.getContext().createTexture2D(); mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
stencilTexture->setSize(texSize); return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
stencilTexture->setFormat(gfx::TexturePixelType::Stencil, gfx::TextureChannelDataType::UnsignedByte); }
stencilTexture->setSamplerConfiguration(
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(stencilTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
}
if (depthTexture) { const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
depthTexture->create(); return renderPassDescriptor;
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) { }
depthTarget->setTexture(static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
}
}
if (stencilTexture) {
stencilTexture->create();
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
}
}
}
void swap() override { const CAMetalLayerPtr& getSwapchain() const { return swapchain; }
commandBuffer->presentDrawable(surface.get());
commandBuffer->commit();
commandBuffer.reset();
renderPassDescriptor.reset();
}
const mtl::RendererBackend& getBackend() const override {
return rendererBackend;
}
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override {
return commandBuffer;
}
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
}
const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
return renderPassDescriptor;
}
const CAMetalLayerPtr& getSwapchain() const {
return swapchain;
}
private: private:
MetalBackend &rendererBackend; MetalBackend& rendererBackend;
MTLCommandQueuePtr commandQueue; MTLCommandQueuePtr commandQueue;
MTLCommandBufferPtr commandBuffer; MTLCommandBufferPtr commandBuffer;
MTLRenderPassDescriptorPtr renderPassDescriptor; MTLRenderPassDescriptorPtr renderPassDescriptor;
CAMetalDrawablePtr surface; CAMetalDrawablePtr surface;
CAMetalLayerPtr swapchain; CAMetalLayerPtr swapchain;
gfx::Texture2DPtr depthTexture; gfx::Texture2DPtr depthTexture;
gfx::Texture2DPtr stencilTexture; gfx::Texture2DPtr stencilTexture;
mbgl::Size size; mbgl::Size size;
bool buffersInvalid = true; bool buffersInvalid = true;
}; };
} // 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()
window.contentView.wantsLayer = YES; .getResource<mbgl::MetalRenderableResource>()
.getSwapchain()
.get();
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() {}
void MetalBackend::updateAssumedState() {} void MetalBackend::updateAssumedState() {}
void MetalBackend::setSize(mbgl::Size size_) { 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,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,90 +10,93 @@
@implementation MLNCameraTransitionFinishTests @implementation MLNCameraTransitionFinishTests
- (void)testEaseToCompletionHandler { - (void)testEaseToCompletionHandler {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), XCTestExpectation *expectation =
CLLocationCoordinate2DMake(1.0, 1.0)); [self expectationWithDescription:@"Completion block should be called"];
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; [self.mapView setCamera:camera
withDuration:0.0
[self.mapView setCamera:camera animationTimingFunction:nil
withDuration:0.0 completionHandler:^{
animationTimingFunction:nil
completionHandler:^{
[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),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), XCTestExpectation *expectation =
CLLocationCoordinate2DMake(1.0, 1.0)); [self expectationWithDescription:@"Completion block should be called"];
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; [self.mapView setCamera:camera
withDuration:0.3
[self.mapView setCamera:camera animationTimingFunction:nil
withDuration:0.3 completionHandler:^{
animationTimingFunction:nil
completionHandler:^{
[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),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), XCTestExpectation *expectation =
CLLocationCoordinate2DMake(1.0, 1.0)); [self expectationWithDescription:@"Completion block should be called"];
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; [self.mapView flyToCamera:camera
withDuration:0.0
completionHandler:^{
[expectation fulfill];
}];
[self.mapView flyToCamera:camera [self waitForExpectations:@[ expectation ] timeout:0.5];
withDuration:0.0
completionHandler:^{
[expectation fulfill];
}];
[self waitForExpectations:@[expectation] timeout:0.5];
} }
- (void)testFlyToCompletionHandlerAnimated { - (void)testFlyToCompletionHandlerAnimated {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), XCTestExpectation *expectation =
CLLocationCoordinate2DMake(1.0, 1.0)); [self expectationWithDescription:@"Completion block should be called"];
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; [self.mapView flyToCamera:camera
withDuration:0.3
completionHandler:^{
[expectation fulfill];
}];
[self.mapView flyToCamera:camera [self waitForExpectations:@[ expectation ] timeout:0.5];
withDuration:0.3
completionHandler:^{
[expectation fulfill];
}];
[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 {
options.zoom = NAN; mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera
return options; edgePadding:insets];
options.zoom = NAN;
return options;
} }
@end @end
@@ -103,6 +106,6 @@
@implementation MLNCameraTransitionFinishNaNTests @implementation MLNCameraTransitionFinishNaNTests
- (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL { - (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL {
return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL]; return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL];
} }
@end @end

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,387 +8,350 @@
@implementation MLNCameraTransitionTests @implementation MLNCameraTransitionTests
- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod { - (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; __weak MLNCameraTransitionTests *weakself = self;
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self; self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself; [expectation fulfill];
if (!strongSelf) return; MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
dispatch_async(dispatch_get_main_queue(), ^{
[mapView resetNorth];
});
}
};
[expectation fulfill]; [self.mapView setDirection:90 animated:YES];
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); // loop, render, and wait
if (mapView.direction != 0.0) { [self waitForExpectations:@[ expectation ] timeout:10];
dispatch_async(dispatch_get_main_queue(), ^{
[mapView resetNorth];
});
}
};
[self.mapView setDirection:90 animated:YES];
// loop, render, and wait
[self waitForExpectations:@[expectation] timeout:10];
} }
- (void)testSetAndResetNorthInDelegateMethod { - (void)testSetAndResetNorthInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; __weak MLNCameraTransitionTests *weakself = self;
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self; self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself; [expectation fulfill];
if (!strongSelf) return; MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
NSLog(@"Reset to north");
[mapView resetNorth];
}
};
[expectation fulfill]; [self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
NSLog(@"Reset to north");
[mapView resetNorth];
}
};
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
} }
- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging { - (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
// Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO];
// Reset to non-zero, prior to testing XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
[self.mapView setDirection:45 animated:NO]; expectation.expectedFulfillmentCount = 1;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; __weak MLNCameraTransitionTests *weakself = self;
expectation.expectedFulfillmentCount = 1; __block BOOL startedReset = NO;
expectation.assertForOverFulfill = YES; __block BOOL finishedReset = NO;
__weak MLNCameraTransitionTests *weakself = self; self.regionIsChanging = ^(MLNMapView *mapView) {
__block BOOL startedReset = NO; MLNCameraTransitionTests *strongSelf = weakself;
__block BOOL finishedReset = NO; if (!strongSelf) return;
self.regionIsChanging = ^(MLNMapView *mapView) { if (!startedReset) {
MLNCameraTransitionTests *strongSelf = weakself; NSLog(@"Reset to north, interrupting the previous transition");
if (!strongSelf) return; startedReset = YES;
[mapView resetNorth];
finishedReset = YES;
}
};
if (!startedReset) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
NSLog(@"Reset to north, interrupting the previous transition"); MLNCameraTransitionTests *strongSelf = weakself;
startedReset = YES; if (!strongSelf) return;
[mapView resetNorth];
finishedReset = YES;
}
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { MLNTestAssert(strongSelf, startedReset);
MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return;
MLNTestAssert(strongSelf, startedReset); if (finishedReset) {
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
[expectation fulfill];
} else {
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
}
};
if (finishedReset) { [self.mapView setDirection:90 animated:YES];
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled)); [self waitForExpectations:@[ expectation ] timeout:10];
[expectation fulfill];
}
else {
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
}
};
[self.mapView setDirection:90 animated:YES]; XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
[self waitForExpectations:@[expectation] timeout:10]; @"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);
double zoomLevel = 15.0; double zoomLevel = 15.0;
[self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; [self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
[self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES]; [self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_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, @"setting center coordinate should cancel transitions");
0.0005, MLNTestAssertEqualWithAccuracy(strongSelf, dc.longitude,
@"setting center coordinate should cancel transitions"); strongSelf.mapView.centerCoordinate.longitude, 0.0005,
MLNTestAssertEqualWithAccuracy(strongSelf, @"setting center coordinate should cancel transitions");
dc.longitude, [cameraIsInDCExpectation fulfill];
strongSelf.mapView.centerCoordinate.longitude, });
0.0005,
@"setting center coordinate should cancel transitions");
[cameraIsInDCExpectation fulfill];
});
[self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0]; [self waitForExpectations:@[ cameraIsInDCExpectation ] timeout:10.0];
} }
- (void)testSetCenterCoordinateInDelegateMethod { - (void)testSetCenterCoordinateInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; __weak MLNCameraTransitionTests *weakself = self;
expectation.expectedFulfillmentCount = 2; __block NSInteger delegateCallCount = 0;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self; CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
__block NSInteger delegateCallCount = 0; CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0); MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself; MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (!strongSelf) return; CLLocationCoordinate2D center = mapView.centerCoordinate;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); switch (delegateCallCount) {
case 0: {
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target");
CLLocationCoordinate2D center = mapView.centerCoordinate; MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
@"center coordinate latitude should be at target");
switch(delegateCallCount) { // Now set another coordinate.
case 0: // Should take MLNAnimationDuration seconds (0.3s)
{ [mapView setCenterCoordinate:target2 animated:YES];
// Our center coordinate should match our target (assuming we're not break;
// constrained by zoom level) }
MLNTestAssertEqualWithAccuracy(strongSelf,
target.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target");
MLNTestAssertEqualWithAccuracy(strongSelf, case 1: {
target.latitude, // Our center coordinate should match our target (assuming we're not
center.latitude, // constrained by zoom level)
0.0005, MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
@"center coordinate latitude should be at target"); @"center coordinate longitude should be at target2");
// Now set another coordinate. MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
// Should take MLNAnimationDuration seconds (0.3s) @"center coordinate latitude should be at target2");
[mapView setCenterCoordinate:target2 animated:YES]; break;
break; }
}
case 1: default:
{ MLNTestFail(strongSelf);
// Our center coordinate should match our target (assuming we're not break;
// constrained by zoom level) }
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2");
MLNTestAssertEqualWithAccuracy(strongSelf, delegateCallCount++;
target2.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target2");
break;
} [expectation fulfill];
};
default: // Should take MLNAnimationDuration seconds (0.3)
MLNTestFail(strongSelf); [self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
break; [self waitForExpectations:@[ expectation ] timeout:10];
}
delegateCallCount++;
[expectation fulfill];
};
// Should take MLNAnimationDuration seconds (0.3)
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
[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;
__block NSInteger delegateCallCount = 0;
expectation.expectedFulfillmentCount = 3;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self; CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
__block NSInteger delegateCallCount = 0; CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
expectation.expectedFulfillmentCount = 3;
expectation.assertForOverFulfill = YES;
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0); __block BOOL runloop = YES;
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
__block BOOL runloop = YES; NSTimeInterval stop0 = CACurrentMediaTime();
__block NSTimeInterval stop1 = 0.0;
__block NSTimeInterval stop2 = 0.0;
NSTimeInterval stop0 = CACurrentMediaTime(); double zoomLevel = 5.0;
__block NSTimeInterval stop1 = 0.0; double altitude =
__block NSTimeInterval stop2 = 0.0; MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
double zoomLevel = 5.0; self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
double altitude = MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size); MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself; MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (!strongSelf) return; CLLocationCoordinate2D center = mapView.centerCoordinate;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading); switch (delegateCallCount) {
case 0: {
stop1 = CACurrentMediaTime();
CLLocationCoordinate2D center = mapView.centerCoordinate; // Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target");
switch(delegateCallCount) { MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
case 0: @"center coordinate latitude should be at target");
{
stop1 = CACurrentMediaTime();
// Our center coordinate should match our target (assuming we're not // Now set another coordinate.
// constrained by zoom level) MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2
MLNTestAssertEqualWithAccuracy(strongSelf, altitude:altitude
target.longitude, pitch:0.0
center.longitude, heading:0.0];
0.0005,
@"center coordinate longitude should be at target");
MLNTestAssertEqualWithAccuracy(strongSelf, // flyToCamera can take a while...
target.latitude, [mapView flyToCamera:camera
center.latitude, completionHandler:^{
0.0005, MLNTestAssert(strongSelf, !runloop,
@"center coordinate latitude should be at target"); @"Completion block should be called after delegate method");
[expectation fulfill];
stop2 = CACurrentMediaTime();
}];
break;
}
// Now set another coordinate. case 1: {
MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2 // Our center coordinate should match our target (assuming we're not
altitude:altitude // constrained by zoom level)
pitch:0.0 MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
heading:0.0]; @"center coordinate longitude should be at target2");
// flyToCamera can take a while... MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
[mapView flyToCamera:camera completionHandler:^{ @"center coordinate latitude should be at target2");
MLNTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method");
[expectation fulfill];
stop2 = CACurrentMediaTime();
}];
break;
}
case 1: runloop = NO;
{ break;
// Our center coordinate should match our target (assuming we're not }
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2");
MLNTestAssertEqualWithAccuracy(strongSelf, default:
target2.latitude, MLNTestFail(strongSelf);
center.latitude, break;
0.0005, }
@"center coordinate latitude should be at target2");
runloop = NO; delegateCallCount++;
break;
}
default: [expectation fulfill];
MLNTestFail(strongSelf); };
break;
}
delegateCallCount++; // Should take MLNAnimationDuration
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
[expectation fulfill]; [self waitForExpectations:@[ expectation ] timeout:10];
};
// Should take MLNAnimationDuration NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES]; NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
[self waitForExpectations:@[expectation] timeout:10]; XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld",
(long)delegateCallCount); // Once for the setDirection and once for the reset north
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", (long)delegateCallCount); // Once for the setDirection and once for the reset north
} }
// 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.
// //
// Possible solutions/expectations: // Possible solutions/expectations:
// - If you set camera parameters that match the *current* target parameters // - If you set camera parameters that match the *current* target parameters
// then the transition could be a no-op. We'd need to consider any completion // then the transition could be a no-op. We'd need to consider any completion
// block // block
// - Ideally we would detect this case and disallow it. // - Ideally we would detect this case and disallow it.
// 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];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2; expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES; expectation.assertForOverFulfill = YES;
self.regionIsChanging = ^(MLNMapView *mapView) { self.regionIsChanging = ^(MLNMapView *mapView) {
[mapView resetNorth]; [mapView resetNorth];
}; };
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill]; [expectation fulfill];
}; };
[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 {
// See above comment in `-testContinuallyResettingNorthInIsChangingPENDING` // See above comment in `-testContinuallyResettingNorthInIsChangingPENDING`
// Reset to non-zero, prior to testing // Reset to non-zero, prior to testing
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO]; [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"]; XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2; expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES; expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self; __weak MLNCameraTransitionTests *weakself = self;
self.regionIsChanging = ^(MLNMapView *mapView) { self.regionIsChanging = ^(MLNMapView *mapView) {
[weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES]; [weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
}; };
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) { self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill]; [expectation fulfill];
}; };
[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>
@@ -12,240 +12,247 @@
// MARK: - Tests // MARK: - Tests
- (void)waitForMapViewToBeRendered { - (void)waitForMapViewToBeRendered {
[self waitForMapViewToBeRenderedWithTimeout:10]; [self waitForMapViewToBeRenderedWithTimeout:10];
} }
// This test does not strictly need to be in this test file/target. Including here for convenience. // This test does not strictly need to be in this test file/target. Including here for convenience.
- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle { - (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
__weak id weakLayer = layer; __weak id weakLayer = layer;
layer = nil; layer = nil;
XCTAssertNil(weakLayer); XCTAssertNil(weakLayer);
} }
- (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 {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0]; [self.style insertLayer:layer atIndex:0];
weakLayer = layer; weakLayer = layer;
// Nil the layer prior to remove to ensure it's being retained // Nil the layer prior to remove to ensure it's being retained
layer = nil; layer = nil;
[self.style removeLayer:weakLayer]; [self.style removeLayer:weakLayer];
} }
XCTAssertNil(weakLayer); XCTAssertNil(weakLayer);
}; };
addRemoveGLLayer(); addRemoveGLLayer();
addRemoveGLLayer(); addRemoveGLLayer();
addRemoveGLLayer(); addRemoveGLLayer();
} }
- (void)testReusingOpenGLLayerIdentifier { - (void)testReusingOpenGLLayerIdentifier {
__weak MLNCustomStyleLayer *weakLayer2; __weak MLNCustomStyleLayer *weakLayer2;
@autoreleasepool { @autoreleasepool {
MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer1 atIndex:0]; [self.style insertLayer:layer1 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer1];
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer2 = layer2;
XCTAssertNotNil(layer2);
XCTAssert(layer1 != layer2);
[self.style insertLayer:layer2 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer2];
XCTAssertNil([layer1 style]);
XCTAssertNil([layer2 style]);
}
// At this point, layer2 (and layer1) should still be around,
// since the render process needs to keep a reference to them.
XCTAssertNotNil(weakLayer2);
// Let render loop run enough to release the layers
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
XCTAssertNil(weakLayer2); [self.style removeLayer:layer1];
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer2 = layer2;
XCTAssertNotNil(layer2);
XCTAssert(layer1 != layer2);
[self.style insertLayer:layer2 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer2];
XCTAssertNil([layer1 style]);
XCTAssertNil([layer2 style]);
}
// At this point, layer2 (and layer1) should still be around,
// since the render process needs to keep a reference to them.
XCTAssertNotNil(weakLayer2);
// Let render loop run enough to release the layers
[self waitForMapViewToBeRendered];
XCTAssertNil(weakLayer2);
} }
- (void)testAddingRemovingOpenGLLayer { - (void)testAddingRemovingOpenGLLayer {
XCTAssertNotNil(self.style); XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{ void (^addRemoveGLLayer)(void) = ^{
__weak id retrievedLayer = nil;
__weak id retrievedLayer = nil; @autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
@autoreleasepool { [self waitForMapViewToBeRendered];
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered]; retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
XCTAssertNotNil(retrievedLayer);
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"]; [self.style removeLayer:retrievedLayer];
XCTAssertNotNil(retrievedLayer); [self waitForMapViewToBeRendered];
}
[self.style removeLayer:retrievedLayer]; XCTAssertNil(retrievedLayer);
[self waitForMapViewToBeRendered]; };
}
XCTAssertNil(retrievedLayer); addRemoveGLLayer();
}; addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
} }
- (void)testReusingOpenGLLayer { - (void)testReusingOpenGLLayer {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0]; [self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
[self.style removeLayer:layer]; [self.style removeLayer:layer];
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
[self.style insertLayer:layer atIndex:0]; [self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
[self.style removeLayer:layer]; [self.style removeLayer:layer];
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
} }
- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { - (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle {
__weak id weakLayer; __weak id weakLayer;
@autoreleasepool { @autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer; weakLayer = layer;
[self.style insertLayer:layer atIndex:0]; [self.style insertLayer:layer atIndex:0];
layer = nil; layer = nil;
[self waitForMapViewToBeRendered];
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
}
MLNStyleLayer *layer2 = weakLayer;
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
}
layer2 = nil; MLNStyleLayer *layer2 = weakLayer;
XCTAssertNil(weakLayer);
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
layer2 = nil;
XCTAssertNil(weakLayer);
} }
- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged { - (void)testOpenGLLayerDoesNotLeakWhenStyleChanged {
__weak MLNCustomStyleLayer *weakLayer; __weak MLNCustomStyleLayer *weakLayer;
@autoreleasepool { @autoreleasepool {
{ {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer; weakLayer = layer;
[self.style insertLayer:layer atIndex:0]; [self.style insertLayer:layer atIndex:0];
layer = nil; layer = nil;
}
} }
}
XCTAssertNotNil(weakLayer); XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered]; [self waitForMapViewToBeRendered];
MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; withExtension:@"json"];
[self.mapView setStyleURL:styleURL]; self.styleLoadingExpectation =
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:10]; [self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:10];
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed // At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been
XCTAssert(weakLayer == layer2); // NULLed
XCTAssertNotNil(weakLayer); XCTAssert(weakLayer == layer2);
XCTAssertNotNil(weakLayer);
// Asking the style for the layer should return nil // Asking the style for the layer should return nil
MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
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"
withExtension:@"json"];
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds
styleURL:styleURL];
mapView2.delegate = self;
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; XCTAssertNil(mapView2.style);
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL];
mapView2.delegate = self;
XCTAssertNil(mapView2.style); self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self waitForExpectationsWithTimeout:10 handler:nil]; weakLayer = layer;
[mapView2.style insertLayer:layer atIndex:0];
layer = nil;
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"]; [self waitForMapViewToBeRendered];
weakLayer = layer; }
[mapView2.style insertLayer:layer atIndex:0]; XCTAssertNil(weakLayer);
layer = nil;
[self waitForMapViewToBeRendered];
}
XCTAssertNil(weakLayer);
} }
- (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.mapView setStyleURL:styleURL]; self.styleLoadingExpectation =
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO]; [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:1]; [self.mapView setStyleURL:styleURL];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:1];
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl]; 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
[self.style addSource:source]; count:sizeof(coordinates) / sizeof(coordinates[0])];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap"
shape:points
options:nil];
[self.style addSource:source];
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source]; MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer"
[self.style addLayer:heatmapLayer]; source:source];
[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];
} }
@end @end

View File

@@ -1,314 +1,327 @@
#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
#define ASSERT_NATIVE_DELEGATE_IS_NIL() \ #define ASSERT_NATIVE_DELEGATE_IS_NIL() \
XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
#define ASSERT_NATIVE_DELEGATE_IS_NOT_NIL() \ #define ASSERT_NATIVE_DELEGATE_IS_NOT_NIL() \
XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]) XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
// NOTE: These tests are currently assumed to run in this specific order. // NOTE: These tests are currently assumed to run in this specific order.
@implementation MLNNetworkConfigurationIntegrationTests @implementation MLNNetworkConfigurationIntegrationTests
- (void)setUp { - (void)setUp {
[super setUp]; [super setUp];
// Reset before each test // Reset before each test
[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 =
XCTAssertNotNil(manager); MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
XCTAssertNotNil(manager);
// Expected properties // Expected properties
XCTAssertNotNil([manager sessionConfiguration]); XCTAssertNotNil([manager sessionConfiguration]);
[MLNNetworkConfiguration sharedManager]; [MLNNetworkConfiguration sharedManager];
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
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
// singleton and created with a dispatch_once, the delegate is re-set for // singleton and created with a dispatch_once, the delegate is re-set for
// each call. // each call.
[MLNNetworkConfiguration sharedManager]; [MLNNetworkConfiguration sharedManager];
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 =
XCTAssertNotNil(manager); MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
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];
[[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate]; [[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate];
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate]; id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
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]`,
// `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate. // `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate.
[MLNOfflineStorage sharedOfflineStorage]; [MLNOfflineStorage sharedOfflineStorage];
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 isMainThread = YES;
__block BOOL didCallSessionDelegate = NO; // Setup delegate object that provides a NSURLSession
__block BOOL isMainThread = YES; MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
MLNNetworkConfigurationTestDelegate *delegate =
// Setup delegate object that provides a NSURLSession [[MLNNetworkConfigurationTestDelegate alloc] init];
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager]; delegate.handler = ^{
MLNNetworkConfigurationTestDelegate *delegate = [[MLNNetworkConfigurationTestDelegate alloc] init]; NSURLSession *internalSession;
delegate.handler = ^{ @synchronized(self) {
NSURLSession *internalSession; didCallSessionDelegate = YES;
@synchronized (self) { isMainThread = [NSThread isMainThread];
didCallSessionDelegate = YES; internalSession = session;
isMainThread = [NSThread isMainThread];
internalSession = session;
}
return internalSession;
};
manager.delegate = delegate;
// The following is modified/taken from MLNOfflineStorageTests as we do not yet have a
// good mechanism to test FileSource (in this SDK)
//
// Want to ensure we download from the network; nuclear option
{
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."];
[[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:10 handler:nil];
} }
return internalSession;
};
// Boston manager.delegate = delegate;
MLNCoordinateBounds bounds = {
{ .latitude = 42.360, .longitude = -71.056 },
{ .latitude = 42.358, .longitude = -71.053 },
};
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:20
toZoomLevel:20];
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{ // The following is modified/taken from MLNOfflineStorageTests as we do not yet have a
@"Name": @"Faneuil Hall" // good mechanism to test FileSource (in this SDK)
}]; //
// Want to ensure we download from the network; nuclear option
__block MLNOfflinePack *pack = nil; {
XCTestExpectation *expectation =
// Add pack [self expectationWithDescription:@"Expect database to be reset without an error."];
{ [[MLNOfflineStorage sharedOfflineStorage]
XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"]; resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error);
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region [expectation fulfill];
withContext:context
completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) {
pack = completionHandlerPack;
[additionCompletionHandlerExpectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:5 handler:nil]; [self waitForExpectationsWithTimeout:10 handler:nil];
} }
XCTAssert(pack.state == MLNOfflinePackStateInactive); // Boston
MLNCoordinateBounds bounds = {
{.latitude = 42.360, .longitude = -71.056},
{.latitude = 42.358, .longitude = -71.053},
};
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
MLNTilePyramidOfflineRegion *region =
[[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
bounds:bounds
fromZoomLevel:20
toZoomLevel:20];
// Download NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"Name" : @"Faneuil Hall"}];
{
XCTestExpectation *expectation = [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) { __block MLNOfflinePack *pack = nil;
return pack.state == MLNOfflinePackStateComplete;
// Add pack
{
XCTestExpectation *additionCompletionHandlerExpectation =
[self expectationWithDescription:@"add pack completion handler"];
[[MLNOfflineStorage sharedOfflineStorage]
addPackForRegion:region
withContext:context
completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack,
NSError *_Nullable error) {
pack = completionHandlerPack;
[additionCompletionHandlerExpectation fulfill];
}]; }];
[self waitForExpectationsWithTimeout:5 handler:nil];
}
expectation.inverted = !shouldDownload; XCTAssert(pack.state == MLNOfflinePackStateInactive);
[pack resume];
[self waitForExpectations:@[expectation] timeout:15]; // Download
} {
XCTestExpectation *expectation =
[self expectationForNotification:MLNOfflinePackProgressChangedNotification
object:pack
handler:^BOOL(NSNotification *_Nonnull notification) {
return pack.state == MLNOfflinePackStateComplete;
}];
XCTAssert(didCallSessionDelegate); expectation.inverted = !shouldDownload;
XCTAssertFalse(isMainThread); [pack resume];
// Remove pack, so we don't affect other tests [self waitForExpectations:@[ expectation ] timeout:15];
{ }
XCTestExpectation *expectation = [self expectationWithDescription:@"remove pack completion handler"];
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) { XCTAssert(didCallSessionDelegate);
[expectation fulfill]; XCTAssertFalse(isMainThread);
}];
[self waitForExpectationsWithTimeout:5 handler:nil]; // Remove pack, so we don't affect other tests
} {
XCTestExpectation *expectation =
[self expectationWithDescription:@"remove pack completion handler"];
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack
withCompletionHandler:^(NSError *_Nullable error) {
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:nil];
}
} }
- (void)testNetworkConfigurationWithSharedSessionLOCKED { - (void)testNetworkConfigurationWithSharedSessionLOCKED {
NSURLSession *session = [NSURLSession sharedSession]; NSURLSession *session = [NSURLSession sharedSession];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
- (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
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]); XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]);
// We cannot do this yet, as it requires intecepting the exception in gl-native // 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 =
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; [NSURLSessionConfiguration defaultSessionConfiguration];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
- (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED { - (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSessionConfiguration *sessionConfig =
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; [NSURLSessionConfiguration ephemeralSessionConfiguration];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
- (void)testNetworkConfigurationWithSessionConfigurationWithDelegateLOCKED { - (void)testNetworkConfigurationWithSessionConfigurationWithDelegateLOCKED {
__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];
delegate.authHandler = ^{ MLNNetworkConfigurationSessionDelegate *delegate =
@synchronized (self) { [[MLNNetworkConfigurationSessionDelegate alloc] init];
didCallAuthChallenge = YES; delegate.authHandler = ^{
isMainThread = [NSThread isMainThread]; @synchronized(self) {
} didCallAuthChallenge = YES;
}; isMainThread = [NSThread isMainThread];
}
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:delegate delegate:delegate
delegateQueue:nil]; delegateQueue:nil];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:NO]; [self internalTestNetworkConfigurationWithSession:session shouldDownload:NO];
[session finishTasksAndInvalidate]; [session finishTasksAndInvalidate];
XCTAssertFalse(isMainThread); XCTAssertFalse(isMainThread);
XCTAssert(didCallAuthChallenge); XCTAssert(didCallAuthChallenge);
} }
- (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];
delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) { MLNNetworkConfigurationSessionDataDelegate *delegate =
@synchronized (self) { [[MLNNetworkConfigurationSessionDataDelegate alloc] init];
didCallReceiveData = YES; delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) {
} @synchronized(self) {
}; didCallReceiveData = YES;
// NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate
// are NOT supported.
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:delegate
delegateQueue:nil];
BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)];
XCTAssert(conforms);
#ifdef DEBUG
if (conforms) {
NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported");
} }
#else };
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
#endif
[session finishTasksAndInvalidate];
XCTAssertFalse(didCallReceiveData); // NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate
// are NOT supported.
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:delegate
delegateQueue:nil];
BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)];
XCTAssert(conforms);
#ifdef DEBUG
if (conforms) {
NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported");
}
#else
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
#endif
[session finishTasksAndInvalidate];
XCTAssertFalse(didCallReceiveData);
} }
- (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED { - (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.HTTPMaximumConnectionsPerHost = 1; sessionConfig.HTTPAdditionalHeaders = @{@"testing" : @YES};
sessionConfig.timeoutIntervalForResource = 30; sessionConfig.HTTPMaximumConnectionsPerHost = 1;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; sessionConfig.timeoutIntervalForResource = 30;
sessionConfig.URLCache = nil; sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfig.URLCache = nil;
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig]; NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES]; [self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
} }
@end @end
@@ -317,33 +330,38 @@
@implementation MLNNetworkConfigurationTestDelegate @implementation MLNNetworkConfigurationTestDelegate
- (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration { - (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration {
if (self.handler) { if (self.handler) {
return self.handler(); return self.handler();
} }
return nil; return nil;
} }
@end @end
// 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
if (self.authHandler) { didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
self.authHandler(); completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
} NSURLCredential *_Nullable credential))completionHandler {
if (self.authHandler) {
self.authHandler();
}
// Cancel the challenge // Cancel the challenge
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
} }
@end @end
// 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
if (self.dataHandler) { dataTask:(NSURLSessionDataTask *)dataTask
self.dataHandler(dataTask, data); didReceiveData:(NSData *)data {
} if (self.dataHandler) {
self.dataHandler(dataTask, data);
}
} }
@end @end

File diff suppressed because it is too large Load Diff

View File

@@ -7,18 +7,17 @@
- (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
[MLNSettings useWellKnownTileServer:MLNMapTiler]; [MLNSettings useWellKnownTileServer:MLNMapTiler];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [MBXBenchViewController new]; self.window.rootViewController = [MBXBenchViewController new];
[self.window makeKeyAndVisible]; [self.window makeKeyAndVisible];
return YES; return YES;
} }
@end @end

View File

@@ -19,31 +19,30 @@
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
frameEncodingTime:status.renderingStats.encodingTime frameEncodingTime:status.renderingStats.encodingTime
frameRenderingTime:status.renderingStats.renderingTime]; frameRenderingTime:status.renderingStats.renderingTime];
} }
protected: protected:
__weak id<BenchMapDelegate> mapDelegate = nullptr; __weak id<BenchMapDelegate> mapDelegate = nullptr;
}; };
@interface MBXBenchViewController () <BenchMapDelegate> { @interface MBXBenchViewController () <BenchMapDelegate> {
std::unique_ptr<BenchMapObserver> observer; std::unique_ptr<BenchMapObserver> observer;
std::unique_ptr<mbgl::HeadlessFrontend> frontend; std::unique_ptr<mbgl::HeadlessFrontend> frontend;
std::unique_ptr<mbgl::Map> map; std::unique_ptr<mbgl::Map> map;
} }
@property (nonatomic) UIImageView *imageView; @property (nonatomic) UIImageView *imageView;
@@ -54,125 +53,129 @@ protected:
// MARK: - Setup // MARK: - Setup
+ (void)initialize + (void)initialize {
{ if (self == [MBXBenchViewController class]) {
if (self == [MBXBenchViewController class]) [[NSUserDefaults standardUserDefaults] registerDefaults:@{
{ @"MBXUserTrackingMode" : @(MLNUserTrackingModeNone),
[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"MBXShowsUserLocation" : @NO,
@"MBXUserTrackingMode": @(MLNUserTrackingModeNone), @"MBXDebug" : @NO,
@"MBXShowsUserLocation": @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"
const std::vector<std::string> styles = { subdirectory:@"tiles/tiles/v3/5/7"];
"maptiler://maps/streets", NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles"
}; withExtension:@"json"
constexpr auto styleIndex = 0; subdirectory:@"tiles"];
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]]; const std::vector<std::string> styles = {
NSLog(@"Using style URL: \"%@\"", [url absoluteString]); "maptiler://maps/streets",
};
constexpr auto styleIndex = 0;
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json"
: [NSString stringWithCString:styles[styleIndex].c_str()
encoding:NSUTF8StringEncoding]];
NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.imageView.autoresizingMask =
[self.view addSubview:self.imageView]; UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[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::ContextMode::Unique,
mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush, /* localFontFamily */ std::nullopt,
mbgl::gfx::ContextMode::Unique, /* invalidateOnUpdate */ false);
/* localFontFamily */ std::nullopt,
/* invalidateOnUpdate */ false
);
mbgl::MapOptions mapOptions; mbgl::MapOptions mapOptions;
mapOptions.withMapMode(mbgl::MapMode::Continuous) mapOptions.withMapMode(mbgl::MapMode::Continuous)
.withSize(viewSize) .withSize(viewSize)
.withPixelRatio(pixelRatio) .withPixelRatio(pixelRatio)
.withConstrainMode(mbgl::ConstrainMode::None) .withConstrainMode(mbgl::ConstrainMode::None)
.withViewportMode(mbgl::ViewportMode::Default) .withViewportMode(mbgl::ViewportMode::Default)
.withCrossSourceCollisions(true); .withCrossSourceCollisions(true);
mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal]; mbgl::TileServerOptions *tileServerOptions =
mbgl::ResourceOptions resourceOptions; [[MLNSettings sharedSettings] tileServerOptionsInternal];
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String) mbgl::ResourceOptions resourceOptions;
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String) resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
.withTileServerOptions(*tileServerOptions); .withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
mbgl::ClientOptions clientOptions; .withTileServerOptions(*tileServerOptions);
mbgl::ClientOptions clientOptions;
auto apiKey = [[MLNSettings sharedSettings] apiKey]; auto apiKey = [[MLNSettings sharedSettings] apiKey];
if (apiKey) { if (apiKey) {
resourceOptions.withApiKey([apiKey UTF8String]); resourceOptions.withApiKey([apiKey UTF8String]);
} }
map = std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions); map =
map->setSize(viewSize); std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
map->setDebug(mbgl::MapDebugOptions::NoDebug); map->setSize(viewSize);
map->getStyle().loadURL([url.absoluteString UTF8String]); map->setDebug(mbgl::MapDebugOptions::NoDebug);
map->getStyle().loadURL([url.absoluteString UTF8String]);
[self startBenchmarkIteration]; [self startBenchmarkIteration];
} }
// 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];
[formatter setDateFormat:@"yyyy-MM-dd-HHmm"]; [formatter setDateFormat:@"yyyy-MM-dd-HHmm"];
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 =
NSString *documentsDirectory = [allPaths objectAtIndex:0]; NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent: filename]; NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command"); NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:filename];
NSLog(@" open \"%@\"", pathForLog); NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
NSLog(@" open \"%@\"", pathForLog);
[self redirectLogToDocuments :pathForLog]; [self redirectLogToDocuments:pathForLog];
} }
- (void)redirectLogToDocuments: (NSString*) fileName - (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);
} }
size_t idx = 0; size_t idx = 0;
@@ -181,163 +184,153 @@ 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();
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, CFRelease(colorSpace);
8, CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
4 * image.size.width, CGContextRelease(bitmapContext);
colorSpace,
kCGImageAlphaPremultipliedLast
);
CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext);
self.imageView.image = [UIImage imageWithCGImage:cgImage]; self.imageView.image = [UIImage imageWithCGImage:cgImage];
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>(
map->easeTo(cameraOptions, animationOptions); std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
map->easeTo(cameraOptions, animationOptions);
state = State::WaitingForAssets; state = State::WaitingForAssets;
NSLog(@"Benchmarking \"%s\"", location.name.c_str()); NSLog(@"Benchmarking \"%s\"", location.name.c_str());
NSLog(@"- Loading assets..."); NSLog(@"- Loading assets...");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame]; [self renderFrame];
}); });
} else { } else {
// Do nothing. The benchmark is completed. // Do nothing. The benchmark is completed.
NSLog(@"Benchmark completed."); NSLog(@"Benchmark completed.");
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(),
totalFrameEncodingTime += row.second.first; 1e3 * row.second.first, 1e3 * row.second.second);
totalFrameRenderingTime += row.second.second; totalFrameEncodingTime += row.second.first;
} totalFrameRenderingTime += row.second.second;
}
NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size()); NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size());
NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size()); NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size());
// NSLog(@"Total uploads: %zu", mbgl::uploadCount); // NSLog(@"Total uploads: %zu", mbgl::uploadCount);
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty); // NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments); // NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount); // NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
#if !defined(NDEBUG) #if !defined(NDEBUG)
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests. // Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
// TODO: This doesn't clean up everything, what are we missing? // TODO: This doesn't clean up everything, what are we missing?
map.reset(); map.reset();
observer.reset(); observer.reset();
frontend.reset(); frontend.reset();
#endif // !defined(NDEBUG) #endif // !defined(NDEBUG)
// 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)];
}
} }
}
} }
- (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++;
{ totalFrameEncodingTime += frameEncodingTime;
frames++; totalFrameRenderingTime += frameRenderingTime;
totalFrameEncodingTime += frameEncodingTime;
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();
state = State::None; if (duration >= benchmarkDuration * 1e6) {
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++;
[self startBenchmarkIteration]; [self startBenchmarkIteration];
} else { } else {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame]; [self renderFrame];
}); });
}
return;
} }
else if (state == State::WaitingForAssets) return;
{ } 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; totalFrameRenderingTime = 0;
totalFrameRenderingTime = 0; state = State::Benchmarking;
state = State::Benchmarking; started = std::chrono::steady_clock::now();
started = std::chrono::steady_clock::now(); NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return;
} }
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return;
}
} }
- (NSUInteger)supportedInterfaceOrientations - (NSUInteger)supportedInterfaceOrientations {
{ return UIInterfaceOrientationMaskLandscape;
return UIInterfaceOrientationMaskLandscape;
} }
@end @end

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