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
BasedOnStyle: Google
ObjCSpaceAfterProperty: true
AccessModifierOffset: -2
...

View File

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

View File

@@ -1,17 +1,17 @@
#import "ExampleCustomDrawableStyleLayer.h"
#import "MLNStyleLayer.h"
#import "MLNCustomDrawableStyleLayer.h"
#import "MLNStyleLayer.h"
#import <UIKit/UIKit.h>
#include <mbgl/util/image+MLNAdditions.hpp>
#include <mbgl/layermanager/layer_factory.hpp>
#include <mbgl/style/layer.hpp>
#include <mbgl/style/layers/custom_drawable_layer.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/image+MLNAdditions.hpp>
#include <memory>
#include <cmath>
#include <memory>
@interface MLNCustomDrawableStyleLayer (Internal)
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer;
@@ -22,271 +22,302 @@ class ExampleCustomDrawableStyleLayerHost;
@implementation ExampleCustomDrawableStyleLayer
- (instancetype)initWithIdentifier:(NSString *)identifier {
auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(identifier.UTF8String,
std::make_unique<ExampleCustomDrawableStyleLayerHost>(self));
return self = [super initWithPendingLayer:std::move(layer)];
auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(
identifier.UTF8String, std::make_unique<ExampleCustomDrawableStyleLayerHost>(self));
return self = [super initWithPendingLayer:std::move(layer)];
}
@end
class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost {
public:
ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
}
void initialize() override {
if (layerRef == nil)
return;
else if (layer == nil)
layer = layerRef;
}
void update(Interface &interface) override {
// if we have built our drawable(s) already, either update or skip
if (interface.getDrawableCount()) return;
constexpr float extent = mbgl::util::EXTENT;
// add classic polylines
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
/*width=*/8.0f, /*color=*/Color::red()},
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
/*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
/*width=*/0.5f, /*color=*/Color::black()},
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints,
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index{0}; index < numLines; ++index) {
for (auto &p : polyline) {
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
}
}
void initialize() override {
if (layerRef == nil) return;
else if (layer == nil) layer = layerRef;
// add wide vector polylines with tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines]{
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
/*width=*/8.0f, /*color=*/Color::red()},
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
/*width=*/4.0f, /*color=*/Color::blue()},
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
/*width=*/0.5f, /*color=*/Color::black()},
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
};
for (auto &opt : options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints,
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index{0}; index < numLines; ++index) {
for (auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone
for (auto &p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for (auto &p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
}
void update(Interface& interface) override {
// add fill polygon
{
using namespace mbgl;
// if we have built our drawable(s) already, either update or skip
if (interface.getDrawableCount())
return;
// set tile
interface.setTileID({11, 327, 790});
constexpr float extent = mbgl::util::EXTENT;
GeometryCollection geometry{
{
// ring 1
{static_cast<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
{
using namespace mbgl;
// set properties
interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index {0}; index < numLines; ++index) {
for(auto &p : polyline) {
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
}
}
// add wide vector polylines with tile coordinates
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 792});
constexpr auto numLines = 6;
Interface::LineOptions options[numLines] {
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
};
for(auto& opt: options) {
opt.geometry.beginCap = style::LineCapType::Butt;
opt.geometry.endCap = style::LineCapType::Butt;
opt.geometry.joinType = style::LineJoinType::Miter;
}
constexpr auto numPoints = 10;
GeometryCoordinates polyline;
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
}
for (auto index {0}; index < numLines; ++index) {
for(auto &p : polyline) {
if (0 == index) p.y += 0.25f * extent / numLines;
p.y += extent / numLines;
}
// set property values
interface.setLineOptions(options[index]);
// add polyline
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
// add clone
for(auto &p : polyline) {
p.y += 0.05f * extent / numLines;
}
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
for(auto &p : polyline) {
p.y -= 0.05f * extent / numLines;
}
}
}
// add fill polygon
{
using namespace mbgl;
// set tile
interface.setTileID({11, 327, 790});
GeometryCollection geometry{
{
// ring 1
{static_cast<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();
// add fill
interface.addFill(geometry);
}
void deinitialize() override {
if (layer == nil) return;
// add symbol
{
using namespace mbgl;
layerRef = layer;
layer = nil;
// set tile
interface.setTileID({11, 327, 789});
GeometryCoordinate position{static_cast<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:
__weak ExampleCustomDrawableStyleLayer * layerRef;
ExampleCustomDrawableStyleLayer * layer = nil;
__weak ExampleCustomDrawableStyleLayer *layerRef;
ExampleCustomDrawableStyleLayer *layer = nil;
};

View File

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

View File

@@ -10,224 +10,208 @@
#import <MetalKit/MetalKit.h>
#import "Mapbox.h"
typedef struct
{
vector_float2 position;
vector_float4 color;
typedef struct {
vector_float2 position;
vector_float4 color;
} Vertex;
@interface PluginLayerExampleMetalRendering () {
// The render pipeline state
id<MTLRenderPipelineState> _pipelineState;
id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
// Properties
float _offsetX;
float _offsetY;
float _scale;
float _r, _g, _b, _a;
// The render pipeline state
id<MTLRenderPipelineState> _pipelineState;
id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
// Properties
float _offsetX;
float _offsetY;
float _scale;
float _r, _g, _b, _a;
}
@end
@implementation PluginLayerExampleMetalRendering
// This is a static class method that is used by the MLNMapView to create the
// required marshalling classes to interface with the MapLibre core.
+(MLNPluginLayerCapabilities *)layerCapabilities {
+ (MLNPluginLayerCapabilities *)layerCapabilities {
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES;
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
tempResult.layerID = @"plugin-layer-metal-rendering";
tempResult.requiresPass3D = YES;
// Define the paint properties that this layer implements and
// what types they are
tempResult.layerProperties = @[
// The scale property
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// Define the paint properties that this layer implements and
// what types they are
tempResult.layerProperties = @[
// The scale property
[MLNPluginLayerProperty propertyWithName:@"scale"
propertyType:MLNPluginLayerPropertyTypeSingleFloat
defaultValue:@(1.0)],
// The fill color property
[MLNPluginLayerProperty propertyWithName:@"fill-color"
propertyType:MLNPluginLayerPropertyTypeColor
defaultValue:[UIColor blueColor]]
// The fill color property
[MLNPluginLayerProperty propertyWithName:@"fill-color"
propertyType:MLNPluginLayerPropertyTypeColor
defaultValue:[UIColor blueColor]]
];
return tempResult;
];
return tempResult;
}
- (void)createShaders:(MLNMapView *)mapView {
MLNBackendResource* resource = [mapView backendResource];
MLNBackendResource *resource = [mapView backendResource];
NSString *shaderSource = @
NSString *shaderSource =
@
" #include <metal_stdlib>\n"
" using namespace metal;\n"
" typedef struct\n"
" {\n"
" vector_float2 position;\n"
" vector_float4 color;\n"
" } Vertex;\n"
" struct RasterizerData\n"
" {\n"
" float4 position [[position]];\n"
" float4 color;\n"
" };\n"
" vertex RasterizerData\n"
" vertexShader(uint vertexID [[vertex_id]],\n"
" constant Vertex *vertices [[buffer(0)]],\n"
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
" {\n"
" RasterizerData out;\n"
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
" out.color = vertices[vertexID].color;\n"
" return out;\n"
" }\n"
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
" {\n"
" return in.color;\n"
" }\n";
" using namespace metal;\n"
" typedef struct\n"
" {\n"
" vector_float2 position;\n"
" vector_float4 color;\n"
" } Vertex;\n"
" struct RasterizerData\n"
" {\n"
" float4 position [[position]];\n"
" float4 color;\n"
" };\n"
" vertex RasterizerData\n"
" vertexShader(uint vertexID [[vertex_id]],\n"
" constant Vertex *vertices [[buffer(0)]],\n"
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
" {\n"
" RasterizerData out;\n"
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
" out.color = vertices[vertexID].color;\n"
" return out;\n"
" }\n"
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
" {\n"
" return in.color;\n"
" }\n";
NSError *error = nil;
id<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;
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"];
// Configure a pipeline descriptor that is used to create a pipeline state.
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
// Configure a pipeline descriptor that is used to create a pipeline state.
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed
depthStencilDescriptor.depthWriteEnabled = NO;
_depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
depthStencilDescriptor.depthCompareFunction =
MTLCompareFunctionAlways; // Or another value as needed
depthStencilDescriptor.depthWriteEnabled = NO;
_depthStencilStateWithoutStencil =
[_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
}
// Setup the rendering environment
-(void)setupRendering:(MLNMapView *)mapView {
[self createShaders:mapView];
if (_scale == 0) {
_scale = 1;
}
// Set the initial
_a = 1;
- (void)setupRendering:(MLNMapView *)mapView {
[self createShaders:mapView];
if (_scale == 0) {
_scale = 1;
}
// Set the initial
_a = 1;
}
// The overrides
-(void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
- (void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
MLNBackendResource *resource = [mapView backendResource];
MLNBackendResource* resource = [mapView backendResource];
if (_pipelineState == nil) {
[self setupRendering:mapView];
}
if (_pipelineState == nil) {
[self setupRendering:mapView];
}
if (renderEncoder == nil) {
return;
}
if (renderEncoder == nil) {
return;
}
vector_uint2 _viewportSize;
_viewportSize.x = resource.mtkView.drawableSize.width;
_viewportSize.y = resource.mtkView.drawableSize.height;
Vertex triangleVerticesWithColor[] = {
// 2D positions, RGBA colors
{{(250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
{{(-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
{{(0 + _offsetX) * _scale, (250 + _offsetY) * _scale}, {_r, _g, _b, _a}},
};
vector_uint2 _viewportSize;
_viewportSize.x = resource.mtkView.drawableSize.width;
_viewportSize.y = resource.mtkView.drawableSize.height;
[renderEncoder setRenderPipelineState:_pipelineState];
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
Vertex triangleVerticesWithColor[] = {
// 2D positions, RGBA colors
{ { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } },
{ { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} },
{ { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } },
};
// Pass in the parameter data.
[renderEncoder setVertexBytes:triangleVerticesWithColor
length:sizeof(triangleVerticesWithColor)
atIndex:0];
[renderEncoder setRenderPipelineState:_pipelineState];
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
[renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
// Pass in the parameter data.
[renderEncoder setVertexBytes:triangleVerticesWithColor
length:sizeof(triangleVerticesWithColor)
atIndex:0];
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:1];
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
// Draw the triangle.
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
}
-(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// This is called before the render call and is a place where
// any animations/etc can be updated
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// This is called before the render call and is a place where
// any animations/etc can be updated
}
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
//NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
// These two properties are in the "properties" section of the style sheet and
// are passed in at layer creation. These are not expression based properties
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
if (offsetX) {
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
// These two properties are in the "properties" section of the style sheet and
// are passed in at layer creation. These are not expression based properties
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
if (offsetX) {
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
}
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
if (offsetY) {
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
}
// These are the paint properties and can be updated each frame or zoom level
// change/etc depending on if they are static or expression based/etc.
NSNumber *scale = [layerProperties objectForKey:@"scale"];
if (scale) {
if ([scale isKindOfClass:[NSNumber class]]) {
_scale = [scale floatValue];
}
}
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
if (offsetY) {
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (fillColor) {
// NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","];
if ([components count] == 4) {
// Convert to float since they come in as 0..255
_r = [[components objectAtIndex:0] floatValue] / 255.0;
_g = [[components objectAtIndex:1] floatValue] / 255.0;
_b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue];
}
// These are the paint properties and can be updated each frame or zoom level
// change/etc depending on if they are static or expression based/etc.
NSNumber *scale = [layerProperties objectForKey:@"scale"];
if (scale) {
if ([scale isKindOfClass:[NSNumber class]]) {
_scale = [scale floatValue];
}
}
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
if (fillColor) {
//NSLog(@"Fill Color: %@", fillColor);
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
NSArray *components = [fillColor componentsSeparatedByString:@","];
if ([components count] == 4) {
// Convert to float since they come in as 0..255
_r = [[components objectAtIndex:0] floatValue] / 255.0;
_g = [[components objectAtIndex:1] floatValue] / 255.0;
_b = [[components objectAtIndex:2] floatValue] / 255.0;
_a = [[components objectAtIndex:3] floatValue];
}
}
}
}
@end

View File

@@ -9,59 +9,56 @@ namespace platform {
class Collator::Impl {
public:
Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
: options((caseSensitive ? 0 : NSCaseInsensitiveSearch) |
(diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch))
, locale(locale_ ?
[[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())] :
[NSLocale currentLocale])
{}
Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
: options((caseSensitive ? 0 : NSCaseInsensitiveSearch) |
(diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)),
locale(locale_ ? [[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())]
: [NSLocale currentLocale]) {}
bool operator==(const Impl& other) const {
return options == other.options &&
[[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]];
}
bool operator==(const Impl& other) const {
return options == other.options &&
[[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]];
}
int compare(const std::string& lhs, const std::string& rhs) const {
NSString* nsLhs = @(lhs.c_str());
NSString* nsRhs = @(rhs.c_str());
// Limiting the compare range to the length of the LHS seems weird, but
// experimentally we've checked that if LHS is a prefix of RHS compare returns -1
// https://developer.apple.com/documentation/foundation/nsstring/1414561-compare
NSRange compareRange = NSMakeRange(0, nsLhs.length);
int compare(const std::string& lhs, const std::string& rhs) const {
NSString* nsLhs = @(lhs.c_str());
NSString* nsRhs = @(rhs.c_str());
// Limiting the compare range to the length of the LHS seems weird, but
// experimentally we've checked that if LHS is a prefix of RHS compare returns -1
// https://developer.apple.com/documentation/foundation/nsstring/1414561-compare
NSRange compareRange = NSMakeRange(0, nsLhs.length);
return static_cast<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:
NSStringCompareOptions options;
NSLocale* locale;
NSStringCompareOptions options;
NSLocale* locale;
};
Collator::Collator(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_))
{}
Collator::Collator(bool caseSensitive, bool diacriticSensitive,
const std::optional<std::string>& locale_)
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_)) {}
bool Collator::operator==(const Collator& other) const {
return *impl == *(other.impl);
}
bool Collator::operator==(const Collator& other) const { return *impl == *(other.impl); }
int Collator::compare(const std::string& lhs, const std::string& rhs) const {
return impl->compare(lhs, rhs);
return impl->compare(lhs, rhs);
}
std::string Collator::resolvedLocale() const {
return impl->resolvedLocale();
}
std::string Collator::resolvedLocale() const { return impl->resolvedLocale(); }
} // namespace platform
} // namespace mbgl
} // namespace platform
} // namespace mbgl

View File

@@ -3,17 +3,17 @@
#include <mbgl/storage/resource_options.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/http_header.hpp>
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/async_request.hpp>
#include <mbgl/util/version.hpp>
#import <Foundation/Foundation.h>
#include <mbgl/interface/native_apple_interface.h>
#include <mutex>
#include <chrono>
#include <mutex>
@interface MBGLBundleCanary : NSObject
@end
@@ -26,422 +26,422 @@ namespace mbgl {
// Data that is shared between the requesting thread and the thread running the completion handler.
class HTTPRequestShared {
public:
HTTPRequestShared(Response& response_, util::AsyncTask& async_)
: response(response_),
async(async_) {
}
HTTPRequestShared(Response &response_, util::AsyncTask &async_)
: response(response_), async(async_) {}
void notify(const Response& response_) {
std::scoped_lock lock(mutex);
if (!cancelled) {
response = response_;
async.send();
}
void notify(const Response &response_) {
std::scoped_lock lock(mutex);
if (!cancelled) {
response = response_;
async.send();
}
}
void cancel() {
std::scoped_lock lock(mutex);
cancelled = true;
}
void cancel() {
std::scoped_lock lock(mutex);
cancelled = true;
}
private:
std::mutex mutex;
bool cancelled = false;
std::mutex mutex;
bool cancelled = false;
Response& response;
util::AsyncTask& async;
Response &response;
util::AsyncTask &async;
};
class HTTPRequest : public AsyncRequest {
public:
HTTPRequest(FileSource::Callback callback_)
: shared(std::make_shared<HTTPRequestShared>(response, async)),
callback(callback_) {
}
HTTPRequest(FileSource::Callback callback_)
: shared(std::make_shared<HTTPRequestShared>(response, async)), callback(callback_) {}
~HTTPRequest() override {
shared->cancel();
if (task) {
[task cancel];
task = nil;
}
~HTTPRequest() override {
shared->cancel();
if (task) {
[task cancel];
task = nil;
}
}
std::shared_ptr<HTTPRequestShared> shared;
NSURLSessionDataTask* task = nil;
std::shared_ptr<HTTPRequestShared> shared;
NSURLSessionDataTask *task = nil;
private:
FileSource::Callback callback;
Response response;
FileSource::Callback callback;
Response response;
util::AsyncTask async { [this] {
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
auto callback_ = callback;
auto response_ = response;
callback_(response_);
} };
util::AsyncTask async{[this] {
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
auto callback_ = callback;
auto response_ = response;
callback_(response_);
}};
};
class HTTPFileSource::Impl {
public:
Impl(const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_)
: resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {
NSURLSessionConfiguration *sessionConfig = MLNNativeNetworkManager.sharedManager.sessionConfiguration;
session = [NSURLSession sessionWithConfiguration:sessionConfig];
Impl(const ResourceOptions &resourceOptions_, const ClientOptions &clientOptions_)
: resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {
NSURLSessionConfiguration *sessionConfig =
MLNNativeNetworkManager.sharedManager.sessionConfiguration;
session = [NSURLSession sessionWithConfiguration:sessionConfig];
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
userAgent = getUserAgent();
} else {
userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
}
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
userAgent = getUserAgent();
} else {
userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
}
}
void setResourceOptions(ResourceOptions options);
ResourceOptions getResourceOptions();
void setResourceOptions(ResourceOptions options);
ResourceOptions getResourceOptions();
void setClientOptions(ClientOptions options);
ClientOptions getClientOptions();
void setClientOptions(ClientOptions options);
ClientOptions getClientOptions();
NSURLSession* session = nil;
NSString* userAgent = nil;
NSURLSession *session = nil;
NSString *userAgent = nil;
private:
NSString* getUserAgent() const;
NSBundle* getSDKBundle() const;
ResourceOptions resourceOptions;
ClientOptions clientOptions;
NSString *getUserAgent() const;
NSBundle *getSDKBundle() const;
ResourceOptions resourceOptions;
ClientOptions clientOptions;
};
void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) {
resourceOptions = options;
resourceOptions = options;
}
ResourceOptions HTTPFileSource::Impl::getResourceOptions() {
return resourceOptions.clone();
}
ResourceOptions HTTPFileSource::Impl::getResourceOptions() { return resourceOptions.clone(); }
void HTTPFileSource::Impl::setClientOptions(ClientOptions options) {
clientOptions = options;
}
void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { clientOptions = options; }
ClientOptions HTTPFileSource::Impl::getClientOptions() {
return clientOptions.clone();
}
ClientOptions HTTPFileSource::Impl::getClientOptions() { return clientOptions.clone(); }
NSString *HTTPFileSource::Impl::getUserAgent() const {
NSMutableArray *userAgentComponents = [NSMutableArray array];
NSMutableArray *userAgentComponents = [NSMutableArray array];
NSBundle *appBundle = [NSBundle mainBundle];
if (appBundle) {
NSString *appName = appBundle.infoDictionary[@"CFBundleName"];
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@",
appName.length ? appName : appBundle.infoDictionary[@"CFBundleIdentifier"],
appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
} else {
[userAgentComponents addObject:[NSProcessInfo processInfo].processName];
NSBundle *appBundle = [NSBundle mainBundle];
if (appBundle) {
NSString *appName = appBundle.infoDictionary[@"CFBundleName"];
[userAgentComponents
addObject:[NSString
stringWithFormat:@"%@/%@",
appName.length
? appName
: appBundle.infoDictionary[@"CFBundleIdentifier"],
appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
} else {
[userAgentComponents addObject:[NSProcessInfo processInfo].processName];
}
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle();
if (sdkBundle) {
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
if (versionString) {
[userAgentComponents
addObject:[NSString stringWithFormat:@"%@/%@", sdkBundle.infoDictionary[@"CFBundleName"],
versionString]];
}
}
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle();
if (sdkBundle) {
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
if (versionString) {
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@",
sdkBundle.infoDictionary[@"CFBundleName"], versionString]];
}
}
// Avoid %s here because it inserts hidden bidirectional markers on macOS when the system
// language is set to a right-to-left language.
[userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
@(mbgl::version::revision)]];
// Avoid %s here because it inserts hidden bidirectional markers on macOS when the system
// language is set to a right-to-left language.
[userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
@(mbgl::version::revision)]];
NSString *systemName = @"Darwin";
NSString *systemName = @"Darwin";
#if TARGET_OS_IPHONE
systemName = @"iOS";
systemName = @"iOS";
#elif TARGET_OS_MAC
systemName = @"macOS";
systemName = @"macOS";
#elif TARGET_OS_WATCH
systemName = @"watchOS";
systemName = @"watchOS";
#elif TARGET_OS_TV
systemName = @"tvOS";
systemName = @"tvOS";
#endif
#if TARGET_OS_SIMULATOR
systemName = [systemName stringByAppendingString:@" Simulator"];
systemName = [systemName stringByAppendingString:@" Simulator"];
#endif
NSString *systemVersion = nil;
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
systemVersion = [NSString stringWithFormat:@"%ld.%ld.%ld",
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion];
}
if (systemVersion) {
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
}
NSString *systemVersion = nil;
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
systemVersion =
[NSString stringWithFormat:@"%ld.%ld.%ld", (long)osVersion.majorVersion,
(long)osVersion.minorVersion, (long)osVersion.patchVersion];
}
if (systemVersion) {
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
}
NSString *cpu = nil;
NSString *cpu = nil;
#if TARGET_CPU_X86
cpu = @"x86";
cpu = @"x86";
#elif TARGET_CPU_X86_64
cpu = @"x86_64";
cpu = @"x86_64";
#elif TARGET_CPU_ARM
cpu = @"arm";
cpu = @"arm";
#elif TARGET_CPU_ARM64
cpu = @"arm64";
cpu = @"arm64";
#endif
if (cpu) {
[userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]];
}
if (cpu) {
[userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]];
}
return [userAgentComponents componentsJoinedByString:@" "];
return [userAgentComponents componentsJoinedByString:@" "];
}
NSBundle *HTTPFileSource::Impl::getSDKBundle() const {
NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]];
if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the class is contained in the application bundle rather than the
// framework bundle.
bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath
stringByAppendingPathComponent:@"Mapbox.framework"]];
}
return bundle;
NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]];
if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the class is contained in the application bundle rather than the
// framework bundle.
bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath
stringByAppendingPathComponent:@"Mapbox.framework"]];
}
return bundle;
}
HTTPFileSource::HTTPFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions)
: impl(std::make_unique<Impl>(resourceOptions, clientOptions)) {
}
HTTPFileSource::HTTPFileSource(const ResourceOptions &resourceOptions,
const ClientOptions &clientOptions)
: impl(std::make_unique<Impl>(resourceOptions, clientOptions)) {}
HTTPFileSource::~HTTPFileSource() = default;
MLN_APPLE_EXPORT
BOOL isValidMapboxEndpoint(NSURL *url) {
return ([url.host isEqualToString:@"mapbox.com"] ||
[url.host hasSuffix:@".mapbox.com"] ||
[url.host isEqualToString:@"mapbox.cn"] ||
[url.host hasSuffix:@".mapbox.cn"]);
return ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"] ||
[url.host isEqualToString:@"mapbox.cn"] || [url.host hasSuffix:@".mapbox.cn"]);
}
MLN_APPLE_EXPORT
NSURL *resourceURL(const Resource& resource) {
NSURL *url = [NSURL URLWithString:@(resource.url.c_str())];
NSURL *resourceURL(const Resource &resource) {
NSURL *url = [NSURL URLWithString:@(resource.url.c_str())];
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
if (isValidMapboxEndpoint(url)) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSMutableArray *queryItems = [NSMutableArray array];
if (isValidMapboxEndpoint(url)) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:NO];
NSMutableArray *queryItems = [NSMutableArray array];
if (resource.usage == Resource::Usage::Offline) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]];
}
if (components.queryItems) {
[queryItems addObjectsFromArray:components.queryItems];
}
components.queryItems = queryItems;
url = components.URL;
if (resource.usage == Resource::Usage::Offline) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]];
}
if (components.queryItems) {
[queryItems addObjectsFromArray:components.queryItems];
}
components.queryItems = queryItems;
url = components.URL;
}
#endif
return url;
return url;
}
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, std::function<void(Response)> callback) {
auto request = std::make_unique<HTTPRequest>(std::move(callback));
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below.
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource &resource,
std::function<void(Response)> callback) {
auto request = std::make_unique<HTTPRequest>(std::move(callback));
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion
// handler block below.
@autoreleasepool {
NSURL *url = resourceURL(resource);
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath];
@autoreleasepool {
NSURL *url = resourceURL(resource);
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
if (resource.priorEtag) {
[req addValue:@(resource.priorEtag->c_str())
forHTTPHeaderField:@"If-None-Match"];
} else if (resource.priorModified) {
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
forHTTPHeaderField:@"If-Modified-Since"];
}
if (resource.dataRange) {
NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld",
static_cast<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];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
if (resource.priorEtag) {
[req addValue:@(resource.priorEtag->c_str()) forHTTPHeaderField:@"If-None-Match"];
} else if (resource.priorModified) {
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
forHTTPHeaderField:@"If-Modified-Since"];
}
return std::move(request);
if (resource.dataRange) {
NSString *rangeHeader = [NSString
stringWithFormat:@"bytes=%lld-%lld", static_cast<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) {
impl->setResourceOptions(options.clone());
impl->setResourceOptions(options.clone());
}
ResourceOptions HTTPFileSource::getResourceOptions() {
return impl->getResourceOptions();
}
ResourceOptions HTTPFileSource::getResourceOptions() { return impl->getResourceOptions(); }
void HTTPFileSource::setClientOptions(ClientOptions options) {
impl->setClientOptions(options.clone());
impl->setClientOptions(options.clone());
}
ClientOptions HTTPFileSource::getClientOptions() {
return impl->getClientOptions();
}
ClientOptions HTTPFileSource::getClientOptions() { return impl->getClientOptions(); }
}
} // namespace mbgl

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
#import <Foundation/Foundation.h>
#include <mbgl/platform/thread.hpp>
#include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/platform/thread.hpp>
#include <pthread.h>
@@ -10,43 +10,41 @@ namespace mbgl {
namespace platform {
std::string getCurrentThreadName() {
char name[32] = "unknown";
pthread_getname_np(pthread_self(), name, sizeof(name));
char name[32] = "unknown";
pthread_getname_np(pthread_self(), name, sizeof(name));
return name;
return name;
}
void setCurrentThreadName(const std::string& name) {
std::string qualifiedName = "org.maplibre.mbgl." + name;
pthread_setname_np(qualifiedName.c_str());
std::string qualifiedName = "org.maplibre.mbgl." + name;
pthread_setname_np(qualifiedName.c_str());
}
void makeThreadLowPriority() {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
}
void setCurrentThreadPriority(double priority) {
if (priority > 1.0 || priority < 0.0) {
Log::Warning(Event::General, "Invalid thread priority was provided");
return;
}
if (priority > 1.0 || priority < 0.0) {
Log::Warning(Event::General, "Invalid thread priority was provided");
return;
}
if (priority < 0.25) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceBackground;
} else if (priority < 0.5) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
} else if (priority < 0.75) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated;
} else {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive;
}
if (priority < 0.25) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceBackground;
} else if (priority < 0.5) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
} else if (priority < 0.75) {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated;
} else {
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive;
}
}
void attachThread() {
}
void attachThread() {}
void detachThread() {
}
void detachThread() {}
}
}
} // namespace platform
} // namespace mbgl

View File

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

View File

@@ -6,26 +6,26 @@ namespace mbgl {
namespace platform {
std::string uppercase(const std::string &string) {
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSString *uppercase = [original uppercaseString];
std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding],
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result;
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSString *uppercase = [original uppercaseString];
std::string result{[uppercase cStringUsingEncoding:NSUTF8StringEncoding],
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result;
}
std::string lowercase(const std::string &string) {
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSString *lowercase = [original lowercaseString];
std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding],
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result;
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
length:string.size()
encoding:NSUTF8StringEncoding
freeWhenDone:NO];
NSString *lowercase = [original lowercaseString];
std::string result{[lowercase cStringUsingEncoding:NSUTF8StringEncoding],
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
return result;
}
} // namespace platform
} // namespace mbgl
} // namespace platform
} // namespace mbgl

View File

@@ -2,61 +2,62 @@
#import <mbgl/util/action_journal_options.hpp>
@implementation MLNActionJournalOptions {
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
}
- (instancetype)init {
if (self = [super init]) {
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
if (self = [super init]) {
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_actionJournalOptionsInternal->withPath([paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
}
return self;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_actionJournalOptionsInternal->withPath(
[paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
}
return self;
}
- (mbgl::util::ActionJournalOptions)getCoreOptions {
return *_actionJournalOptionsInternal;
return *_actionJournalOptionsInternal;
}
- (void)setEnabled:(BOOL)value {
_actionJournalOptionsInternal->enable(value);
_actionJournalOptionsInternal->enable(value);
}
- (BOOL)enabled {
return _actionJournalOptionsInternal->enabled();
return _actionJournalOptionsInternal->enabled();
}
- (void)setPath:(NSString*)value {
_actionJournalOptionsInternal->withPath(value.UTF8String);
- (void)setPath:(NSString *)value {
_actionJournalOptionsInternal->withPath(value.UTF8String);
}
- (NSString*)path {
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
- (NSString *)path {
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
}
- (void)setLogFileSize:(NSInteger)value {
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
}
- (NSInteger)logFileSize {
return _actionJournalOptionsInternal->logFileSize();
return _actionJournalOptionsInternal->logFileSize();
}
- (void)setLogFileCount:(NSInteger)value {
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
}
- (NSInteger)logFileCount {
return _actionJournalOptionsInternal->logFileCount();
return _actionJournalOptionsInternal->logFileCount();
}
- (void)setRenderingStatsReportInterval:(NSInteger)value {
_actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value));
_actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value));
}
- (NSInteger)renderingStatsReportInterval {
return _actionJournalOptionsInternal->renderingStatsReportInterval();
return _actionJournalOptionsInternal->renderingStatsReportInterval();
}
@end

View File

@@ -1,186 +1,199 @@
#import "MLNAttributionInfo_Private.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
#endif
#import "MLNSettings.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNMapCamera.h"
#import "MLNSettings.h"
#import "NSArray+MLNAdditions.h"
#import "NSBundle+MLNAdditions.h"
#import "NSString+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#include <string>
@implementation MLNAttributionInfo
+ (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor {
if (!htmlString) {
return @[];
}
+ (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString
fontSize:(CGFloat)fontSize
linkColor:(nullable MLNColor *)linkColor {
if (!htmlString) {
return @[];
}
NSDictionary *options = @{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding),
};
// Apply a bogus, easily detectable style rule to any feedback link, since
// NSAttributedString doesn’t preserve the class attribute.
NSMutableString *css = [NSMutableString stringWithString:
@"html { font-family: -apple-system, -apple-system-font, sans-serif; }"
@".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
if (fontSize) {
NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize];
NSDictionary *options = @{
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
};
// Apply a bogus, easily detectable style rule to any feedback link, since
// NSAttributedString doesn’t preserve the class attribute.
NSMutableString *css = [NSMutableString
stringWithString:@"html { font-family: -apple-system, -apple-system-font, sans-serif; }"
@".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
if (fontSize) {
NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize];
#if !TARGET_OS_IPHONE
if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) {
sizeRule = @"font: -webkit-mini-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) {
sizeRule = @"font: -webkit-small-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) {
sizeRule = @"font: -webkit-control";
}
#endif
[css appendFormat:@"html { %@ }", sizeRule];
if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) {
sizeRule = @"font: -webkit-mini-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) {
sizeRule = @"font: -webkit-small-control";
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) {
sizeRule = @"font: -webkit-control";
}
if (linkColor) {
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
#endif
[css appendFormat:@"html { %@ }", sizeRule];
}
if (linkColor) {
CGFloat red;
CGFloat green;
CGFloat blue;
CGFloat alpha;
#if !TARGET_OS_IPHONE
// CSS uses the sRGB color space.
if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) {
linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
} else {
linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
}
#endif
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
[css appendFormat:
@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }",
red * 100, green * 100, blue * 100, alpha];
// CSS uses the sRGB color space.
if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) {
linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
} else {
linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
}
NSString *styledHTML = [NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
#endif
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
[css appendFormat:@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", red * 100, green * 100,
blue * 100, alpha];
}
NSString *styledHTML =
[NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
#if TARGET_OS_IPHONE
__block NSMutableAttributedString *attributedString;
dispatch_block_t initialization = ^{
// This initializer should be called from a global or main queue. https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata
attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
options:options
documentAttributes:nil
error:NULL];
};
__block NSMutableAttributedString *attributedString;
dispatch_block_t initialization = ^{
// This initializer should be called from a global or main queue.
// https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata
attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
options:options
documentAttributes:nil
error:NULL];
};
if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), initialization);
} else {
initialization();
}
if (![[NSThread currentThread] isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), initialization);
} else {
initialization();
}
#else
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData
options:options
documentAttributes:nil];
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithHTML:htmlData
options:options
documentAttributes:nil];
#endif
NSMutableArray *infos = [NSMutableArray array];
[attributedString enumerateAttribute:NSLinkAttributeName
inRange:attributedString.mgl_wholeRange
options:0
usingBlock:
^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
MLNCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL.");
NSMutableArray *infos = [NSMutableArray array];
[attributedString
enumerateAttribute:NSLinkAttributeName
inRange:attributedString.mgl_wholeRange
options:0
usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
MLNCAssert(!value || [value isKindOfClass:[NSURL class]],
@"If present, URL attribute must be an NSURL.");
// Detect feedback links by the bogus style rule applied above.
NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
atIndex:range.location
effectiveRange:NULL];
BOOL isFeedbackLink = NO;
if ([strokeWidth floatValue] > 100) {
isFeedbackLink = YES;
[attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
}
// Detect feedback links by the bogus style rule applied above.
NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
atIndex:range.location
effectiveRange:NULL];
BOOL isFeedbackLink = NO;
if ([strokeWidth floatValue] > 100) {
isFeedbackLink = YES;
[attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
}
// Clean up strings by stripping punctuation and whitespace (often present for the web).
NSMutableCharacterSet *charset = [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
mgl_attributedStringByTrimmingCharactersInSet:charset];
// Clean up strings by stripping punctuation and whitespace (often present for the
// web).
NSMutableCharacterSet *charset =
[NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
mgl_attributedStringByTrimmingCharactersInSet:charset];
// Omit strings that are empty after cleaning.
if (!title.length) {
return;
}
// Omit strings that are empty after cleaning.
if (!title.length) {
return;
}
// Remove the link, because it forces the text to be blue on macOS 10.12
// and above.
NSMutableAttributedString *unlinkedTitle = [title mutableCopy];
[unlinkedTitle removeAttribute:NSLinkAttributeName range:unlinkedTitle.mgl_wholeRange];
// Remove the link, because it forces the text to be blue on macOS 10.12
// and above.
NSMutableAttributedString *unlinkedTitle = [title mutableCopy];
[unlinkedTitle removeAttribute:NSLinkAttributeName
range:unlinkedTitle.mgl_wholeRange];
MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle URL:value];
info.feedbackLink = isFeedbackLink;
[infos addObject:info];
}];
return infos;
MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle
URL:value];
info.feedbackLink = isFeedbackLink;
[infos addObject:info];
}];
return infos;
}
+ (NSAttributedString *)attributedStringForAttributionInfos:(NSArray<MLNAttributionInfo *> *)attributionInfos {
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
for (MLNAttributionInfo *info in attributionInfos) {
NSMutableAttributedString *title = info.title.mutableCopy;
if (info.URL) {
[title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange];
}
[titles addObject:title];
+ (NSAttributedString *)attributedStringForAttributionInfos:
(NSArray<MLNAttributionInfo *> *)attributionInfos {
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
for (MLNAttributionInfo *info in attributionInfos) {
NSMutableAttributedString *title = info.title.mutableCopy;
if (info.URL) {
[title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange];
}
return [titles mgl_attributedComponentsJoinedByString:@" "];
[titles addObject:title];
}
return [titles mgl_attributedComponentsJoinedByString:@" "];
}
- (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL {
if (self = [super init]) {
_title = title;
_URL = URL;
}
return self;
if (self = [super init]) {
_title = title;
_URL = URL;
}
return self;
}
- (id)copyWithZone:(nullable NSZone *)zone
{
MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title
URL:_URL];
info.feedbackLink = _feedbackLink;
- (id)copyWithZone:(nullable NSZone *)zone {
MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title URL:_URL];
info.feedbackLink = _feedbackLink;
return info;
return info;
}
- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style
{
NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", @"OpenStreetMap full name attribution");
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", @"OpenStreetMap short name attribution");
- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style {
NSString *openStreetMap =
NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap",
@"OpenStreetMap full name attribution");
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM",
@"OpenStreetMap short name attribution");
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title];
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
NSMutableAttributedString *title =
[[NSMutableAttributedString alloc] initWithAttributedString:self.title];
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [title.mutableString length])];
}
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap"
withString:isAbbreviated ? OSM : openStreetMap
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [title.mutableString length])];
}
return title;
return title;
}
- (BOOL)isEqual:(id)object {
return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && [[object URL] isEqual:self.URL];
return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] &&
[[object URL] isEqual:self.URL];
}
- (NSUInteger)hash {
return self.title.hash + self.URL.hash;
return self.title.hash + self.URL.hash;
}
/**
@@ -192,15 +205,15 @@
if there is no overlap.
*/
- (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo {
NSString *title = self.title.string;
NSString *otherTitle = otherInfo.title.string;
if ([title containsString:otherTitle]) {
return NSOrderedDescending;
}
if ([otherTitle containsString:title]) {
return NSOrderedAscending;
}
return NSOrderedSame;
NSString *title = self.title.string;
NSString *otherTitle = otherInfo.title.string;
if ([title containsString:otherTitle]) {
return NSOrderedDescending;
}
if ([otherTitle containsString:title]) {
return NSOrderedAscending;
}
return NSOrderedSame;
}
@end
@@ -208,43 +221,44 @@
@implementation NSMutableArray (MLNAttributionInfoAdditions)
- (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info {
__block BOOL didInsertInfo = NO;
__block BOOL shouldAddInfo = YES;
[self enumerateObjectsUsingBlock:^(MLNAttributionInfo * _Nonnull existingInfo, NSUInteger idx, BOOL * _Nonnull stop) {
switch ([info subsetCompare:existingInfo]) {
case NSOrderedDescending:
// The existing info object is a subset of the one we’re adding.
// Replace the existing object the first time we find a subset;
// remove the existing object every time after that.
if (didInsertInfo) {
[self removeObjectAtIndex:idx];
} else {
[self replaceObjectAtIndex:idx withObject:info];
didInsertInfo = YES;
}
break;
case NSOrderedAscending:
// The info object we’re adding is a subset of the existing one.
// Don’t add the object and stop looking.
shouldAddInfo = NO;
*stop = YES;
break;
default:
break;
__block BOOL didInsertInfo = NO;
__block BOOL shouldAddInfo = YES;
[self enumerateObjectsUsingBlock:^(MLNAttributionInfo *_Nonnull existingInfo, NSUInteger idx,
BOOL *_Nonnull stop) {
switch ([info subsetCompare:existingInfo]) {
case NSOrderedDescending:
// The existing info object is a subset of the one we’re adding.
// Replace the existing object the first time we find a subset;
// remove the existing object every time after that.
if (didInsertInfo) {
[self removeObjectAtIndex:idx];
} else {
[self replaceObjectAtIndex:idx withObject:info];
didInsertInfo = YES;
}
}];
if (shouldAddInfo && !didInsertInfo) {
// No overlapping infos were found, so append the info object.
[self addObject:info];
break;
case NSOrderedAscending:
// The info object we’re adding is a subset of the existing one.
// Don’t add the object and stop looking.
shouldAddInfo = NO;
*stop = YES;
break;
default:
break;
}
}];
if (shouldAddInfo && !didInsertInfo) {
// No overlapping infos were found, so append the info object.
[self addObject:info];
}
}
- (void)growArrayByAddingAttributionInfosFromArray:(NSArray<MLNAttributionInfo *> *)infos {
for (MLNAttributionInfo *info in infos) {
[self growArrayByAddingAttributionInfo:info];
}
for (MLNAttributionInfo *info in infos) {
[self growArrayByAddingAttributionInfo:info];
}
}
@end

View File

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

View File

@@ -1,10 +1,10 @@
#import "MLNComputedShapeSource_Private.h"
#import "MLNMapView_Private.h"
#import "MLNSource_Private.h"
#import "MLNShape_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNMapView_Private.h"
#import "MLNShapeCollection.h"
#import "MLNShape_Private.h"
#import "MLNSource_Private.h"
#include <mbgl/map/map.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp>
@@ -13,65 +13,68 @@
const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException";
const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates = @"MLNShapeSourceOptionWrapsCoordinates";
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates = @"MLNShapeSourceOptionClipsCoordinates";
const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates =
@"MLNShapeSourceOptionWrapsCoordinates";
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates =
@"MLNShapeSourceOptionClipsCoordinates";
mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) {
mbgl::style::CustomGeometrySource::Options sourceOptions;
mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(
NSDictionary<MLNShapeSourceOption, id> *options) {
mbgl::style::CustomGeometrySource::Options sourceOptions;
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.min = value.integerValue;
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.min = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.max = value.integerValue;
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
sourceOptions.zoomRange.max = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
sourceOptions.tileOptions.buffer = value.integerValue;
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
sourceOptions.tileOptions.buffer = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
sourceOptions.tileOptions.tolerance = value.doubleValue;
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
sourceOptions.tileOptions.tolerance = value.doubleValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.wrap = value.boolValue;
if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.wrap = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.clip = value.boolValue;
if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."];
}
sourceOptions.tileOptions.clip = value.boolValue;
}
return sourceOptions;
return sourceOptions;
}
@interface MLNComputedShapeSource () {
std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource;
std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource;
}
@property (nonatomic, readwrite) NSDictionary *options;
@@ -90,164 +93,181 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
@property (nonatomic, weak, nullable) id<MLNComputedShapeSourceDataSource> dataSource;
@property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource;
- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileId;
- (instancetype)initForSource:(MLNComputedShapeSource *)source
tile:(const mbgl::CanonicalTileID &)tileId;
@end
@implementation MLNComputedShapeSourceFetchOperation
- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID {
self = [super init];
_z = tileID.z;
_x = tileID.x;
_y = tileID.y;
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
_dataSource = source.dataSource;
mbgl::style::CustomGeometrySource *rawSource = static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
_rawSource = rawSource;
return self;
- (instancetype)initForSource:(MLNComputedShapeSource *)source
tile:(const mbgl::CanonicalTileID &)tileID {
self = [super init];
_z = tileID.z;
_x = tileID.x;
_y = tileID.y;
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
_dataSource = source.dataSource;
mbgl::style::CustomGeometrySource *rawSource =
static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
_rawSource = rawSource;
return self;
}
- (void)main {
if ([self isCancelled]) {
return;
}
if ([self isCancelled]) {
return;
}
NSArray<MLNShape <MLNFeature> *> *data;
if(!self.dataSource) {
data = nil;
} else if(self.dataSourceImplementsFeaturesForTile) {
data = [self.dataSource featuresInTileAtX:self.x
y:self.y
zoomLevel:self.z];
} else {
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
data = [self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds)
zoomLevel:self.z];
}
NSArray<MLNShape<MLNFeature> *> *data;
if (!self.dataSource) {
data = nil;
} else if (self.dataSourceImplementsFeaturesForTile) {
data = [self.dataSource featuresInTileAtX:self.x y:self.y zoomLevel:self.z];
} else {
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
data =
[self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds)
zoomLevel:self.z];
}
if(![self isCancelled]) {
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(data.count);
for (MLNShape <MLNFeature> * feature in data) {
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
}
const auto geojson = mbgl::GeoJSON{featureCollection};
// Note: potential race condition with `cancel`
if(![self isCancelled]) {
if (auto *rawSource = self.rawSource) {
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
}
}
if (![self isCancelled]) {
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(data.count);
for (MLNShape<MLNFeature> *feature in data) {
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
}
const auto geojson = mbgl::GeoJSON{featureCollection};
// Note: potential race condition with `cancel`
if (![self isCancelled]) {
if (auto *rawSource = self.rawSource) {
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
}
}
}
}
- (void)cancel {
[super cancel];
self.rawSource = NULL;
[super cancel];
self.rawSource = NULL;
}
@end
@implementation MLNComputedShapeSource
- (instancetype)initWithIdentifier:(NSString *)identifier options:(NSDictionary<MLNShapeSourceOption, id> *)options {
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
requestQueue.qualityOfService = NSQualityOfServiceUtility;
requestQueue.maxConcurrentOperationCount = 4;
- (instancetype)initWithIdentifier:(NSString *)identifier
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
requestQueue.qualityOfService = NSQualityOfServiceUtility;
requestQueue.maxConcurrentOperationCount = 4;
auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options);
sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) {
NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID];
[requestQueue addOperation:operation];
};
auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options);
sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self
tile:tileID];
[requestQueue addOperation:operation];
};
sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) {
for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) {
if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) {
[operation cancel];
}
}
};
auto source = std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
if (self = [super initWithPendingSource:std::move(source)]) {
_requestQueue = requestQueue;
sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) {
if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) {
[operation cancel];
}
}
return self;
};
auto source =
std::make_unique<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 {
if (self = [self initWithIdentifier:identifier options:options]) {
[self setDataSource:dataSource];
}
return self;
- (instancetype)initWithIdentifier:(NSString *)identifier
dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
if (self = [self initWithIdentifier:identifier options:options]) {
[self setDataSource:dataSource];
}
return self;
}
- (void)dealloc {
[self.requestQueue cancelAllOperations];
[self.requestQueue cancelAllOperations];
}
- (void)setFeatures:(NSArray<MLNShape <MLNFeature> *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel {
MLNAssertStyleSourceIsValid();
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(features.count);
for (MLNShape <MLNFeature> * feature in features) {
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
- (void)setFeatures:(NSArray<MLNShape<MLNFeature> *> *)features
inTileAtX:(NSUInteger)x
y:(NSUInteger)y
zoomLevel:(NSUInteger)zoomLevel {
MLNAssertStyleSourceIsValid();
mbgl::CanonicalTileID tileID =
mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(features.count);
for (MLNShape<MLNFeature> *feature in features) {
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
featureCollection.push_back(geoJsonObject);
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
const auto geojson = mbgl::GeoJSON{featureCollection};
static_cast<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 {
[self.requestQueue cancelAllOperations];
// Check which method the datasource implements, to avoid having to check for each tile
self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)];
self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)];
[self.requestQueue cancelAllOperations];
// Check which method the datasource implements, to avoid having to check for each tile
self.dataSourceImplementsFeaturesForTile =
[dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)];
self.dataSourceImplementsFeaturesForBounds =
[dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)];
if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) {
[NSException raise:MLNInvalidDatasourceException
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
} else if (self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) {
[NSException raise:MLNInvalidDatasourceException
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
}
if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) {
[NSException
raise:MLNInvalidDatasourceException
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
} else if (self.dataSourceImplementsFeaturesForBounds &&
self.dataSourceImplementsFeaturesForTile) {
[NSException raise:MLNInvalidDatasourceException
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
}
_dataSource = dataSource;
_dataSource = dataSource;
}
- (void) invalidateBounds:(MLNCoordinateBounds)bounds {
MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds));
- (void)invalidateBounds:(MLNCoordinateBounds)bounds {
MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)
->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds));
}
- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z {
MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y));
- (void)invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z {
MLNAssertStyleSourceIsValid();
((mbgl::style::CustomGeometrySource *)self.rawSource)
->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y));
}
@end

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
#import "MLNCustomStyleLayer.h"
#import "MLNCustomStyleLayer_Private.h"
#import "MLNMapView_Private.h"
#import "MLNStyle_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNMapView_Private.h"
#import "MLNStyleLayer_Private.h"
#import "MLNStyle_Private.h"
#if MLN_RENDER_BACKEND_METAL
#import <MetalKit/MetalKit.h>
#endif
#include <mbgl/style/layers/custom_layer.hpp>
#include <mbgl/math/wrap.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
#if MLN_RENDER_BACKEND_METAL
#include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp>
@@ -28,44 +28,42 @@ class MLNCustomLayerHost;
@implementation MLNCustomStyleLayer
- (instancetype)initWithIdentifier:(NSString *)identifier {
auto layer = std::make_unique<mbgl::style::CustomLayer>(
identifier.UTF8String,
std::make_unique<MLNCustomLayerHost>(self)
);
return self = [super initWithPendingLayer:std::move(layer)];
auto layer = std::make_unique<mbgl::style::CustomLayer>(
identifier.UTF8String, std::make_unique<MLNCustomLayerHost>(self));
return self = [super initWithPendingLayer:std::move(layer)];
}
- (mbgl::style::CustomLayer *)rawLayer {
return (mbgl::style::CustomLayer *)super.rawLayer;
return (mbgl::style::CustomLayer *)super.rawLayer;
}
- (MLNMapView *)mapView {
if ([self.style.stylable isKindOfClass:[MLNMapView class]]) {
return (MLNMapView *)self.style.stylable;
}
return nil;
if ([self.style.stylable isKindOfClass:[MLNMapView class]]) {
return (MLNMapView *)self.style.stylable;
}
return nil;
}
#if TARGET_OS_IPHONE
- (EAGLContext *)context {
return self.mapView.context;
return self.mapView.context;
}
#else
- (CGLContextObj)context {
return self.mapView.context;
return self.mapView.context;
}
#endif
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
self.style = style;
self.style.customLayers[self.identifier] = self;
[super addToStyle:style belowLayer:otherLayer];
self.style = style;
self.style.customLayers[self.identifier] = self;
[super addToStyle:style belowLayer:otherLayer];
}
- (void)removeFromStyle:(MLNStyle *)style {
[super removeFromStyle:style];
self.style.customLayers[self.identifier] = nil;
self.style = nil;
[super removeFromStyle:style];
self.style.customLayers[self.identifier] = nil;
self.style = nil;
}
- (void)didMoveToMapView:(MLNMapView *)mapView {
@@ -78,74 +76,75 @@ class MLNCustomLayerHost;
}
- (void)setNeedsDisplay {
[self.mapView setNeedsRerender];
[self.mapView setNeedsRerender];
}
@end
class MLNCustomLayerHost : public mbgl::style::CustomLayerHost {
public:
MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) {
layerRef = styleLayer;
layer = nil;
}
void initialize() {
if (layerRef == nil)
return;
else if (layer == nil)
layer = layerRef;
if (layer.mapView) {
[layer didMoveToMapView:layer.mapView];
}
}
void initialize() {
if (layerRef == nil) return;
else if (layer == nil) layer = layerRef;
if (layer.mapView) {
[layer didMoveToMapView:layer.mapView];
}
}
void render(const mbgl::style::CustomLayerRenderParameters& parameters) {
if (!layer) return;
void render(const mbgl::style::CustomLayerRenderParameters &parameters) {
if (!layer) return;
#if MLN_RENDER_BACKEND_METAL
MTL::RenderCommandEncoder* ptr =
static_cast<const mbgl::style::mtl::CustomLayerRenderParameters&>(parameters).encoder.get();
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
layer.renderEncoder = encoder;
MTL::RenderCommandEncoder *ptr =
static_cast<const mbgl::style::mtl::CustomLayerRenderParameters &>(parameters)
.encoder.get();
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
layer.renderEncoder = encoder;
#endif
MLNStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(parameters.width, parameters.height),
.centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude),
.zoomLevel = parameters.zoom,
.direction = mbgl::util::wrap(parameters.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(parameters.pitch),
.fieldOfView = static_cast<CGFloat>(parameters.fieldOfView),
.projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)
};
MLNStyleLayerDrawingContext drawingContext = {
.size = CGSizeMake(parameters.width, parameters.height),
.centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude),
.zoomLevel = parameters.zoom,
.direction = mbgl::util::wrap(parameters.bearing, 0., 360.),
.pitch = static_cast<CGFloat>(parameters.pitch),
.fieldOfView = static_cast<CGFloat>(parameters.fieldOfView),
.projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)};
if (layer.mapView) {
[layer drawInMapView:layer.mapView withContext:drawingContext];
}
if (layer.mapView) {
[layer drawInMapView:layer.mapView withContext:drawingContext];
}
}
void contextLost() {
}
void deinitialize() {
if (layer == nil) return;
if (layer.mapView) {
[layer willMoveFromMapView:layer.mapView];
}
layerRef = layer;
layer = nil;
void contextLost() {}
void deinitialize() {
if (layer == nil) return;
if (layer.mapView) {
[layer willMoveFromMapView:layer.mapView];
}
layerRef = layer;
layer = nil;
}
private:
__weak MLNCustomStyleLayer * layerRef;
MLNCustomStyleLayer * layer = nil;
__weak MLNCustomStyleLayer *layerRef;
MLNCustomStyleLayer *layer = nil;
};
namespace mbgl {
MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer];
MLNStyleLayer *CustomStyleLayerPeerFactory::createPeer(style::Layer *rawLayer) {
return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer];
}
} // namespace mbgl

View File

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

View File

@@ -1,29 +1,29 @@
#import "MLNFoundation_Private.h"
#import "MLNFeature_Private.h"
#import "MLNCluster.h"
#import "MLNFeature_Private.h"
#import "MLNFoundation_Private.h"
#import "MLNPointAnnotation.h"
#import "MLNPolyline.h"
#import "MLNPolygon.h"
#import "MLNPolyline.h"
#import "MLNValueEvaluator.h"
#import "MLNShape_Private.h"
#import "MLNPointCollection_Private.h"
#import "MLNPolyline_Private.h"
#import "MLNPolygon_Private.h"
#import "MLNPolyline_Private.h"
#import "MLNShape_Private.h"
#import "NSDictionary+MLNAdditions.h"
#import "NSArray+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#import "NSArray+MLNAdditions.h"
#import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import <mbgl/util/geometry.hpp>
#import <mbgl/style/conversion/geojson.hpp>
#import <mapbox/feature.hpp>
#import <mbgl/style/conversion/geojson.hpp>
#import <mbgl/util/geometry.hpp>
// Cluster constants
static NSString * const MLNClusterIdentifierKey = @"cluster_id";
static NSString * const MLNClusterCountKey = @"point_count";
static NSString *const MLNClusterIdentifierKey = @"cluster_id";
static NSString *const MLNClusterCountKey = @"point_count";
const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax;
@interface MLNEmptyFeature ()
@@ -40,28 +40,29 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry(@{@"type": @"Empty"}, self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry(@{@"type" : @"Empty"}, self.attributes, self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (mbgl::Geometry<double>)geometryObject {
return mbgl::EmptyGeometry();
return mbgl::EmptyGeometry();
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier
? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -80,25 +81,27 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
self.coordinate.latitude, self.coordinate.longitude,
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
self.coordinate.latitude, self.coordinate.longitude,
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -106,28 +109,28 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
@implementation MLNPointFeatureCluster
- (NSUInteger)clusterIdentifier {
NSNumber *clusterNumber = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
NSNumber *clusterNumber =
MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
if (!clusterNumber) {
return MLNClusterIdentifierInvalid;
}
if (!clusterNumber) {
return MLNClusterIdentifierInvalid;
}
NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue];
MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit");
NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue];
MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit");
return clusterIdentifier;
return clusterIdentifier;
}
- (NSUInteger)clusterPointCount {
NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber);
MLNAssert(count, @"Clusters should have a point_count");
NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber);
MLNAssert(count, @"Clusters should have a point_count");
return [count unsignedIntegerValue];
return [count unsignedIntegerValue];
}
@end
@interface MLNPolylineFeature ()
@end
@@ -142,26 +145,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -180,26 +185,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)[self pointCount],
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -218,16 +225,17 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
@end
@@ -246,26 +254,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
(unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -284,26 +294,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
(unsigned long)self.polygons.count,
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
: self.identifier,
(unsigned long)self.polygons.count,
MLNStringFromCoordinateBounds(self.overlayBounds),
self.attributes.count ? self.attributes : @"none"];
}
@end
@@ -319,7 +331,7 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
@dynamic shapes;
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape<MLNFeature> *> *)shapes {
return [super shapeCollectionWithShapes:shapes];
return [super shapeCollectionWithShapes:shapes];
}
MLN_DEFINE_FEATURE_INIT_WITH_CODER();
@@ -328,22 +340,24 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
- (id)attributeForKey:(NSString *)key {
return self.attributes[key];
return self.attributes[key];
}
- (NSDictionary *)geoJSONDictionary {
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
self.identifier);
}
- (mbgl::GeoJSON)geoJSONObject {
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(self.shapes.count);
for (MLNShape <MLNFeature> *feature in self.shapes) {
auto geoJSONObject = feature.geoJSONObject;
MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(), @"Feature collection must only contain features.");
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
}
return featureCollection;
mbgl::FeatureCollection featureCollection;
featureCollection.reserve(self.shapes.count);
for (MLNShape<MLNFeature> *feature in self.shapes) {
auto geoJSONObject = feature.geoJSONObject;
MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(),
@"Feature collection must only contain features.");
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
}
return featureCollection;
}
@end
@@ -355,199 +369,211 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
template <typename T>
class GeometryEvaluator {
private:
const mbgl::PropertyMap *shared_properties;
const bool is_in_feature;
const mbgl::PropertyMap *shared_properties;
const bool is_in_feature;
public:
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false):
shared_properties(properties),
is_in_feature(isInFeature)
{}
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false)
: shared_properties(properties), is_in_feature(isInFeature) {}
MLNShape * operator()(const mbgl::EmptyGeometry &) const {
return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init];
}
MLNShape *operator()(const mbgl::EmptyGeometry &) const {
return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init];
}
MLNShape * operator()(const mbgl::Point<T> &geometry) const {
Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class];
MLNShape *operator()(const mbgl::Point<T> &geometry) const {
Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class];
// If we're dealing with a cluster, we should change the class type.
// This could be generic and build the subclass at runtime if it turns
// out we need to support more than point clusters.
if (shared_properties) {
auto clusterIt = shared_properties->find("cluster");
if (clusterIt != shared_properties->end()) {
auto clusterValue = clusterIt->second;
if (clusterValue.template is<bool>()) {
if (clusterValue.template get<bool>()) {
shapeClass = [MLNPointFeatureCluster class];
}
}
}
// If we're dealing with a cluster, we should change the class type.
// This could be generic and build the subclass at runtime if it turns
// out we need to support more than point clusters.
if (shared_properties) {
auto clusterIt = shared_properties->find("cluster");
if (clusterIt != shared_properties->end()) {
auto clusterValue = clusterIt->second;
if (clusterValue.template is<bool>()) {
if (clusterValue.template get<bool>()) {
shapeClass = [MLNPointFeatureCluster class];
}
}
MLNPointAnnotation *shape = [[shapeClass alloc] init];
shape.coordinate = toLocationCoordinate2D(geometry);
return shape;
}
}
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()];
MLNPointAnnotation *shape = [[shapeClass alloc] init];
shape.coordinate = toLocationCoordinate2D(geometry);
return shape;
}
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 {
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature);
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)];
}
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];
}
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 ? [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];
}
private:
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
return CLLocationCoordinate2DMake(point.y, point.x);
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
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) {
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];
}
}
Class shapeClass = isInFeature ? [V class] : [U class];
return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons];
}
Class shapeClass = isInFeature ? [V class] : [U class];
return [shapeClass polygonWithCoordinates:&coordinates[0]
count:coordinates.size()
interiorPolygons:innerPolygons];
}
};
template <typename T>
class GeoJSONEvaluator {
public:
MLNShape * operator()(const mbgl::Geometry<T> &geometry) const {
GeometryEvaluator<T> evaluator;
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
return shape;
}
MLNShape *operator()(const mbgl::Geometry<T> &geometry) const {
GeometryEvaluator<T> evaluator;
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
return shape;
}
MLNShape <MLNFeature> * operator()(const mbgl::GeoJSONFeature &feature) const {
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)MLNFeatureFromMBGLFeature(feature);
return shape;
}
MLNShape<MLNFeature> *operator()(const mbgl::GeoJSONFeature &feature) const {
MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)MLNFeatureFromMBGLFeature(feature);
return shape;
}
MLNShape <MLNFeature> * operator()(const mbgl::FeatureCollection &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
for (const auto &feature : collection) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
}
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
MLNShape<MLNFeature> *operator()(const mbgl::FeatureCollection &collection) const {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
for (const auto &feature : collection) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
}
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
}
};
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
}
return shapes;
NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
const std::vector<mbgl::Feature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
}
return shapes;
}
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
}
return shapes;
NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
const std::vector<mbgl::GeoJSONFeature> &features) {
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
for (const auto &feature : features) {
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
}
return shapes;
}
id <MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
for (auto &pair : feature.properties) {
auto &value = pair.second;
ValueEvaluator evaluator;
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
}
GeometryEvaluator<double> evaluator(&feature.properties, true);
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator);
if (!feature.id.is<mapbox::feature::null_value_t>()) {
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
}
shape.attributes = attributes;
id<MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
NSMutableDictionary *attributes =
[NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
for (auto &pair : feature.properties) {
auto &value = pair.second;
ValueEvaluator evaluator;
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
}
GeometryEvaluator<double> evaluator(&feature.properties, true);
MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)mapbox::geometry::geometry<double>::visit(
feature.geometry, evaluator);
if (!feature.id.is<mapbox::feature::null_value_t>()) {
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
}
shape.attributes = attributes;
return shape;
return shape;
}
MLNShape* MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
GeoJSONEvaluator<double> evaluator;
MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator);
return shape;
MLNShape *MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
GeoJSONEvaluator<double> evaluator;
MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator);
return shape;
}
mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary *attributes)
{
if (identifier) {
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
feature.id = [identifierExpression mgl_featureIdentifier];
}
feature.properties = [attributes mgl_propertyMap];
return feature;
mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier,
NSDictionary *attributes) {
if (identifier) {
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
feature.id = [identifierExpression mgl_featureIdentifier];
}
feature.properties = [attributes mgl_propertyMap];
return feature;
}
NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier) {
NSMutableDictionary *feature = [@{@"type": @"Feature",
@"properties": attributes,
@"geometry": geometry} mutableCopy];
feature[@"id"] = identifier;
return [feature copy];
NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry,
NSDictionary *attributes,
id identifier) {
NSMutableDictionary *feature =
[@{@"type" : @"Feature", @"properties" : attributes, @"geometry" : geometry} mutableCopy];
feature[@"id"] = identifier;
return [feature copy];
}

View File

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

View File

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

View File

@@ -19,113 +19,141 @@ const CLLocationDegrees MLNAngularFieldOfView = 30;
const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0};
CGRect MLNExtendRect(CGRect rect, CGPoint point) {
if (point.x < rect.origin.x) {
rect.size.width += rect.origin.x - point.x;
rect.origin.x = point.x;
}
if (point.x > rect.origin.x + rect.size.width) {
rect.size.width += point.x - (rect.origin.x + rect.size.width);
}
if (point.y < rect.origin.y) {
rect.size.height += rect.origin.y - point.y;
rect.origin.y = point.y;
}
if (point.y > rect.origin.y + rect.size.height) {
rect.size.height += point.y - (rect.origin.y + rect.size.height);
}
return rect;
if (point.x < rect.origin.x) {
rect.size.width += rect.origin.x - point.x;
rect.origin.x = point.x;
}
if (point.x > rect.origin.x + rect.size.width) {
rect.size.width += point.x - (rect.origin.x + rect.size.width);
}
if (point.y < rect.origin.y) {
rect.size.height += rect.origin.y - point.y;
rect.origin.y = point.y;
}
if (point.y > rect.origin.y + rect.size.height) {
rect.size.height += point.y - (rect.origin.y + rect.size.height);
}
return rect;
}
mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
try {
return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
} catch (std::domain_error &error) {
[NSException raise:NSInvalidArgumentException format:@"%s", error.what()];
return {};
}
try {
return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
} catch (std::domain_error &error) {
[NSException raise:NSInvalidArgumentException format:@"%s", error.what()];
return {};
}
}
CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
CLLocationDistance metersTall = metersPerPixel * size.height;
CLLocationDistance altitude = metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch,
CLLocationDegrees latitude, CGSize size) {
CLLocationDistance metersPerPixel =
mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
CLLocationDistance metersTall = metersPerPixel * size.height;
CLLocationDistance altitude =
metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
}
double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
CLLocationDistance metersPerPixel = metersTall / size.height;
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel;
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch,
CLLocationDegrees latitude, CGSize size) {
CLLocationDistance eyeAltitude =
altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
CLLocationDistance metersTall =
eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
CLLocationDistance metersPerPixel = metersTall / size.height;
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI *
mbgl::util::EARTH_RADIUS_M / metersPerPixel;
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
}
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) {
double a = pow(sin((to.latitude - from.latitude) / 2), 2)
+ pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude);
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from,
MLNRadianCoordinate2D to) {
double a =
pow(sin((to.latitude - from.latitude) / 2), 2) +
pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude);
return 2 * atan2(sqrt(a), sqrt(1 - a));
return 2 * atan2(sqrt(a), sqrt(1 - a));
}
MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) {
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
double b = cos(from.latitude) * sin(to.latitude)
- sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
return atan2(a, b);
MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from,
MLNRadianCoordinate2D to) {
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
double b = cos(from.latitude) * sin(to.latitude) -
sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
return atan2(a, b);
}
MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate,
MLNRadianDistance distance,
MLNRadianDirection direction) {
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance)
+ cos(coordinate.latitude) * sin(distance) * cos(direction));
double otherLongitude = coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude);
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) +
cos(coordinate.latitude) * sin(distance) * cos(direction));
double otherLongitude =
coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude);
}
CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, CLLocationCoordinate2D secondCoordinate) {
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) && MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
return 0;
}
CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate,
CLLocationCoordinate2D secondCoordinate) {
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) &&
MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
return 0;
}
// Ported from
// https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
MLNRadianCoordinate2D firstRadianCoordinate =
MLNRadianCoordinateFromLocationCoordinate(firstCoordinate);
MLNRadianCoordinate2D secondRadianCoordinate =
MLNRadianCoordinateFromLocationCoordinate(secondCoordinate);
// Ported from https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
MLNRadianCoordinate2D firstRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(firstCoordinate);
MLNRadianCoordinate2D secondRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(secondCoordinate);
CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) * cos(secondRadianCoordinate.latitude);
CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude)
- sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
MLNRadianDirection radianDirection = atan2(a, b);
return radianDirection * 180 / M_PI;
CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) *
cos(secondRadianCoordinate.latitude);
CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude) -
sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) *
cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
MLNRadianDirection radianDirection = atan2(a, b);
return radianDirection * 180 / M_PI;
}
CGPoint MLNPointRounded(CGPoint point) {
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
#elif TARGET_OS_MAC
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
#endif
return CGPointMake(round(point.x * scaleFactor) / scaleFactor, round(point.y * scaleFactor) / scaleFactor);
return CGPointMake(round(point.x * scaleFactor) / scaleFactor,
round(point.y * scaleFactor) / scaleFactor);
}
MLNMapPoint MLNMapPointForCoordinate(CLLocationCoordinate2D coordinate, double zoomLevel) {
mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel));
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel);
mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(
MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel));
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel);
}
MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) {
MLNMatrix4 mat4 = {
.m00 = array[0], .m01 = array[1], .m02 = array[2], .m03 = array[3],
.m10 = array[4], .m11 = array[5], .m12 = array[6], .m13 = array[7],
.m20 = array[8], .m21 = array[9], .m22 = array[10], .m23 = array[11],
.m30 = array[12], .m31 = array[13], .m32 = array[14], .m33 = array[15]
};
return mat4;
MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) {
MLNMatrix4 mat4 = {.m00 = array[0],
.m01 = array[1],
.m02 = array[2],
.m03 = array[3],
.m10 = array[4],
.m11 = array[5],
.m12 = array[6],
.m13 = array[7],
.m20 = array[8],
.m21 = array[9],
.m22 = array[10],
.m23 = array[11],
.m30 = array[12],
.m31 = array[13],
.m32 = array[14],
.m33 = array[15]};
return mat4;
}
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy)
{
return MAX(left, right) - MIN(left, right) <= accuracy;
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) {
return MAX(left, right) - MIN(left, right) <= accuracy;
}

View File

@@ -6,16 +6,17 @@
#import "MLNTileSource_Private.h"
#import "NSURL+MLNAdditions.h"
#if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h"
#import "UIImage+MLNAdditions.h"
#else
#import "NSImage+MLNAdditions.h"
#import "NSImage+MLNAdditions.h"
#endif
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/util/premultiply.hpp>
@interface MLNImageSource ()
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) mbgl::style::ImageSource *rawSource;
@@ -23,90 +24,93 @@
@implementation MLNImageSource
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad {
const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad);
auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray);
return self = [super initWithPendingSource:std::move(source)];
- (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad {
const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad);
auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray);
return self = [super initWithPendingSource:std::move(source)];
}
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad URL:(NSURL *)url {
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad];
self.URL = url;
return self;
- (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad
URL:(NSURL *)url {
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
self.URL = url;
return self;
}
- (instancetype)initWithIdentifier:(NSString *)identifier
coordinateQuad:(MLNCoordinateQuad)coordinateQuad
image:(MLNImage *)image {
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
self.image = image;
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad image:(MLNImage *)image {
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad];
self.image = image;
return self;
return self;
}
- (NSURL *)URL {
MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
}
- (void)setURL:(NSURL *)url {
MLNAssertStyleSourceIsValid();
if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_image = nil;
} else {
self.image = nullptr;
}
MLNAssertStyleSourceIsValid();
if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_image = nil;
} else {
self.image = nullptr;
}
}
- (void)setImage:(MLNImage *)image {
MLNAssertStyleSourceIsValid();
if (image != nullptr) {
self.rawSource->setImage(image.mgl_premultipliedImage);
} else {
self.rawSource->setImage(mbgl::PremultipliedImage({0,0}));
}
_image = image;
MLNAssertStyleSourceIsValid();
if (image != nullptr) {
self.rawSource->setImage(image.mgl_premultipliedImage);
} else {
self.rawSource->setImage(mbgl::PremultipliedImage({0, 0}));
}
_image = image;
}
- (MLNCoordinateQuad)coordinates {
MLNAssertStyleSourceIsValid();
return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates());
MLNAssertStyleSourceIsValid();
return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates());
}
- (void)setCoordinates: (MLNCoordinateQuad)coordinateQuad {
MLNAssertStyleSourceIsValid();
self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad));
- (void)setCoordinates:(MLNCoordinateQuad)coordinateQuad {
MLNAssertStyleSourceIsValid();
self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad));
}
- (NSString *)description {
if (self.rawSource) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier,
MLNStringFromCoordinateQuad(self.coordinates),
self.URL,
self.image];
}
else {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.image];
}
if (self.rawSource) {
return [NSString
stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier,
MLNStringFromCoordinateQuad(self.coordinates), self.URL, self.image];
} else {
return [NSString
stringWithFormat:
@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.image];
}
}
- (mbgl::style::ImageSource *)rawSource {
return (mbgl::style::ImageSource *)super.rawSource;
return (mbgl::style::ImageSource *)super.rawSource;
}
- (NSString *)attributionHTMLString {
if (!self.rawSource) {
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
return nil;
}
if (!self.rawSource) {
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
self.identifier);
return nil;
}
auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil;
auto attribution = self.rawSource->getAttribution();
return attribution ? @(attribution->c_str()) : nil;
}
@end

View File

@@ -1,5 +1,5 @@
#include <mbgl/util/logging.hpp>
#include <mbgl/util/enum.hpp>
#include <mbgl/util/logging.hpp>
#import "MLNLoggingConfiguration_Private.h"
@@ -10,154 +10,155 @@
namespace mbgl {
class MLNCoreLoggingObserver : public Log :: Observer {
class MLNCoreLoggingObserver : public Log ::Observer {
public:
//Return true not print messages at core level, and filter at platform level.
bool onRecord(EventSeverity severity, Event event, int64_t code, const std::string& msg) override{
NSString *message = [NSString stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code, [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
switch (severity) {
case EventSeverity::Debug:
MLNLogDebug(message);
break;
case EventSeverity::Info:
MLNLogInfo(message);
break;
case EventSeverity::Warning:
MLNLogWarning(message);
break;
case EventSeverity::Error:
MLNLogError(message);
break;
default:
assert(false);
}
return true;
// Return true not print messages at core level, and filter at platform level.
bool onRecord(EventSeverity severity, Event event, int64_t code,
const std::string &msg) override {
NSString *message = [NSString
stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code,
[NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
switch (severity) {
case EventSeverity::Debug:
MLNLogDebug(message);
break;
case EventSeverity::Info:
MLNLogInfo(message);
break;
case EventSeverity::Warning:
MLNLogWarning(message);
break;
case EventSeverity::Error:
MLNLogError(message);
break;
default:
assert(false);
}
return true;
}
};
}
} // namespace mbgl
@implementation MLNLoggingConfiguration
{
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
@implementation MLNLoggingConfiguration {
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
}
+ (instancetype)sharedConfiguration {
static dispatch_once_t once;
static id sharedConfiguration;
dispatch_once(&once, ^{
sharedConfiguration = [[self alloc] init];
((MLNLoggingConfiguration *)sharedConfiguration).handler = nil;
});
return sharedConfiguration;
static dispatch_once_t once;
static id sharedConfiguration;
dispatch_once(&once, ^{
sharedConfiguration = [[self alloc] init];
((MLNLoggingConfiguration *)sharedConfiguration).handler = nil;
});
return sharedConfiguration;
}
- (id)init{
if(self = [super init]){
mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>());
}
return self;
- (id)init {
if (self = [super init]) {
mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>());
}
return self;
}
- (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler {
if (!handler) {
_handler = [self defaultBlockHandler];
} else {
_handler = handler;
}
if (!handler) {
_handler = [self defaultBlockHandler];
} else {
_handler = handler;
}
}
- (void)logCallingFunction:(const char *)callingFunction functionLine:(NSUInteger)functionLine messageType:(MLNLoggingLevel)type format:(id)messageFormat, ... {
va_list formatList;
va_start(formatList, messageFormat);
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
va_end(formatList);
_handler(type, @(callingFunction), functionLine, formattedMessage);
- (void)logCallingFunction:(const char *)callingFunction
functionLine:(NSUInteger)functionLine
messageType:(MLNLoggingLevel)type
format:(id)messageFormat, ... {
va_list formatList;
va_start(formatList, messageFormat);
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
va_end(formatList);
_handler(type, @(callingFunction), functionLine, formattedMessage);
}
- (MLNLoggingBlockHandler)defaultBlockHandler {
MLNLoggingBlockHandler maplibreHandler = ^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) {
MLNLoggingBlockHandler maplibreHandler =
^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) {
if (@available(iOS 10.0, macOS 10.12.0, *)) {
static dispatch_once_t once;
static os_log_t info_log;
static dispatch_once_t once;
static os_log_t info_log;
#if MLN_LOGGING_ENABLE_DEBUG
static os_log_t debug_log;
static os_log_t debug_log;
#endif
static os_log_t error_log;
static os_log_t fault_log;
static os_log_type_t log_types[] = { OS_LOG_TYPE_DEFAULT,
OS_LOG_TYPE_INFO,
static os_log_t error_log;
static os_log_t fault_log;
static os_log_type_t log_types[] = {OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_INFO,
#if MLN_LOGGING_ENABLE_DEBUG
OS_LOG_TYPE_DEBUG,
OS_LOG_TYPE_DEBUG,
#endif
OS_LOG_TYPE_ERROR,
OS_LOG_TYPE_FAULT };
constexpr const char* const subsystem = "org.maplibre.Native";
dispatch_once(&once, ^ {
info_log = os_log_create(subsystem, "INFO");
OS_LOG_TYPE_ERROR, OS_LOG_TYPE_FAULT};
constexpr const char *const subsystem = "org.maplibre.Native";
dispatch_once(&once, ^{
info_log = os_log_create(subsystem, "INFO");
#if MLN_LOGGING_ENABLE_DEBUG
debug_log = os_log_create(subsystem, "DEBUG");
debug_log = os_log_create(subsystem, "DEBUG");
#endif
error_log = os_log_create(subsystem, "ERROR");
fault_log = os_log_create(subsystem, "FAULT");
});
error_log = os_log_create(subsystem, "ERROR");
fault_log = os_log_create(subsystem, "FAULT");
});
os_log_t maplibre_log;
switch (level) {
case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning:
maplibre_log = info_log;
break;
os_log_t maplibre_log;
switch (level) {
case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning:
maplibre_log = info_log;
break;
#if MLN_LOGGING_ENABLE_DEBUG
case MLNLoggingLevelDebug:
maplibre_log = debug_log;
break;
case MLNLoggingLevelDebug:
maplibre_log = debug_log;
break;
#endif
case MLNLoggingLevelError:
maplibre_log = error_log;
break;
case MLNLoggingLevelFault:
maplibre_log = fault_log;
break;
case MLNLoggingLevelNone:
default:
break;
}
case MLNLoggingLevelError:
maplibre_log = error_log;
break;
case MLNLoggingLevelFault:
maplibre_log = fault_log;
break;
case MLNLoggingLevelNone:
default:
break;
}
os_log_type_t logType = log_types[level];
os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line, message);
os_log_type_t logType = log_types[level];
os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line,
message);
} else {
NSString *category;
switch (level) {
case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning:
category = @"INFO";
break;
NSString *category;
switch (level) {
case MLNLoggingLevelInfo:
case MLNLoggingLevelWarning:
category = @"INFO";
break;
#if MLN_LOGGING_ENABLE_DEBUG
case MLNLoggingLevelDebug:
category = @"DEBUG";
break;
case MLNLoggingLevelDebug:
category = @"DEBUG";
break;
#endif
case MLNLoggingLevelError:
category = @"ERROR";
break;
case MLNLoggingLevelFault:
category = @"FAULT";
break;
case MLNLoggingLevelNone:
default:
break;
}
case MLNLoggingLevelError:
category = @"ERROR";
break;
case MLNLoggingLevelFault:
category = @"FAULT";
break;
case MLNLoggingLevelNone:
default:
break;
}
NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message);
NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message);
}
};
};
return maplibreHandler;
return maplibreHandler;
}
@end

View File

@@ -8,191 +8,179 @@
@implementation MLNMapCamera
+ (BOOL)supportsSecureCoding
{
return YES;
+ (BOOL)supportsSecureCoding {
return YES;
}
+ (instancetype)camera
{
return [[self alloc] init];
+ (instancetype)camera {
return [[self alloc] init];
}
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
eyeAltitude:(CLLocationDistance)eyeAltitude
{
CLLocationDirection heading = -1;
CGFloat pitch = -1;
if (CLLocationCoordinate2DIsValid(centerCoordinate) && CLLocationCoordinate2DIsValid(eyeCoordinate)) {
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
eyeAltitude:(CLLocationDistance)eyeAltitude {
CLLocationDirection heading = -1;
CGFloat pitch = -1;
if (CLLocationCoordinate2DIsValid(centerCoordinate) &&
CLLocationCoordinate2DIsValid(eyeCoordinate)) {
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
longitude:centerCoordinate.longitude];
CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude
longitude:eyeCoordinate.longitude];
CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation];
CGFloat radianPitch = atan2(eyeAltitude, groundDistance);
pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0);
}
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
longitude:centerCoordinate.longitude];
CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude
longitude:eyeCoordinate.longitude];
CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation];
CGFloat radianPitch = atan2(eyeAltitude, groundDistance);
pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0);
}
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:eyeAltitude
pitch:pitch
heading:heading];
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:eyeAltitude
pitch:pitch
heading:heading];
}
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
acrossDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
// Angle at the viewpoint formed by the straight lines running perpendicular
// to the ground and toward the center coordinate.
CLLocationDirection eyeAngle = 90 - pitch;
CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
heading:(CLLocationDirection)heading {
// Angle at the viewpoint formed by the straight lines running perpendicular
// to the ground and toward the center coordinate.
CLLocationDirection eyeAngle = 90 - pitch;
CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude
pitch:pitch
heading:heading];
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude
pitch:pitch
heading:heading];
}
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
altitude:(CLLocationDistance)altitude
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude
pitch:pitch
heading:heading];
heading:(CLLocationDirection)heading {
return [[self alloc] initWithCenterCoordinate:centerCoordinate
altitude:altitude
pitch:pitch
heading:heading];
}
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
fromDistance:(CLLocationDistance)distance
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
// TODO: Remove this compatibility shim in favor of `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:.
// https://github.com/mapbox/mapbox-gl-native/issues/12943
return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
altitude:distance
pitch:pitch
heading:heading];
heading:(CLLocationDirection)heading {
// TODO: Remove this compatibility shim in favor of
// `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:.
// https://github.com/mapbox/mapbox-gl-native/issues/12943
return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
altitude:distance
pitch:pitch
heading:heading];
}
- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
altitude:(CLLocationDistance)altitude
pitch:(CGFloat)pitch
heading:(CLLocationDirection)heading
{
MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°", MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading);
if (self = [super init])
{
_centerCoordinate = centerCoordinate;
_altitude = altitude;
_pitch = pitch;
_heading = heading;
_roll = 0;
}
return self;
heading:(CLLocationDirection)heading {
MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°",
MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading);
if (self = [super init]) {
_centerCoordinate = centerCoordinate;
_altitude = altitude;
_pitch = pitch;
_heading = heading;
_roll = 0;
}
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
MLNLogInfo(@"Initialiazing with coder.");
if (self = [super init])
{
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
[coder decodeDoubleForKey:@"centerLongitude"]);
_altitude = [coder decodeDoubleForKey:@"altitude"];
_pitch = [coder decodeDoubleForKey:@"pitch"];
_heading = [coder decodeDoubleForKey:@"heading"];
_roll = [coder decodeDoubleForKey:@"roll"];
}
return self;
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
MLNLogInfo(@"Initialiazing with coder.");
if (self = [super init]) {
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
[coder decodeDoubleForKey:@"centerLongitude"]);
_altitude = [coder decodeDoubleForKey:@"altitude"];
_pitch = [coder decodeDoubleForKey:@"pitch"];
_heading = [coder decodeDoubleForKey:@"heading"];
_roll = [coder decodeDoubleForKey:@"roll"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
[coder encodeDouble:_altitude forKey:@"altitude"];
[coder encodeDouble:_pitch forKey:@"pitch"];
[coder encodeDouble:_heading forKey:@"heading"];
[coder encodeDouble:_roll forKey:@"roll"];
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
[coder encodeDouble:_altitude forKey:@"altitude"];
[coder encodeDouble:_pitch forKey:@"pitch"];
[coder encodeDouble:_heading forKey:@"heading"];
[coder encodeDouble:_roll forKey:@"roll"];
}
- (id)copyWithZone:(nullable NSZone *)zone
{
MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
altitude:_altitude
pitch:_pitch
heading:_heading];
copy.roll = _roll;
return copy;
- (id)copyWithZone:(nullable NSZone *)zone {
MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
altitude:_altitude
pitch:_pitch
heading:_heading];
copy.roll = _roll;
return copy;
}
+ (NSSet<NSString *> *)keyPathsForValuesAffectingViewingDistance {
return [NSSet setWithObjects:@"altitude", @"pitch", nil];
return [NSSet setWithObjects:@"altitude", @"pitch", nil];
}
- (CLLocationDistance)viewingDistance {
CLLocationDirection eyeAngle = 90 - self.pitch;
return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle));
CLLocationDirection eyeAngle = 90 - self.pitch;
return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle));
}
- (void)setViewingDistance:(CLLocationDistance)distance {
MLNLogDebug(@"Setting viewingDistance: %f", distance);
CLLocationDirection eyeAngle = 90 - self.pitch;
self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
MLNLogDebug(@"Setting viewingDistance: %f", distance);
CLLocationDirection eyeAngle = 90 - self.pitch;
self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; heading = %.0f°; pitch = %.0f°; roll = %.0f°>",
NSStringFromClass([self class]), (void *)self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch, _roll];
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; "
@"heading = %.0f°; pitch = %.0f°; roll = %.0f°>",
NSStringFromClass([self class]), (void *)self,
_centerCoordinate.latitude, _centerCoordinate.longitude,
_altitude, _heading, _pitch, _roll];
}
- (BOOL)isEqual:(id)other
{
if ( ! [other isKindOfClass:[self class]])
{
return NO;
}
if (other == self)
{
return YES;
}
- (BOOL)isEqual:(id)other {
if (![other isKindOfClass:[self class]]) {
return NO;
}
if (other == self) {
return YES;
}
MLNMapCamera *otherCamera = other;
return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude
&& _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude
&& _altitude == otherCamera.altitude
&& _pitch == otherCamera.pitch && _heading == otherCamera.heading
&& _roll == otherCamera.roll);
MLNMapCamera *otherCamera = other;
return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude &&
_centerCoordinate.longitude == otherCamera.centerCoordinate.longitude &&
_altitude == otherCamera.altitude && _pitch == otherCamera.pitch &&
_heading == otherCamera.heading && _roll == otherCamera.roll);
}
- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera
{
if (otherCamera == self)
{
return YES;
}
- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera {
if (otherCamera == self) {
return YES;
}
return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-8)
&& MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-8)
&& MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6)
&& MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2)
&& MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2)
&& MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2));
return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude,
otherCamera.centerCoordinate.latitude, 1e-8) &&
MLNEqualFloatWithAccuracy(_centerCoordinate.longitude,
otherCamera.centerCoordinate.longitude, 1e-8) &&
MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) &&
MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) &&
MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) &&
MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2));
}
- (NSUInteger)hash
{
return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash
+ @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
- (NSUInteger)hash {
return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash +
@(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
}
@end

View File

@@ -6,19 +6,17 @@
@implementation MLNMapOptions
-(instancetype _Nonnull)init
{
self = [super init];
if (self)
{
_styleURL = nil;
_styleJSON = nil;
_pluginLayers = nil;
- (instancetype _Nonnull)init {
self = [super init];
if (self) {
_styleURL = nil;
_styleJSON = nil;
_pluginLayers = nil;
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
}
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
}
return self;
return self;
}
@end

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

View File

@@ -1,244 +1,238 @@
#import "MLNNetworkConfiguration_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNNetworkConfiguration_Private.h"
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
#import "MLNSettings_Private.h"
#endif
#import "MLNReachability.h"
static NSString * const MLNStartTime = @"start_time";
static NSString * const MLNResourceType = @"resource_type";
NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
static NSString *const MLNStartTime = @"start_time";
static NSString *const MLNResourceType = @"resource_type";
NSString *const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
@interface MLNNetworkConfiguration () <MLNNativeNetworkDelegate>
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary*> *events;
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *events;
@property (nonatomic, weak) id<MLNNetworkConfigurationMetricsDelegate> metricsDelegate;
@property (nonatomic) dispatch_queue_t eventsQueue;
@end
@implementation MLNNetworkConfiguration
{
NSURLSessionConfiguration *_sessionConfig;
@implementation MLNNetworkConfiguration {
NSURLSessionConfiguration *_sessionConfig;
}
- (instancetype)init {
if (self = [super init]) {
self.sessionConfiguration = nil;
_events = [NSMutableDictionary dictionary];
_eventsQueue = dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT);
}
if (self = [super init]) {
self.sessionConfiguration = nil;
_events = [NSMutableDictionary dictionary];
_eventsQueue =
dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT);
}
return self;
return self;
}
+ (instancetype)sharedManager {
static dispatch_once_t onceToken;
static MLNNetworkConfiguration *_sharedManager;
dispatch_once(&onceToken, ^{
_sharedManager = [[self alloc] init];
});
static dispatch_once_t onceToken;
static MLNNetworkConfiguration *_sharedManager;
dispatch_once(&onceToken, ^{
_sharedManager = [[self alloc] init];
});
// Notice, this is reset for each call. This is primarily for testing purposes.
// TODO: Consider only calling this for testing?
[_sharedManager resetNativeNetworkManagerDelegate];
// Notice, this is reset for each call. This is primarily for testing purposes.
// TODO: Consider only calling this for testing?
[_sharedManager resetNativeNetworkManagerDelegate];
return _sharedManager;
return _sharedManager;
}
- (void)resetNativeNetworkManagerDelegate {
// Tell core about our network integration. `delegate` here is not (yet)
// intended to be set to nil, except for testing.
[MLNNativeNetworkManager sharedManager].delegate = self;
// Tell core about our network integration. `delegate` here is not (yet)
// intended to be set to nil, except for testing.
[MLNNativeNetworkManager sharedManager].delegate = self;
}
+ (NSURLSessionConfiguration *)defaultSessionConfiguration {
NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *sessionConfiguration =
[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.timeoutIntervalForResource = 30;
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfiguration.URLCache = nil;
sessionConfiguration.timeoutIntervalForResource = 30;
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfiguration.URLCache = nil;
return sessionConfiguration;
return sessionConfiguration;
}
// MARK: - MLNNativeNetworkDelegate
- (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager {
// Note: this method is NOT called on the main thread.
NSURLSession *session;
if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) {
session = [self.delegate sessionForNetworkConfiguration:self];
}
// Note: this method is NOT called on the main thread.
NSURLSession *session;
if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) {
session = [self.delegate sessionForNetworkConfiguration:self];
}
// Check for a background session; string checking is fragile, but this is not
// a deal breaker as we're only doing this to provide more clarity to the
// developer
NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")],
@"Background NSURLSessions are not yet supported");
// Check for a background session; string checking is fragile, but this is not
// a deal breaker as we're only doing this to provide more clarity to the
// developer
NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")],
@"Background NSURLSessions are not yet supported");
if (session.delegate) {
NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)],
@"Session delegates conforming to NSURLSessionDataDelegate are not yet supported");
}
if (session.delegate) {
NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)],
@"Session delegates conforming to NSURLSessionDataDelegate are not yet supported");
}
return session;
return session;
}
- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request {
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
return [self.delegate willSendRequest:request];
}
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
return [self.delegate willSendRequest:request];
}
return request;
return request;
}
- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response {
if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data
urlResponse:response.response
error:response.error];
if (tempResponse) {
MLNInternalNetworkResponse *internalResponse = [MLNInternalNetworkResponse responseWithData:tempResponse.data
urlResponse:tempResponse.response
error:tempResponse.error];
return internalResponse;
} else {
return nil;
}
if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data
urlResponse:response.response
error:response.error];
if (tempResponse) {
MLNInternalNetworkResponse *internalResponse =
[MLNInternalNetworkResponse responseWithData:tempResponse.data
urlResponse:tempResponse.response
error:tempResponse.error];
return internalResponse;
} else {
return nil;
}
}
return response;
return response;
}
- (NSURLSessionConfiguration *)sessionConfiguration {
NSURLSessionConfiguration *sessionConfig = nil;
@synchronized (self) {
sessionConfig = _sessionConfig;
}
return sessionConfig;
NSURLSessionConfiguration *sessionConfig = nil;
@synchronized(self) {
sessionConfig = _sessionConfig;
}
return sessionConfig;
}
- (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
@synchronized (self) {
if (sessionConfiguration == nil) {
_sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration];
} else {
_sessionConfig = sessionConfiguration;
}
@synchronized(self) {
if (sessionConfiguration == nil) {
_sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration];
} else {
_sessionConfig = sessionConfiguration;
}
}
}
- (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType {
if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) {
NSDate *startDate = [NSDate date];
[self setEventDictionary:@{ MLNStartTime: startDate, MLNResourceType: resourceType }
forKey:urlString];
}
if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) {
NSDate *startDate = [NSDate date];
[self setEventDictionary:@{MLNStartTime : startDate, MLNResourceType : resourceType}
forKey:urlString];
}
}
- (void)stopDownloadEventForResponse:(NSURLResponse *)response {
[self sendEventForURLResponse:response withAction:nil];
[self sendEventForURLResponse:response withAction:nil];
}
- (void)cancelDownloadEventForResponse:(NSURLResponse *)response {
[self sendEventForURLResponse:response withAction:@"cancel"];
[self sendEventForURLResponse:response withAction:@"cancel"];
}
- (void)debugLog:(NSString *)message {
MLNLogDebug(message);
MLNLogDebug(message);
}
- (void)errorLog:(NSString *)message {
MLNLogError(message);
MLNLogError(message);
}
// MARK: - Event management
- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action
{
if ([response isKindOfClass:[NSURLResponse class]]) {
NSString *urlString = response.URL.relativePath;
if (urlString && [self eventDictionaryForKey:urlString]) {
NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action];
[self removeEventDictionaryForKey:urlString];
- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action {
if ([response isKindOfClass:[NSURLResponse class]]) {
NSString *urlString = response.URL.relativePath;
if (urlString && [self eventDictionaryForKey:urlString]) {
NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action];
[self removeEventDictionaryForKey:urlString];
dispatch_async(dispatch_get_main_queue(), ^{
[self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes];
});
}
}
}
- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action
{
NSString *urlString = response.URL.relativePath;
NSDictionary *parameters = [self eventDictionaryForKey:urlString];
NSDate *startDate = [parameters objectForKey:MLNStartTime];
NSDate *endDate = [NSDate date];
NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate];
NSDateFormatter* iso8601Formatter = [[NSDateFormatter alloc] init];
iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]];
- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action {
NSString *urlString = response.URL.relativePath;
NSDictionary *parameters = [self eventDictionaryForKey:urlString];
NSDate *startDate = [parameters objectForKey:MLNStartTime];
NSDate *endDate = [NSDate date];
NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate];
NSDateFormatter *iso8601Formatter = [[NSDateFormatter alloc] init];
iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]];
NSMutableArray *attributes = [NSMutableArray array];
[attributes addObject:@{ @"name" : @"requestUrl" , @"value" : urlString }];
[attributes addObject:@{ @"name" : MLNResourceType , @"value" : [parameters objectForKey:MLNResourceType] }];
NSMutableArray *attributes = [NSMutableArray array];
[attributes addObject:@{@"name" : @"requestUrl", @"value" : urlString}];
[attributes
addObject:@{@"name" : MLNResourceType, @"value" : [parameters objectForKey:MLNResourceType]}];
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
[attributes addObject:@{ @"name" : @"responseCode", @"value" : @(responseCode)}];
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
[attributes addObject:@{@"name" : @"responseCode", @"value" : @(responseCode)}];
}
BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi];
[attributes addObject:@{ @"name" : @"wifiOn", @"value" : @(isWIFIOn)}];
BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi];
[attributes addObject:@{@"name" : @"wifiOn", @"value" : @(isWIFIOn)}];
if (action) {
[attributes addObject:@{ @"name" : @"action" , @"value" : action }];
}
if (action) {
[attributes addObject:@{@"name" : @"action", @"value" : action}];
}
double elapsedTimeInMS = elapsedTime * 1000.0;
double elapsedTimeInMS = elapsedTime * 1000.0;
return @{
@"event" : kMLNDownloadPerformanceEvent,
@"created" : createdDate,
@"sessionId" : [NSUUID UUID].UUIDString,
@"counters" : @[ @{ @"name" : @"elapsedMS" , @"value" : @(elapsedTimeInMS) } ],
@"attributes" : attributes
};
return @{
@"event" : kMLNDownloadPerformanceEvent,
@"created" : createdDate,
@"sessionId" : [NSUUID UUID].UUIDString,
@"counters" : @[ @{@"name" : @"elapsedMS", @"value" : @(elapsedTimeInMS)} ],
@"attributes" : attributes
};
}
// MARK: - Events dictionary access
- (nullable NSDictionary*)eventDictionaryForKey:(nonnull NSString*)key {
__block NSDictionary *dictionary;
- (nullable NSDictionary *)eventDictionaryForKey:(nonnull NSString *)key {
__block NSDictionary *dictionary;
dispatch_sync(self.eventsQueue, ^{
dictionary = [self.events objectForKey:key];
});
dispatch_sync(self.eventsQueue, ^{
dictionary = [self.events objectForKey:key];
});
return dictionary;
return dictionary;
}
- (void)setEventDictionary:(nonnull NSDictionary*)dictionary forKey:(nonnull NSString*)key {
dispatch_barrier_async(self.eventsQueue, ^{
[self.events setObject:dictionary forKey:key];
});
- (void)setEventDictionary:(nonnull NSDictionary *)dictionary forKey:(nonnull NSString *)key {
dispatch_barrier_async(self.eventsQueue, ^{
[self.events setObject:dictionary forKey:key];
});
}
- (void)removeEventDictionaryForKey:(nonnull NSString*)key {
dispatch_barrier_async(self.eventsQueue, ^{
[self.events removeObjectForKey:key];
});
- (void)removeEventDictionaryForKey:(nonnull NSString *)key {
dispatch_barrier_async(self.eventsQueue, ^{
[self.events removeObjectForKey:key];
});
}
@end
@@ -246,11 +240,11 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
@implementation MLNNetworkConfiguration (ForTesting)
+ (void)testing_clearNativeNetworkManagerDelegate {
[MLNNativeNetworkManager sharedManager].delegate = nil;
[MLNNativeNetworkManager sharedManager].delegate = nil;
}
+ (id)testing_nativeNetworkManagerDelegate {
return [MLNNativeNetworkManager sharedManager].delegate;
return [MLNNativeNetworkManager sharedManager].delegate;
}
@end

View File

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

View File

@@ -1,12 +1,12 @@
#import "MLNOfflinePack_Private.h"
#import "MLNOfflineStorage_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNOfflineRegion_Private.h"
#import "MLNTilePyramidOfflineRegion.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
#import "MLNOfflineStorage_Private.h"
#import "MLNShapeOfflineRegion.h"
#import "MLNShapeOfflineRegion_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNTilePyramidOfflineRegion.h"
#import "MLNTilePyramidOfflineRegion_Private.h"
#import "NSValue+MLNAdditions.h"
@@ -22,18 +22,19 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
This macro should be used at the beginning of any public-facing instance method
of ``MLNOfflinePack``. For private methods, an assertion is more appropriate.
*/
#define MLNAssertOfflinePackIsValid() \
do { \
if (_state == MLNOfflinePackStateInvalid) { \
[NSException raise:MLNInvalidOfflinePackException \
format: \
@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \
@"on this instance of MLNOfflinePack, rendering it invalid. It is an " \
@"error to send any message to this pack."]; \
} \
} while (NO);
#define MLNAssertOfflinePackIsValid() \
do { \
if (_state == MLNOfflinePackStateInvalid) { \
[NSException \
raise:MLNInvalidOfflinePackException \
format:@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \
@"on this instance of MLNOfflinePack, rendering it invalid. It is an " \
@"error to send any message to this pack."]; \
} \
} while (NO);
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private, MLNTilePyramidOfflineRegion_Private>
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private,
MLNTilePyramidOfflineRegion_Private>
@end
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
@@ -41,14 +42,14 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver {
public:
MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {}
MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {}
void statusChanged(mbgl::OfflineRegionStatus status) override;
void responseError(mbgl::Response::Error error) override;
void mapboxTileCountLimitExceeded(uint64_t limit) override;
void statusChanged(mbgl::OfflineRegionStatus status) override;
void responseError(mbgl::Response::Error error) override;
void mapboxTileCountLimitExceeded(uint64_t limit) override;
private:
__weak MLNOfflinePack *pack = nullptr;
__weak MLNOfflinePack *pack = nullptr;
};
@interface MLNOfflinePack ()
@@ -59,268 +60,296 @@ private:
@end
@implementation MLNOfflinePack {
BOOL _isSuspending;
std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource;
BOOL _isSuspending;
std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource;
}
- (instancetype)init {
MLNLogInfo(@"Calling this initializer is not allowed.");
if (self = [super init]) {
_state = MLNOfflinePackStateInvalid;
NSLog(@"%s called; did you mean to call +[MLNOfflineStorage addPackForRegion:withContext:completionHandler:] instead?", __PRETTY_FUNCTION__);
}
return self;
MLNLogInfo(@"Calling this initializer is not allowed.");
if (self = [super init]) {
_state = MLNOfflinePackStateInvalid;
NSLog(@"%s called; did you mean to call +[MLNOfflineStorage "
@"addPackForRegion:withContext:completionHandler:] instead?",
__PRETTY_FUNCTION__);
}
return self;
}
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region {
if (self = [super init]) {
_mbglOfflineRegion = region;
_state = MLNOfflinePackStateUnknown;
if (self = [super init]) {
_mbglOfflineRegion = region;
_state = MLNOfflinePackStateUnknown;
_mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource];
_mbglDatabaseFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self));
}
return self;
_mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource];
_mbglDatabaseFileSource->setOfflineRegionObserver(
*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self));
}
return self;
}
- (void)dealloc {
MLNAssert(_state == MLNOfflinePackStateInvalid, @"MLNOfflinePack was not invalided prior to deallocation.");
MLNAssert(_state == MLNOfflinePackStateInvalid,
@"MLNOfflinePack was not invalided prior to deallocation.");
}
- (id <MLNOfflineRegion>)region {
MLNAssertOfflinePackIsValid();
- (id<MLNOfflineRegion>)region {
MLNAssertOfflinePackIsValid();
const mbgl::OfflineRegionDefinition &regionDefinition = _mbglOfflineRegion->getDefinition();
MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private.");
MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private.");
const mbgl::OfflineRegionDefinition &regionDefinition = _mbglOfflineRegion->getDefinition();
MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)],
@"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private.");
MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)],
@"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private.");
return std::visit(mbgl::overloaded{
[&] (const mbgl::OfflineTilePyramidRegionDefinition def){
return (id <MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc] initWithOfflineRegionDefinition:def];
},
[&] (const mbgl::OfflineGeometryRegionDefinition& def){
return (id <MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc] initWithOfflineRegionDefinition:def];
}
}, regionDefinition);
return std::visit(
mbgl::overloaded{[&](const mbgl::OfflineTilePyramidRegionDefinition def) {
return (id<MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc]
initWithOfflineRegionDefinition:def];
},
[&](const mbgl::OfflineGeometryRegionDefinition &def) {
return (id<MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc]
initWithOfflineRegionDefinition:def];
}},
regionDefinition);
}
- (NSData *)context {
MLNAssertOfflinePackIsValid();
MLNAssertOfflinePackIsValid();
const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata();
return [NSData dataWithBytes:&metadata[0] length:metadata.size()];
const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata();
return [NSData dataWithBytes:&metadata[0] length:metadata.size()];
}
- (void)setContext:(NSData *)context completionHandler:(void (^_Nullable)(NSError * _Nullable error))completion {
MLNAssertOfflinePackIsValid();
- (void)setContext:(NSData *)context
completionHandler:(void (^_Nullable)(NSError *_Nullable error))completion {
MLNAssertOfflinePackIsValid();
mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()];
mbgl::OfflineRegionMetadata metadata(context.length);
[context getBytes:&metadata[0] length:metadata.size()];
[self willChangeValueForKey:@"context"];
__weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->updateOfflineMetadata(_mbglOfflineRegion->getID(), metadata, [&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr> mbglOfflineRegionMetadata) {
[self willChangeValueForKey:@"context"];
__weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->updateOfflineMetadata(
_mbglOfflineRegion->getID(), metadata,
[&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr>
mbglOfflineRegionMetadata) {
NSError *error;
if (!mbglOfflineRegionMetadata) {
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str());
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{
NSLocalizedDescriptionKey: errorDescription,
} : nil];
NSString *errorDescription =
@(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str());
error = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeModifyingOfflineStorageFailed
userInfo:errorDescription ? @{
NSLocalizedDescriptionKey : errorDescription,
}
: nil];
}
dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) {
[weakSelf reloadWithCompletionHandler:^(NSError * _Nullable reloadingError) {
MLNOfflinePack *strongSelf = weakSelf;
[strongSelf didChangeValueForKey:@"context"];
if (completion) {
completion(error ?: reloadingError);
}
}];
[weakSelf reloadWithCompletionHandler:^(NSError *_Nullable reloadingError) {
MLNOfflinePack *strongSelf = weakSelf;
[strongSelf didChangeValueForKey:@"context"];
if (completion) {
completion(error ?: reloadingError);
}
}];
});
});
});
}
- (void)reloadWithCompletionHandler:(void (^)(NSError * _Nullable error))completion {
auto regionID = _mbglOfflineRegion->getID();
MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage];
__weak MLNOfflinePack *weakSelf = self;
[sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs, __unused NSError * _Nullable error) {
MLNOfflinePack *strongSelf = weakSelf;
for (MLNOfflinePack *pack in packs) {
if (pack.mbglOfflineRegion->getID() == regionID) {
strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion;
}
[pack invalidate];
}
completion(error);
}];
- (void)reloadWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
auto regionID = _mbglOfflineRegion->getID();
MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage];
__weak MLNOfflinePack *weakSelf = self;
[sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs,
__unused NSError *_Nullable error) {
MLNOfflinePack *strongSelf = weakSelf;
for (MLNOfflinePack *pack in packs) {
if (pack.mbglOfflineRegion->getID() == regionID) {
strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion;
}
[pack invalidate];
}
completion(error);
}];
}
- (void)resume {
MLNLogInfo(@"Resuming pack download.");
MLNAssertOfflinePackIsValid();
MLNLogInfo(@"Resuming pack download.");
MLNAssertOfflinePackIsValid();
self.state = MLNOfflinePackStateActive;
self.state = MLNOfflinePackStateActive;
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active);
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion,
mbgl::OfflineRegionDownloadState::Active);
}
- (void)suspend {
MLNLogInfo(@"Suspending pack download.");
MLNAssertOfflinePackIsValid();
MLNLogInfo(@"Suspending pack download.");
MLNAssertOfflinePackIsValid();
if (self.state == MLNOfflinePackStateActive) {
self.state = MLNOfflinePackStateInactive;
_isSuspending = YES;
}
if (self.state == MLNOfflinePackStateActive) {
self.state = MLNOfflinePackStateInactive;
_isSuspending = YES;
}
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
_mbglDatabaseFileSource->setOfflineRegionDownloadState(
*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
}
- (void)invalidate {
MLNLogInfo(@"Invalidating pack.");
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack.");
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region");
MLNLogInfo(@"Invalidating pack.");
MLNAssert(_state != MLNOfflinePackStateInvalid,
@"Cannot invalidate an already invalid offline pack.");
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region");
@synchronized (self) {
self.state = MLNOfflinePackStateInvalid;
if (self.mbglOfflineRegion) {
_mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
}
self.mbglOfflineRegion = nil;
@synchronized(self) {
self.state = MLNOfflinePackStateInvalid;
if (self.mbglOfflineRegion) {
_mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
}
self.mbglOfflineRegion = nil;
}
}
- (void)setState:(MLNOfflinePackState)state {
MLNLogDebug(@"Setting state: %ld", (long)state);
if (!self.mbglOfflineRegion) {
// A progress update has arrived after the call to
// -[MLNOfflineStorage removePack:withCompletionHandler:] but before the
// removal is complete and the completion handler is called.
MLNAssert(_state == MLNOfflinePackStateInvalid, @"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
return;
}
MLNLogDebug(@"Setting state: %ld", (long)state);
if (!self.mbglOfflineRegion) {
// A progress update has arrived after the call to
// -[MLNOfflineStorage removePack:withCompletionHandler:] but before the
// removal is complete and the completion handler is called.
MLNAssert(_state == MLNOfflinePackStateInvalid,
@"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
return;
}
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change the state of an invalid offline pack.");
MLNAssert(_state != MLNOfflinePackStateInvalid,
@"Cannot change the state of an invalid offline pack.");
if (!_isSuspending || state != MLNOfflinePackStateActive) {
_isSuspending = NO;
_state = state;
}
if (!_isSuspending || state != MLNOfflinePackStateActive) {
_isSuspending = NO;
_state = state;
}
}
- (void)requestProgress {
MLNLogInfo(@"Requesting pack progress.");
MLNAssertOfflinePackIsValid();
MLNLogInfo(@"Requesting pack progress.");
MLNAssertOfflinePackIsValid();
__weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->getOfflineRegionStatus(*_mbglOfflineRegion, [&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) {
__weak MLNOfflinePack *weakSelf = self;
_mbglDatabaseFileSource->getOfflineRegionStatus(
*_mbglOfflineRegion,
[&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) {
if (status) {
mbgl::OfflineRegionStatus checkedStatus = *status;
dispatch_async(dispatch_get_main_queue(), ^{
MLNOfflinePack *strongSelf = weakSelf;
[strongSelf offlineRegionStatusDidChange:checkedStatus];
});
mbgl::OfflineRegionStatus checkedStatus = *status;
dispatch_async(dispatch_get_main_queue(), ^{
MLNOfflinePack *strongSelf = weakSelf;
[strongSelf offlineRegionStatusDidChange:checkedStatus];
});
}
});
});
}
- (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status {
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change update progress of an invalid offline pack.");
MLNAssert(_state != MLNOfflinePackStateInvalid,
@"Cannot change update progress of an invalid offline pack.");
switch (status.downloadState) {
case mbgl::OfflineRegionDownloadState::Inactive:
self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive;
break;
switch (status.downloadState) {
case mbgl::OfflineRegionDownloadState::Inactive:
self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive;
break;
case mbgl::OfflineRegionDownloadState::Active:
self.state = MLNOfflinePackStateActive;
break;
}
case mbgl::OfflineRegionDownloadState::Active:
self.state = MLNOfflinePackStateActive;
break;
}
if (_isSuspending) {
return;
}
if (_isSuspending) {
return;
}
MLNOfflinePackProgress progress;
progress.countOfResourcesCompleted = status.completedResourceCount;
progress.countOfBytesCompleted = status.completedResourceSize;
progress.countOfTilesCompleted = status.completedTileCount;
progress.countOfTileBytesCompleted = status.completedTileSize;
progress.countOfResourcesExpected = status.requiredResourceCount;
progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
self.progress = progress;
MLNOfflinePackProgress progress;
progress.countOfResourcesCompleted = status.completedResourceCount;
progress.countOfBytesCompleted = status.completedResourceSize;
progress.countOfTilesCompleted = status.completedTileCount;
progress.countOfTileBytesCompleted = status.completedTileSize;
progress.countOfResourcesExpected = status.requiredResourceCount;
progress.maximumResourcesExpected =
status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
self.progress = progress;
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyState: @(self.state),
MLNOfflinePackUserInfoKeyProgress: [NSValue valueWithMLNOfflinePackProgress:progress]};
NSDictionary *userInfo = @{
MLNOfflinePackUserInfoKeyState : @(self.state),
MLNOfflinePackUserInfoKeyProgress : [NSValue valueWithMLNOfflinePackProgress:progress]
};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification
object:self
userInfo:userInfo];
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification
object:self
userInfo:userInfo];
}
- (void)didReceiveError:(NSError *)error {
MLNLogError(@"Error: %@", error.localizedDescription);
MLNLogInfo(@"Notifying about pack error.");
MLNLogError(@"Error: %@", error.localizedDescription);
MLNLogInfo(@"Notifying about pack error.");
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyError: error };
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackErrorNotification
object:self
userInfo:userInfo];
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyError : error};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackErrorNotification object:self userInfo:userInfo];
}
- (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit {
MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit);
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyMaximumCount: @(limit) };
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification
object:self
userInfo:userInfo];
MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit);
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyMaximumCount : @(limit)};
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
[noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification
object:self
userInfo:userInfo];
}
NSError *MLNErrorFromResponseError(mbgl::Response::Error error) {
NSInteger errorCode = MLNErrorCodeUnknown;
switch (error.reason) {
case mbgl::Response::Error::Reason::NotFound:
errorCode = MLNErrorCodeNotFound;
break;
NSInteger errorCode = MLNErrorCodeUnknown;
switch (error.reason) {
case mbgl::Response::Error::Reason::NotFound:
errorCode = MLNErrorCodeNotFound;
break;
case mbgl::Response::Error::Reason::Server:
errorCode = MLNErrorCodeBadServerResponse;
break;
case mbgl::Response::Error::Reason::Server:
errorCode = MLNErrorCodeBadServerResponse;
break;
case mbgl::Response::Error::Reason::Connection:
errorCode = MLNErrorCodeConnectionFailed;
break;
case mbgl::Response::Error::Reason::Connection:
errorCode = MLNErrorCodeConnectionFailed;
break;
default:
break;
}
return [NSError errorWithDomain:MLNErrorDomain code:errorCode userInfo:@{
NSLocalizedFailureReasonErrorKey: @(error.message.c_str())
}];
default:
break;
}
return [NSError errorWithDomain:MLNErrorDomain
code:errorCode
userInfo:@{
NSLocalizedFailureReasonErrorKey : @(error.message.c_str())
}];
}
@end
void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) {
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack offlineRegionStatusDidChange:status];
});
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack offlineRegionStatusDidChange:status];
});
}
void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) {
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveError:MLNErrorFromResponseError(error)];
});
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveError:MLNErrorFromResponseError(error)];
});
}
void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) {
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveMaximumAllowedMapboxTiles:limit];
});
__weak MLNOfflinePack *weakPack = pack;
dispatch_async(dispatch_get_main_queue(), ^{
[weakPack didReceiveMaximumAllowedMapboxTiles:limit];
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,42 +2,41 @@
@implementation MLNPluginLayerProperty
+(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
propertyType:(MLNPluginLayerPropertyType)propertyType
defaultValue:(id)defaultValue {
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
tempResult.propertyName = propertyName;
tempResult.propertyType = propertyType;
+ (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
propertyType:(MLNPluginLayerPropertyType)propertyType
defaultValue:(id)defaultValue {
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
tempResult.propertyName = propertyName;
tempResult.propertyType = propertyType;
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
if ([defaultValue isKindOfClass:[NSNumber class]]) {
tempResult.singleFloatDefaultValue = [defaultValue floatValue];
}
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
#if TARGET_OS_IPHONE
if ([defaultValue isKindOfClass:[UIColor class]]) {
#else
if ([defaultValue isKindOfClass:[NSColor class]]) {
#endif
tempResult.colorDefaultValue = defaultValue;
}
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
if ([defaultValue isKindOfClass:[NSNumber class]]) {
tempResult.singleFloatDefaultValue = [defaultValue floatValue];
}
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
#if TARGET_OS_IPHONE
if ([defaultValue isKindOfClass:[UIColor class]]) {
#else
if ([defaultValue isKindOfClass:[NSColor class]]) {
#endif
tempResult.colorDefaultValue = defaultValue;
}
}
return tempResult;
return tempResult;
}
- (id)init {
// Base class implemenation
if (self = [super init]) {
// Default setup
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
self.propertyName = @"unknown";
-(id)init {
// Base class implemenation
if (self = [super init]) {
// Default setup
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
self.propertyName = @"unknown";
// Default values for the various types
self.singleFloatDefaultValue = 1.0;
}
return self;
// Default values for the various types
self.singleFloatDefaultValue = 1.0;
}
return self;
}
@end
@@ -45,36 +44,31 @@
@implementation MLNPluginLayer
// This is the layer type in the style that is used
+(MLNPluginLayerCapabilities *)layerCapabilities {
// Base class returns the class name just to return something
// TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
return nil;
+ (MLNPluginLayerCapabilities *)layerCapabilities {
// Base class returns the class name just to return something
// TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
return nil;
}
- (void)onRenderLayer:(MLNMapView *)mapView
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
// Base class does nothing
// Base class does nothing
}
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
// Base class does nothing
// Base class does nothing
}
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// Base class does nothing
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
// Base class does nothing
}
/// Added to a map view
- (void)didMoveToMapView:(MLNMapView *)mapView {
// Base class does nothing
// Base class does nothing
}
@end
@implementation MLNPluginLayerCapabilities
@end

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,128 +1,125 @@
#import "MLNPolyline_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNMultiPoint_Private.h"
#import "MLNFeature.h"
#import "MLNLoggingConfiguration_Private.h"
#import <mbgl/util/geojson.hpp>
#import <mapbox/polylabel.hpp>
#import <mbgl/util/geojson.hpp>
@implementation MLNPolyline
@dynamic overlayBounds;
+ (BOOL)supportsSecureCoding
{
return YES;
+ (BOOL)supportsSecureCoding {
return YES;
}
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords
count:(NSUInteger)count
{
return [[self alloc] initWithCoordinates:coords count:count];
count:(NSUInteger)count {
return [[self alloc] initWithCoordinates:coords count:count];
}
- (mbgl::LineString<double>)lineString {
NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;
NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;
mbgl::LineString<double> geometry;
geometry.reserve(self.pointCount);
for (NSUInteger i = 0; i < count; i++) {
geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
}
mbgl::LineString<double> geometry;
geometry.reserve(self.pointCount);
for (NSUInteger i = 0; i < count; i++) {
geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
}
return geometry;
return geometry;
}
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate {
mbgl::LineAnnotation annotation { [self lineString] };
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
annotation.color = { [delegate strokeColorForShapeAnnotation:self] };
annotation.width = { static_cast<float>([delegate lineWidthForPolylineAnnotation:self]) };
- (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
mbgl::LineAnnotation annotation{[self lineString]};
annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
annotation.color = {[delegate strokeColorForShapeAnnotation:self]};
annotation.width = {static_cast<float>([delegate lineWidthForPolylineAnnotation:self])};
return annotation;
return annotation;
}
- (mbgl::Geometry<double>)geometryObject {
return [self lineString];
return [self lineString];
}
- (NSDictionary *)geoJSONDictionary {
return @{@"type": @"LineString",
@"coordinates": self.mgl_coordinates};
return @{@"type" : @"LineString", @"coordinates" : self.mgl_coordinates};
}
- (NSArray<id> *)mgl_coordinates {
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
for (NSUInteger index = 0; index < self.pointCount; index++) {
CLLocationCoordinate2D coordinate = self.coordinates[index];
[coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
}
return [coordinates copy];
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
for (NSUInteger index = 0; index < self.pointCount; index++) {
CLLocationCoordinate2D coordinate = self.coordinates[index];
[coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
}
return [coordinates copy];
}
- (BOOL)isEqual:(id)other {
return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]);
return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]);
}
- (CLLocationCoordinate2D)coordinate {
NSUInteger count = self.pointCount;
MLNAssert(count > 0, @"Polyline must have coordinates");
NSUInteger count = self.pointCount;
MLNAssert(count > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D *coordinates = self.coordinates;
CLLocationDistance middle = [self length] / 2.0;
CLLocationDistance traveled = 0.0;
CLLocationCoordinate2D *coordinates = self.coordinates;
CLLocationDistance middle = [self length] / 2.0;
CLLocationDistance traveled = 0.0;
if (count > 1 || middle > traveled) {
for (NSUInteger i = 0; i < count; i++) {
if (count > 1 || middle > traveled) {
for (NSUInteger i = 0; i < count; i++) {
// Avoid a heap buffer overflow when there are only two coordinates.
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
// Avoid a heap buffer overflow when there are only two coordinates.
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
MLNRadianCoordinate2D to =
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
MLNRadianCoordinate2D to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
if (traveled >= middle) {
double overshoot = middle - traveled;
if (overshoot == 0) {
return coordinates[i];
}
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180;
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(from,
overshoot/mbgl::util::EARTH_RADIUS_M,
MLNRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
MLNDegreesFromRadians(otherCoordinate.longitude));
}
traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
if (traveled >= middle) {
double overshoot = middle - traveled;
if (overshoot == 0) {
return coordinates[i];
}
}
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
CLLocationDirection direction = [self direction:from to:to] - 180;
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(
from, overshoot / mbgl::util::EARTH_RADIUS_M, MLNRadiansFromDegrees(direction));
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
MLNDegreesFromRadians(otherCoordinate.longitude));
}
return coordinates[count - 1];
traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
}
}
return coordinates[count - 1];
}
- (CLLocationDistance)length
{
CLLocationDistance length = 0.0;
- (CLLocationDistance)length {
CLLocationDistance length = 0.0;
NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;
NSUInteger count = self.pointCount;
CLLocationCoordinate2D *coordinates = self.coordinates;
for (NSUInteger i = 0; i < count - 1; i++) {
length += (MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinateFromLocationCoordinate(coordinates[i]), MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * mbgl::util::EARTH_RADIUS_M);
}
for (NSUInteger i = 0; i < count - 1; i++) {
length += (MLNDistanceBetweenRadianCoordinates(
MLNRadianCoordinateFromLocationCoordinate(coordinates[i]),
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) *
mbgl::util::EARTH_RADIUS_M);
}
return length;
return length;
}
- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to
{
return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to));
- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to {
return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to));
}
@end
@@ -134,112 +131,112 @@
@end
@implementation MLNMultiPolyline {
MLNCoordinateBounds _overlayBounds;
MLNCoordinateBounds _overlayBounds;
}
@synthesize overlayBounds = _overlayBounds;
+ (instancetype)multiPolylineWithPolylines:(NSArray<MLNPolyline *> *)polylines {
return [[self alloc] initWithPolylines:polylines];
return [[self alloc] initWithPolylines:polylines];
}
- (instancetype)initWithPolylines:(NSArray<MLNPolyline *> *)polylines {
MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count);
if (self = [super init]) {
_polylines = polylines;
MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count);
if (self = [super init]) {
_polylines = polylines;
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
return self;
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) {
NSSet<Class> *polylinesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolyline class]]];
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
MLNLogInfo(@"Initializing with coder.");
if (self = [super initWithCoder:decoder]) {
NSSet<Class> *polylinesClasses =
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolyline class] ]];
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
for (MLNPolyline *polyline in _polylines) {
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
}
return self;
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:_polylines forKey:@"polylines"];
[super encodeWithCoder:coder];
[coder encodeObject:_polylines forKey:@"polylines"];
}
- (BOOL)isEqual:(id)other
{
if (self == other) return YES;
if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO;
- (BOOL)isEqual:(id)other {
if (self == other) return YES;
if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO;
MLNMultiPolyline *otherMultipoline = other;
return ([super isEqual:otherMultipoline]
&& [self.polylines isEqualToArray:otherMultipoline.polylines]);
MLNMultiPolyline *otherMultipoline = other;
return ([super isEqual:otherMultipoline] &&
[self.polylines isEqualToArray:otherMultipoline.polylines]);
}
- (NSUInteger)hash {
NSUInteger hash = [super hash];
for (MLNPolyline *polyline in self.polylines) {
hash += [polyline hash];
}
return hash;
NSUInteger hash = [super hash];
for (MLNPolyline *polyline in self.polylines) {
hash += [polyline hash];
}
return hash;
}
- (CLLocationCoordinate2D)coordinate {
MLNPolyline *polyline = self.polylines.firstObject;
CLLocationCoordinate2D *coordinates = polyline.coordinates;
MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D firstCoordinate = coordinates[0];
MLNPolyline *polyline = self.polylines.firstObject;
CLLocationCoordinate2D *coordinates = polyline.coordinates;
MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
CLLocationCoordinate2D firstCoordinate = coordinates[0];
return firstCoordinate;
return firstCoordinate;
}
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
}
- (mbgl::Geometry<double>)geometryObject {
mbgl::MultiLineString<double> multiLineString;
multiLineString.reserve(self.polylines.count);
for (MLNPolyline *polyline in self.polylines) {
multiLineString.push_back([polyline lineString]);
}
return multiLineString;
mbgl::MultiLineString<double> multiLineString;
multiLineString.reserve(self.polylines.count);
for (MLNPolyline *polyline in self.polylines) {
multiLineString.push_back([polyline lineString]);
}
return multiLineString;
}
- (NSDictionary *)geoJSONDictionary {
NSMutableArray *coordinates = [NSMutableArray array];
for (MLNPolylineFeature *feature in self.polylines) {
[coordinates addObject: feature.mgl_coordinates];
}
return @{@"type": @"MultiLineString",
@"coordinates": coordinates};
NSMutableArray *coordinates = [NSMutableArray array];
for (MLNPolylineFeature *feature in self.polylines) {
[coordinates addObject:feature.mgl_coordinates];
}
return @{@"type" : @"MultiLineString", @"coordinates" : coordinates};
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self,
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle,
(unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds)];
- (NSString *)description {
return [NSString
stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
NSStringFromClass([self class]), (void *)self,
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
: self.subtitle,
(unsigned long)self.polylines.count,
MLNStringFromCoordinateBounds(self.overlayBounds)];
}
@end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,169 +1,188 @@
#import "MLNFoundation_Private.h"
#import "MLNShapeSource_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNStyle_Private.h"
#import "MLNStyleValue_Private.h"
#import "MLNMapView_Private.h"
#import "MLNSource_Private.h"
#import "MLNFeature_Private.h"
#import "MLNShape_Private.h"
#import "MLNCluster.h"
#import "MLNFeature_Private.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNMapView_Private.h"
#import "MLNShape_Private.h"
#import "MLNSource_Private.h"
#import "MLNStyleValue_Private.h"
#import "MLNStyle_Private.h"
#import "NSPredicate+MLNPrivateAdditions.h"
#import "NSURL+MLNAdditions.h"
#include <mbgl/map/map.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer";
const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius";
const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints = @"MLNShapeSourceOptionClusterMinPoints";
const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints =
@"MLNShapeSourceOptionClusterMinPoints";
const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered";
const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties = @"MLNShapeSourceOptionClusterProperties";
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel = @"MLNShapeSourceOptionMaximumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = @"MLNShapeSourceOptionMaximumZoomLevelForClustering";
const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = @"MLNShapeSourceOptionMinimumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = @"MLNShapeSourceOptionSimplificationTolerance";
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = @"MLNShapeSourceOptionLineDistanceMetrics";
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = @"MLNShapeSourceOptionSynchronousUpdate";
const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties =
@"MLNShapeSourceOptionClusterProperties";
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel =
@"MLNShapeSourceOptionMaximumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering =
@"MLNShapeSourceOptionMaximumZoomLevelForClustering";
const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel =
@"MLNShapeSourceOptionMinimumZoomLevel";
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance =
@"MLNShapeSourceOptionSimplificationTolerance";
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics =
@"MLNShapeSourceOptionLineDistanceMetrics";
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate =
@"MLNShapeSourceOptionSynchronousUpdate";
mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) {
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(
NSDictionary<MLNShapeSourceOption, id> *options) {
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
geoJSONOptions->minzoom = value.integerValue;
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
geoJSONOptions->minzoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
geoJSONOptions->maxzoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
geoJSONOptions->buffer = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
geoJSONOptions->tolerance = value.doubleValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
}
geoJSONOptions->clusterRadius = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
}
geoJSONOptions->clusterMinPoints = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
}
geoJSONOptions->clusterMaxZoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
}
geoJSONOptions->cluster = value.boolValue;
}
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
if (![value isKindOfClass:[NSDictionary<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]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
}
geoJSONOptions->maxzoom = value.integerValue;
NSEnumerator *stringEnumerator = [value keyEnumerator];
NSString *key;
while (key = [stringEnumerator nextObject]) {
NSArray *expressionsArray = value[key];
if (![expressionsArray isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be "
@"an array containing two objects."];
}
// Check that the array has 2 values. One should be a the reduce expression and one should be
// the map expression.
if ([expressionsArray count] != 2) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value requires array of "
@"two objects."];
}
// reduceExpression should be a valid NSExpression
NSExpression *reduceExpression = expressionsArray[0];
if (![reduceExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties array value requires two "
@"expression objects."];
}
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
if (!reduce) {
[NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
}
// mapExpression should be a valid NSExpression
NSExpression *mapExpression = expressionsArray[1];
if (![mapExpression isKindOfClass:[NSExpression class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterProperties member value must contain a "
@"valid NSExpression."];
}
auto map = MLNClusterPropertyFromNSExpression(mapExpression);
if (!map) {
[NSException
raise:NSInvalidArgumentException
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
}
std::string keyString = std::string([key UTF8String]);
geoJSONOptions->clusterProperties.emplace(keyString,
std::make_pair(std::move(map), std::move(reduce)));
}
}
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
}
geoJSONOptions->buffer = value.integerValue;
if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."];
}
geoJSONOptions->lineMetrics = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
}
geoJSONOptions->tolerance = value.doubleValue;
if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."];
}
geoJSONOptions->synchronousUpdate = value.boolValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
}
geoJSONOptions->clusterRadius = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
}
geoJSONOptions->clusterMinPoints = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
}
geoJSONOptions->clusterMaxZoom = value.integerValue;
}
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
if (![value isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
}
geoJSONOptions->cluster = value.boolValue;
}
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
if (![value isKindOfClass:[NSDictionary<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;
return geoJSONOptions;
}
@interface MLNShapeSource ()
@@ -175,203 +194,225 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
@implementation MLNShapeSource
- (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) {
self.URL = url;
}
return self;
- (instancetype)initWithIdentifier:(NSString *)identifier
URL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) {
self.URL = url;
}
return self;
}
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MLNShape *)shape options:(NSDictionary<MLNShapeSourceOption, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) {
if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
self.shape = shape;
- (instancetype)initWithIdentifier:(NSString *)identifier
shape:(nullable MLNShape *)shape
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
std::move(geoJSONOptions));
if (self = [super initWithPendingSource:std::move(source)]) {
if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
@"Use MLNShapeCollectionFeature to retain attributes instead."
@"This will be logged only once.");
});
}
return self;
self.shape = shape;
}
return self;
}
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NSArray<MLNShape<MLNFeature> *> *)features options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
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];
}
- (instancetype)initWithIdentifier:(NSString *)identifier
features:(NSArray<MLNShape<MLNFeature> *> *)features
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
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 {
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
return [self initWithIdentifier:identifier shape:shapeCollection options:options];
- (instancetype)initWithIdentifier:(NSString *)identifier
shapes:(NSArray<MLNShape *> *)shapes
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
return [self initWithIdentifier:identifier shape:shapeCollection options:options];
}
- (mbgl::style::GeoJSONSource *)rawSource {
return (mbgl::style::GeoJSONSource *)super.rawSource;
return (mbgl::style::GeoJSONSource *)super.rawSource;
}
- (NSURL *)URL {
MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
MLNAssertStyleSourceIsValid();
auto url = self.rawSource->getURL();
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
}
- (void)setURL:(NSURL *)url {
MLNAssertStyleSourceIsValid();
if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_shape = nil;
} else {
self.shape = nil;
}
MLNAssertStyleSourceIsValid();
if (url) {
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
_shape = nil;
} else {
self.shape = nil;
}
}
- (void)setShape:(MLNShape *)shape {
MLNAssertStyleSourceIsValid();
self.rawSource->setGeoJSON({ shape.geoJSONObject });
_shape = shape;
MLNAssertStyleSourceIsValid();
self.rawSource->setGeoJSON({shape.geoJSONObject});
_shape = shape;
}
- (NSString *)description {
if (self.rawSource) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.URL, self.shape];
}
else {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier, self.shape];
}
if (self.rawSource) {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier, self.URL, self.shape];
} else {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>",
NSStringFromClass([self class]), (void *)self,
self.identifier, self.shape];
}
}
- (NSArray<id <MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate {
MLNAssertStyleSourceIsValid();
std::optional<mbgl::style::Filter> optionalFilter;
if (predicate) {
optionalFilter = predicate.mgl_filter;
}
- (NSArray<id<MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate {
MLNAssertStyleSourceIsValid();
std::optional<mbgl::style::Filter> optionalFilter;
if (predicate) {
optionalFilter = predicate.mgl_filter;
}
std::vector<mbgl::Feature> features;
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable;
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter });
}
return MLNFeaturesFromMBGLFeatures(features);
std::vector<mbgl::Feature> features;
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
MLNMapView *mapView = (MLNMapView *)self.stylable;
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), {{}, optionalFilter});
}
return MLNFeaturesFromMBGLFeatures(features);
}
// MARK: - MLNCluster management
- (std::optional<mbgl::FeatureExtensionValue>)featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster extension:(std::string)extension options:(const std::map<std::string, mbgl::Value>)options {
MLNAssertStyleSourceIsValid();
std::optional<mbgl::FeatureExtensionValue> extensionValue;
- (std::optional<mbgl::FeatureExtensionValue>)
featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster
extension:(std::string)extension
options:(const std::map<std::string, mbgl::Value>)options {
MLNAssertStyleSourceIsValid();
std::optional<mbgl::FeatureExtensionValue> extensionValue;
// Check parameters
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);
}
// Check parameters
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;
}
- (NSArray<id <MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit {
const std::map<std::string, mbgl::Value> options = {
{ "limit", static_cast<uint64_t>(limit) },
{ "offset", static_cast<uint64_t>(offset) }
};
- (NSArray<id<MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster
offset:(NSUInteger)offset
limit:(NSUInteger)limit {
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) {
return @[];
}
if (!featureExtension) {
return @[];
}
if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[];
}
if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[];
}
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MLNFeaturesFromMBGLFeatures(leaves);
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MLNFeaturesFromMBGLFeatures(leaves);
}
- (NSArray<id <MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster {
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"children" options:{}];
- (NSArray<id<MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster {
auto featureExtension = [self featureExtensionValueOfCluster:cluster
extension:"children"
options:{}];
if (!featureExtension) {
return @[];
}
if (!featureExtension) {
return @[];
}
if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[];
}
if (!featureExtension->is<mbgl::FeatureCollection>()) {
return @[];
}
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MLNFeaturesFromMBGLFeatures(leaves);
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
return MLNFeaturesFromMBGLFeatures(leaves);
}
- (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)cluster {
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"expansion-zoom" options:{}];
if (!featureExtension) {
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);
}
auto featureExtension = [self featureExtensionValueOfCluster:cluster
extension:"expansion-zoom"
options:{}];
if (!featureExtension) {
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 {
NSString *description = feature.description;
- (void)debugRecursiveLogForFeature:(id<MLNFeature>)feature indent:(NSUInteger)indent {
NSString *description = feature.description;
// Want our recursive log on a single line
NSString *log = [description stringByReplacingOccurrencesOfString:@"\\s+"
withString:@" "
options:NSRegularExpressionSearch
range:NSMakeRange(0, description.length)];
// Want our recursive log on a single line
NSString *log =
[description stringByReplacingOccurrencesOfString:@"\\s+"
withString:@" "
options:NSRegularExpressionSearch
range:NSMakeRange(0, description.length)];
printf("%*s%s\n", (int)indent, "", log.UTF8String);
printf("%*s%s\n", (int)indent, "", log.UTF8String);
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
if (cluster) {
for (id <MLNFeature> child in [self childrenOfCluster:cluster]) {
[self debugRecursiveLogForFeature:child indent:indent + 4];
}
if (cluster) {
for (id<MLNFeature> child in [self childrenOfCluster:cluster]) {
[self debugRecursiveLogForFeature:child indent:indent + 4];
}
}
}
@end

View File

@@ -1,11 +1,11 @@
#import "MLNMapView_Private.h"
#import "MLNSource_Private.h"
#import "MLNStyle_Private.h"
#import "MLNMapView_Private.h"
#import "NSBundle+MLNAdditions.h"
#include <mbgl/style/style.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/style/source.hpp>
#include <mbgl/style/style.hpp>
const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException";
@@ -15,95 +15,102 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
// special internal source types like mbgl::AnnotationSource.
@property (nonatomic, readonly) mbgl::style::Source *rawSource;
@property (nonatomic, readonly, weak) id <MLNStylable> stylable;
@property (nonatomic, readonly, weak) id<MLNStylable> stylable;
@end
@implementation MLNSource {
std::unique_ptr<mbgl::style::Source> _pendingSource;
mapbox::base::WeakPtr<mbgl::style::Source> _weakSource;
std::unique_ptr<mbgl::style::Source> _pendingSource;
mapbox::base::WeakPtr<mbgl::style::Source> _weakSource;
}
- (instancetype)initWithIdentifier:(NSString *)identifier
{
if (self = [super init]) {
_identifier = [identifier copy];
}
return self;
- (instancetype)initWithIdentifier:(NSString *)identifier {
if (self = [super init]) {
_identifier = [identifier copy];
}
return self;
}
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource stylable:(id <MLNStylable>)stylable {
NSString *identifier = @(rawSource->getID().c_str());
if (self = [self initWithIdentifier:identifier]) {
_weakSource = rawSource->makeWeakPtr();
rawSource->peer = SourceWrapper { self };
_stylable = stylable;
}
return self;
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource
stylable:(id<MLNStylable>)stylable {
NSString *identifier = @(rawSource->getID().c_str());
if (self = [self initWithIdentifier:identifier]) {
_weakSource = rawSource->makeWeakPtr();
rawSource->peer = SourceWrapper{self};
_stylable = stylable;
}
return self;
}
- (mbgl::style::Source *)rawSource
{
return _weakSource.get();
- (mbgl::style::Source *)rawSource {
return _weakSource.get();
}
- (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource {
if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) {
_pendingSource = std::move(pendingSource);
}
return self;
if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) {
_pendingSource = std::move(pendingSource);
}
return self;
}
- (void)addToStylable:(id <MLNStylable>)stylable {
if (_pendingSource == nullptr) {
[NSException raise:MLNRedundantSourceException
format:@"This instance %@ was already added to %@. Adding the same source instance " \
"to the style more than once is invalid.", self, stylable.style];
}
- (void)addToStylable:(id<MLNStylable>)stylable {
if (_pendingSource == nullptr) {
[NSException raise:MLNRedundantSourceException
format:@"This instance %@ was already added to %@. Adding the same source instance "
"to the style more than once is invalid.",
self, stylable.style];
}
_stylable = stylable;
_stylable.style.rawStyle->addSource(std::move(_pendingSource));
_stylable = stylable;
_stylable.style.rawStyle->addSource(std::move(_pendingSource));
}
- (BOOL)removeFromStylable:(id <MLNStylable>)mapView error:(NSError * __nullable * __nullable)outError {
MLNAssertStyleSourceIsValid();
BOOL removed = NO;
- (BOOL)removeFromStylable:(id<MLNStylable>)mapView
error:(NSError *__nullable *__nullable)outError {
MLNAssertStyleSourceIsValid();
BOOL removed = NO;
if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) {
if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) {
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String);
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String);
if (removedSource) {
removed = YES;
_pendingSource = std::move(removedSource);
_stylable = nil;
} else if (outError) {
NSString *localizedDescription = [NSString stringWithFormat:
NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil, @"The source “%@” can’t be removed while it is in use.", @"User-friendly error description; first placeholder is the source’s identifier"),
self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIsInUseCannotRemove
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }];
}
if (removedSource) {
removed = YES;
_pendingSource = std::move(removedSource);
_stylable = nil;
} else if (outError) {
// TODO: Consider raising an exception here
NSString *localizedDescription = [NSString stringWithFormat:
NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil, @"The source can’t be removed because its identifier, “%@”, belongs to a different source in this style.", @"User-friendly error description"),
self.identifier];
NSString *localizedDescription = [NSString
stringWithFormat:
NSLocalizedStringWithDefaultValue(
@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil,
@"The source “%@” can’t be removed while it is in use.",
@"User-friendly error description; first placeholder is the source’s identifier"),
self.identifier];
*outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIdentifierMismatch
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }];
*outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIsInUseCannotRemove
userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
}
} else if (outError) {
// TODO: Consider raising an exception here
NSString *localizedDescription =
[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(
@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil,
@"The source can’t be removed because its identifier, “%@”, "
@"belongs to a different source in this style.",
@"User-friendly error description"),
self.identifier];
return removed;
*outError = [NSError errorWithDomain:MLNErrorDomain
code:MLNErrorCodeSourceIdentifierMismatch
userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
}
return removed;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p; identifier = %@>",
NSStringFromClass([self class]), (void *)self, self.identifier];
return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", NSStringFromClass([self class]),
(void *)self, self.identifier];
}
@end

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mb
template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType,
typename ObjCEnum = ObjCType>
class MLNStyleValueTransformer {
public:
public:
/// Convert an mbgl property value into an mgl style value
NSExpression *toExpression(const mbgl::style::PropertyValue<MBGLType> &mbglValue) {
PropertyExpressionEvaluator evaluator;
@@ -139,7 +139,7 @@ class MLNStyleValueTransformer {
return *value;
}
private: // Private utilities for converting from mgl to mbgl values
private: // Private utilities for converting from mgl to mbgl values
/**
As hack to allow converting enum => string values, we accept a second, dummy parameter in
the toRawStyleSpecValue() methods for converting 'atomic' (non-style-function) values.
@@ -307,7 +307,7 @@ class MLNStyleValueTransformer {
}
}
private: // Private utilities for converting from mbgl to mgl values
private: // Private utilities for converting from mbgl to mgl values
// Bool
static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); }
@@ -390,7 +390,7 @@ class MLNStyleValueTransformer {
/// Converts all types of mbgl property values into an equivalent NSExpression.
class PropertyExpressionEvaluator {
public:
public:
NSExpression *operator()(const mbgl::style::Undefined) const { return nil; }
NSExpression *operator()(const MBGLType &value) const {

View File

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

View File

@@ -8,164 +8,180 @@
#import "NSValue+MLNAdditions.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
#endif
#include <mbgl/util/tileset.hpp>
const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel = @"MLNTileSourceOptionMinimumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel = @"MLNTileSourceOptionMaximumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds = @"MLNTileSourceOptionCoordinateBounds";
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString = @"MLNTileSourceOptionAttributionHTMLString";
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos = @"MLNTileSourceOptionAttributionInfos";
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = @"MLNTileSourceOptionTileCoordinateSystem";
const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel =
@"MLNTileSourceOptionMinimumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel =
@"MLNTileSourceOptionMaximumZoomLevel";
const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds =
@"MLNTileSourceOptionCoordinateBounds";
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString =
@"MLNTileSourceOptionAttributionHTMLString";
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos =
@"MLNTileSourceOptionAttributionInfos";
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem =
@"MLNTileSourceOptionTileCoordinateSystem";
@implementation MLNTileSource
- (NSURL *)configurationURL {
[NSException raise:MLNAbstractClassException
format:@"MLNTileSource is an abstract class"];
return nil;
[NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
return nil;
}
- (NSArray<MLNAttributionInfo *> *)attributionInfos {
return [self attributionInfosWithFontSize:0 linkColor:nil];
return [self attributionInfosWithFontSize:0 linkColor:nil];
}
- (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor {
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
fontSize:fontSize
linkColor:linkColor];
- (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize
linkColor:(nullable MLNColor *)linkColor {
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
fontSize:fontSize
linkColor:linkColor];
}
- (NSString *)attributionHTMLString {
[NSException raise:MLNAbstractClassException
format:@"MLNTileSource is an abstract class"];
return nil;
[NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
return nil;
}
@end
mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplates, NSDictionary<MLNTileSourceOption, id> * _Nullable options) {
mbgl::Tileset tileSet;
mbgl::Tileset MLNTileSetFromTileURLTemplates(
NSArray<NSString *> *tileURLTemplates,
NSDictionary<MLNTileSourceOption, id> *_Nullable options) {
mbgl::Tileset tileSet;
for (NSString *tileURLTemplate in tileURLTemplates) {
tileSet.tiles.push_back(tileURLTemplate.UTF8String);
for (NSString *tileURLTemplate in tileURLTemplates) {
tileSet.tiles.push_back(tileURLTemplate.UTF8String);
}
// set the minimum / maximum zoom range to the values specified by this class if they
// were set. otherwise, use the core objects default values
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
}
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
}
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than "
@"MLNTileSourceOptionMaximumZoomLevel."];
}
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
if (![coordinateBounds isKindOfClass:[NSValue class]] &&
strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue "
@"containing an MLNCoordinateBounds."];
}
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
}
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
}
tileSet.attribution = attribution.UTF8String;
}
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
if (![attributionInfos isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
}
// set the minimum / maximum zoom range to the values specified by this class if they
// were set. otherwise, use the core objects default values
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
}
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
}
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
}
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than MLNTileSourceOptionMaximumZoomLevel."];
}
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
if (![coordinateBounds isKindOfClass:[NSValue class]]
&& strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue containing an MLNCoordinateBounds."];
}
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
}
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
}
tileSet.attribution = attribution.UTF8String;
}
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
if (![attributionInfos isKindOfClass:[NSArray class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
}
NSAttributedString *attributedString = [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
NSAttributedString *attributedString =
[MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
#if TARGET_OS_IPHONE
static NSString * const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
static NSString *const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
#endif
NSDictionary *documentAttributes = @{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding),
// The attribution string is meant to be a simple, inline fragment, not a full-fledged, validating document.
NSExcludedElementsDocumentAttribute: @[@"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p"],
};
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange documentAttributes:documentAttributes error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
tileSet.attribution = html.UTF8String;
}
NSDictionary *documentAttributes = @{
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
// The attribution string is meant to be a simple, inline fragment, not a full-fledged,
// validating document.
NSExcludedElementsDocumentAttribute :
@[ @"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p" ],
};
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange
documentAttributes:documentAttributes
error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
tileSet.attribution = html.UTF8String;
}
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
}
MLNTileCoordinateSystem tileCoordinateSystem;
[tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
switch (tileCoordinateSystem) {
case MLNTileCoordinateSystemXYZ:
tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
break;
case MLNTileCoordinateSystemTMS:
tileSet.scheme = mbgl::Tileset::Scheme::TMS;
break;
}
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
[NSException
raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
}
NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding];
if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
}
MLNDEMEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNDEMEncodingMapbox:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
break;
case MLNDEMEncodingTerrarium:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
break;
}
MLNTileCoordinateSystem tileCoordinateSystem;
[tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
switch (tileCoordinateSystem) {
case MLNTileCoordinateSystemXYZ:
tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
break;
case MLNTileCoordinateSystemTMS:
tileSet.scheme = mbgl::Tileset::Scheme::TMS;
break;
}
}
encodingNumber = options[MLNVectorTileSourceOptionEncoding];
if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
}
MLNVectorTileSourceEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNVectorTileSourceEncodingMapbox:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
break;
case MLNVectorTileSourceEncodingMLT:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
break;
}
NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding];
if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
}
MLNDEMEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNDEMEncodingMapbox:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
break;
case MLNDEMEncodingTerrarium:
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
break;
}
}
return tileSet;
encodingNumber = options[MLNVectorTileSourceOptionEncoding];
if (encodingNumber) {
if (![encodingNumber isKindOfClass:[NSValue class]]) {
[NSException
raise:NSInvalidArgumentException
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
}
MLNVectorTileSourceEncoding encoding;
[encodingNumber getValue:&encoding];
switch (encoding) {
case MLNVectorTileSourceEncodingMapbox:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
break;
case MLNVectorTileSourceEncodingMLT:
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
break;
}
}
return tileSet;
}

View File

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

View File

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

View File

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

View File

@@ -1,66 +1,67 @@
#import "NSArray+MLNAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
#import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "MLNLoggingConfiguration_Private.h"
@implementation NSArray (MLNAdditions)
- (std::vector<mbgl::Value>)mgl_vector {
std::vector<mbgl::Value> vector;
vector.reserve(self.count);
for (id value in self) {
if ([value isKindOfClass:[NSArray class]]) {
std::vector<mbgl::Value> innerVector = [value mgl_vector];
vector.push_back(innerVector);
} else if ([value isKindOfClass:[NSDictionary class]]) {
mbgl::PropertyMap propertyMap = [value mgl_propertyMap];
vector.push_back(propertyMap);
} else {
NSExpression *expression = [NSExpression expressionForConstantValue:value];
vector.push_back(expression.mgl_constantMBGLValue);
}
std::vector<mbgl::Value> vector;
vector.reserve(self.count);
for (id value in self) {
if ([value isKindOfClass:[NSArray class]]) {
std::vector<mbgl::Value> innerVector = [value mgl_vector];
vector.push_back(innerVector);
} else if ([value isKindOfClass:[NSDictionary class]]) {
mbgl::PropertyMap propertyMap = [value mgl_propertyMap];
vector.push_back(propertyMap);
} else {
NSExpression *expression = [NSExpression expressionForConstantValue:value];
vector.push_back(expression.mgl_constantMBGLValue);
}
return vector;
}
return vector;
}
- (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator {
NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
BOOL isSubsequentItem = NO;
for (NSAttributedString *component in self) {
MLNAssert([component isKindOfClass:[NSAttributedString class]], @"Items in array must be attributed strings.");
if (isSubsequentItem) {
[attributedString appendAttributedString:attributedSeparator];
}
isSubsequentItem = YES;
[attributedString appendAttributedString:component];
NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
BOOL isSubsequentItem = NO;
for (NSAttributedString *component in self) {
MLNAssert([component isKindOfClass:[NSAttributedString class]],
@"Items in array must be attributed strings.");
if (isSubsequentItem) {
[attributedString appendAttributedString:attributedSeparator];
}
return attributedString;
isSubsequentItem = YES;
[attributedString appendAttributedString:component];
}
return attributedString;
}
+ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector<CLLocationCoordinate2D>)coords {
NSMutableArray *coordinates = [NSMutableArray array];
for (auto coord : coords) {
[coordinates addObject:@{@"latitude": @(coord.latitude),
@"longitude": @(coord.longitude)}];
}
return coordinates;
NSMutableArray *coordinates = [NSMutableArray array];
for (auto coord : coords) {
[coordinates addObject:@{@"latitude" : @(coord.latitude), @"longitude" : @(coord.longitude)}];
}
return coordinates;
}
- (std::vector<CLLocationCoordinate2D>)mgl_coordinates {
NSUInteger numberOfCoordinates = [self count];
CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
NSUInteger numberOfCoordinates = [self count];
CLLocationCoordinate2D *coords =
(CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue],
[self[i][@"longitude"] doubleValue]);
}
for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue],
[self[i][@"longitude"] doubleValue]);
}
std::vector<CLLocationCoordinate2D> coordinates = { coords, coords + numberOfCoordinates };
free(coords);
std::vector<CLLocationCoordinate2D> coordinates = {coords, coords + numberOfCoordinates};
free(coords);
return coordinates;
return coordinates;
}
@end

View File

@@ -7,46 +7,48 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException
@implementation NSBundle (MLNAdditions)
+ (instancetype)mgl_frameworkBundle {
NSBundle *bundle = [self bundleForClass:[MLNSettings class]];
NSBundle *bundle = [self bundleForClass:[MLNSettings class]];
#ifndef MLN_CUSTOM_COMBINED_BUNDLE
if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the bundle is the containing application
// bundle but the resources are in Mapbox.bundle.
NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"];
if (bundlePath) {
bundle = [self bundleWithPath:bundlePath];
} else {
[NSException raise:MLNBundleNotFoundException
format:@"The MapLibre framework bundle could not be found. If using MapLibre iOS as a static framework, make sure that MapLibre bundle is copied into the root of the app bundle."];
}
if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
// For static frameworks, the bundle is the containing application
// bundle but the resources are in Mapbox.bundle.
NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"];
if (bundlePath) {
bundle = [self bundleWithPath:bundlePath];
} else {
[NSException raise:MLNBundleNotFoundException
format:@"The MapLibre framework bundle could not be found. If using MapLibre "
@"iOS as a static framework, make sure that MapLibre bundle is copied "
@"into the root of the app bundle."];
}
}
#endif
return bundle;
return bundle;
}
+ (nullable NSString *)mgl_frameworkBundleIdentifier {
return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"];
return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"];
}
+ (nullable NSDictionary<NSString *, id> *)mgl_frameworkInfoDictionary {
NSBundle *bundle = self.mgl_frameworkBundle;
return bundle.infoDictionary;
NSBundle *bundle = self.mgl_frameworkBundle;
return bundle.infoDictionary;
}
+ (nullable NSString *)mgl_applicationBundleIdentifier {
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \
(defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101404))
// Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a unit test bundle.
// 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool
if (!bundleIdentifier) {
bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier;
}
// Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a
// unit test bundle. 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool
if (!bundleIdentifier) {
bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier;
}
#endif
return bundleIdentifier;
return bundleIdentifier;
}
@end

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,24 @@
#import "NSDictionary+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
#import "NSArray+MLNAdditions.h"
#import "NSExpression+MLNPrivateAdditions.h"
@implementation NSDictionary (MLNAdditions)
- (mbgl::PropertyMap)mgl_propertyMap {
mbgl::PropertyMap propertyMap;
for (NSString *key in self.allKeys) {
if ([self[key] isKindOfClass:[NSDictionary class]]) {
propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap];
} else if ([self[key] isKindOfClass:[NSArray class]]) {
NSArray *array = self[key];
propertyMap[[key UTF8String]] = [array mgl_vector];
} else {
NSExpression *expression = [NSExpression expressionForConstantValue:self[key]];
propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue;
}
mbgl::PropertyMap propertyMap;
for (NSString *key in self.allKeys) {
if ([self[key] isKindOfClass:[NSDictionary class]]) {
propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap];
} else if ([self[key] isKindOfClass:[NSArray class]]) {
NSArray *array = self[key];
propertyMap[[key UTF8String]] = [array mgl_vector];
} else {
NSExpression *expression = [NSExpression expressionForConstantValue:self[key]];
propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue;
}
return propertyMap;
}
return propertyMap;
}
@end

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,76 +1,73 @@
#import "NSValue+MLNStyleAttributeAdditions.h"
#import "MLNGeometry_Private.h"
#import "MLNLight.h"
#import "MLNLoggingConfiguration_Private.h"
#import "MLNGeometry_Private.h"
#import "NSValue+MLNStyleAttributeAdditions.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
#endif
@implementation NSValue (MLNStyleAttributeAdditions)
+ (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray
{
CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]);
+ (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray {
CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]);
#if !TARGET_OS_IPHONE
// Style specification assumes an origin at the upper-left corner.
// macOS defines an origin at the lower-left corner.
vector.dy *= -1;
// Style specification assumes an origin at the upper-left corner.
// macOS defines an origin at the lower-left corner.
vector.dy *= -1;
#endif
return [NSValue value:&vector withObjCType:@encode(CGVector)];
return [NSValue value:&vector withObjCType:@encode(CGVector)];
}
+ (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray
{
// Style specification defines padding in clockwise order: top, right, bottom, left.
// Foundation defines padding in counterclockwise order: top, left, bottom, right.
MLNEdgeInsets insets = {
.top = paddingArray[0],
.right = paddingArray[1],
.bottom = paddingArray[2],
.left = paddingArray[3],
};
return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)];
+ (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray {
// Style specification defines padding in clockwise order: top, right, bottom, left.
// Foundation defines padding in counterclockwise order: top, left, bottom, right.
MLNEdgeInsets insets = {
.top = paddingArray[0],
.right = paddingArray[1],
.bottom = paddingArray[2],
.left = paddingArray[3],
};
return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)];
}
- (std::array<float, 2>)mgl_offsetArrayValue
{
MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector");
CGVector vector;
[self getValue:&vector];
- (std::array<float, 2>)mgl_offsetArrayValue {
MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector");
CGVector vector;
[self getValue:&vector];
#if !TARGET_OS_IPHONE
vector.dy *= -1;
vector.dy *= -1;
#endif
return {
static_cast<float>(vector.dx),
static_cast<float>(vector.dy),
};
return {
static_cast<float>(vector.dx),
static_cast<float>(vector.dy),
};
}
- (std::array<float, 4>)mgl_paddingArrayValue
{
MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0, @"Value does not represent an NSEdgeInsets/UIEdgeInsets");
MLNEdgeInsets insets;
[self getValue:&insets];
// Style specification defines padding in clockwise order: top, right, bottom, left.
return {
static_cast<float>(insets.top),
static_cast<float>(insets.right),
static_cast<float>(insets.bottom),
static_cast<float>(insets.left),
};
- (std::array<float, 4>)mgl_paddingArrayValue {
MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0,
@"Value does not represent an NSEdgeInsets/UIEdgeInsets");
MLNEdgeInsets insets;
[self getValue:&insets];
// Style specification defines padding in clockwise order: top, right, bottom, left.
return {
static_cast<float>(insets.top),
static_cast<float>(insets.right),
static_cast<float>(insets.bottom),
static_cast<float>(insets.left),
};
}
- (std::array<float, 3>)mgl_lightPositionArrayValue
{
MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, @"Value does not represent an MLNSphericalPosition");
MLNSphericalPosition lightPosition;
[self getValue:&lightPosition];
// Style specification defines padding in clockwise order: top, right, bottom, left.
return {
static_cast<float>(lightPosition.radial),
static_cast<float>(lightPosition.azimuthal),
static_cast<float>(lightPosition.polar),
};
- (std::array<float, 3>)mgl_lightPositionArrayValue {
MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0,
@"Value does not represent an MLNSphericalPosition");
MLNSphericalPosition lightPosition;
[self getValue:&lightPosition];
// Style specification defines padding in clockwise order: top, right, bottom, left.
return {
static_cast<float>(lightPosition.radial),
static_cast<float>(lightPosition.azimuthal),
static_cast<float>(lightPosition.polar),
};
}
@end

View File

@@ -1,12 +1,12 @@
#include <mbgl/gl/headless_backend.hpp>
#include <mbgl/util/logging.hpp>
#include <OpenGL/OpenGL.h>
#include <Foundation/NSString.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/NSString.h>
#include <OpenGL/OpenGL.h>
#include <string>
#include <stdexcept>
#include <string>
namespace mbgl {
namespace gl {
@@ -15,113 +15,113 @@ namespace gl {
// instantiating new headless rendering contexts.
class CGLDisplayConfig {
private:
// Key for singleton construction.
struct Key { explicit Key() = default; };
// Key for singleton construction.
struct Key {
explicit Key() = default;
};
public:
CGLDisplayConfig(Key) {
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
// Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when running in VMs.
kCGLPFAClosestPolicy,
kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24),
kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8),
kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(16),
kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8),
kCGLPFASupportsAutomaticGraphicsSwitching,
kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
static_cast<CGLPixelFormatAttribute>(0)
};
CGLDisplayConfig(Key) {
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
// Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when
// running in VMs.
kCGLPFAClosestPolicy, kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24), kCGLPFAAlphaSize,
static_cast<CGLPixelFormatAttribute>(8), kCGLPFADepthSize,
static_cast<CGLPixelFormatAttribute>(16), kCGLPFAStencilSize,
static_cast<CGLPixelFormatAttribute>(8), kCGLPFASupportsAutomaticGraphicsSwitching,
kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
static_cast<CGLPixelFormatAttribute>(0)};
GLint num;
// TODO: obtain all configurations and choose the best one.
const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + "\n");
}
if (num <= 0) {
throw std::runtime_error("No pixel formats found.");
}
GLint num;
// TODO: obtain all configurations and choose the best one.
const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) +
"\n");
}
if (num <= 0) {
throw std::runtime_error("No pixel formats found.");
}
}
~CGLDisplayConfig() {
const CGLError error = CGLDestroyPixelFormat(pixelFormat);
if (error != kCGLNoError) {
Log::Error(Event::OpenGL, std::string("Error destroying pixel format:") + CGLErrorString(error));
}
~CGLDisplayConfig() {
const CGLError error = CGLDestroyPixelFormat(pixelFormat);
if (error != kCGLNoError) {
Log::Error(Event::OpenGL,
std::string("Error destroying pixel format:") + CGLErrorString(error));
}
}
static std::shared_ptr<const CGLDisplayConfig> create() {
static std::weak_ptr<const CGLDisplayConfig> instance;
auto shared = instance.lock();
if (!shared) {
instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
}
return shared;
static std::shared_ptr<const CGLDisplayConfig> create() {
static std::weak_ptr<const CGLDisplayConfig> instance;
auto shared = instance.lock();
if (!shared) {
instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
}
return shared;
}
public:
CGLPixelFormatObj pixelFormat = nullptr;
CGLPixelFormatObj pixelFormat = nullptr;
};
class CGLBackendImpl final : public HeadlessBackend::Impl {
public:
CGLBackendImpl() {
CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error creating GL context object:") +
CGLErrorString(error) + "\n");
}
error = CGLEnable(glContext, kCGLCEMPEngine);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
CGLErrorString(error) + "\n");
}
CGLBackendImpl() {
CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error creating GL context object:") +
CGLErrorString(error) + "\n");
}
~CGLBackendImpl() final {
CGLDestroyContext(glContext);
error = CGLEnable(glContext, kCGLCEMPEngine);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
CGLErrorString(error) + "\n");
}
}
~CGLBackendImpl() final { CGLDestroyContext(glContext); }
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
}
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
}
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
}
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
void activateContext() final {
CGLError error = CGLSetCurrentContext(glContext);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Switching OpenGL context failed:") +
CGLErrorString(error) + "\n");
}
}
void activateContext() final {
CGLError error = CGLSetCurrentContext(glContext);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Switching OpenGL context failed:") +
CGLErrorString(error) + "\n");
}
}
void deactivateContext() final {
CGLError error = CGLSetCurrentContext(nullptr);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Removing OpenGL context failed:") +
CGLErrorString(error) + "\n");
}
void deactivateContext() final {
CGLError error = CGLSetCurrentContext(nullptr);
if (error != kCGLNoError) {
throw std::runtime_error(std::string("Removing OpenGL context failed:") +
CGLErrorString(error) + "\n");
}
}
private:
const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
CGLContextObj glContext = nullptr;
const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
CGLContextObj glContext = nullptr;
};
void HeadlessBackend::createImpl() {
assert(!impl);
impl = std::make_unique<CGLBackendImpl>();
assert(!impl);
impl = std::make_unique<CGLBackendImpl>();
}
} // namespace gl
} // namespace mbgl
} // namespace gl
} // namespace mbgl

View File

@@ -9,43 +9,39 @@ namespace gl {
class EAGLBackendImpl : public HeadlessBackend::Impl {
public:
EAGLBackendImpl() {
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (glContext == nil) {
throw std::runtime_error("Error creating GL context object");
}
glContext.multiThreaded = YES;
EAGLBackendImpl() {
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (glContext == nil) {
throw std::runtime_error("Error creating GL context object");
}
glContext.multiThreaded = YES;
}
// Required for ARC to deallocate correctly.
~EAGLBackendImpl() final = default;
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
}
// Required for ARC to deallocate correctly.
~EAGLBackendImpl() final = default;
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
}
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
if (!framework) {
throw std::runtime_error("Failed to load OpenGL framework.");
}
void activateContext() final { [EAGLContext setCurrentContext:glContext]; }
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
}
void activateContext() final {
[EAGLContext setCurrentContext:glContext];
}
void deactivateContext() final {
[EAGLContext setCurrentContext:nil];
}
void deactivateContext() final { [EAGLContext setCurrentContext:nil]; }
private:
EAGLContext* glContext = nullptr;
EAGLContext* glContext = nullptr;
};
void HeadlessBackend::createImpl() {
assert(!impl);
impl = std::make_unique<EAGLBackendImpl>();
assert(!impl);
impl = std::make_unique<EAGLBackendImpl>();
}
} // namespace gl
} // namespace mbgl
} // namespace gl
} // 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 <mbgl/util/geometry.hpp>
#import "MLNFoundation_Private.h"
#import "../../darwin/src/MLNFeature_Private.h"
#import "MLNFoundation_Private.h"
@interface MLNFeatureTests : XCTestCase
@@ -12,349 +12,390 @@
@implementation MLNFeatureTests
- (void)testGeometryConversion {
std::vector<mbgl::Feature> features;
std::vector<mbgl::Feature> features;
mbgl::Point<double> point = { -90.066667, 29.95 };
features.push_back(mbgl::Feature { point });
mbgl::Point<double> point = {-90.066667, 29.95};
features.push_back(mbgl::Feature{point});
mbgl::LineString<double> lineString = {
{ -84.516667, 39.1 },
{ -90.066667, 29.95 },
};
features.push_back(mbgl::Feature { lineString });
mbgl::LineString<double> lineString = {
{-84.516667, 39.1},
{-90.066667, 29.95},
};
features.push_back(mbgl::Feature{lineString});
mbgl::Polygon<double> polygon = {
{
{ 1, 1 },
{ 4, 1 },
{ 4, 4 },
{ 1, 4 },
},
{
{ 2, 2 },
{ 3, 2 },
{ 3, 3 },
{ 2, 3 },
},
};
features.push_back(mbgl::Feature { polygon });
mbgl::Polygon<double> polygon = {
{
{1, 1},
{4, 1},
{4, 4},
{1, 4},
},
{
{2, 2},
{3, 2},
{3, 3},
{2, 3},
},
};
features.push_back(mbgl::Feature{polygon});
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes");
NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes");
MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0];
XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:pointShape.coordinate],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0];
XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]);
XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:pointShape.coordinate],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]);
XCTAssertEqual(polylineShape.pointCount, 2UL);
CLLocationCoordinate2D polylineCoordinates[2];
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]);
XCTAssertEqual(polylineShape.pointCount, 2UL);
CLLocationCoordinate2D polylineCoordinates[2];
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
XCTAssertEqualObjects(
[NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]);
XCTAssertEqual(polygonShape.pointCount, 4UL);
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
XCTAssertNotEqual(polygonCoordinates, nil);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]);
NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons;
XCTAssertEqual(interiorPolygons.count, 1UL);
MLNPolygon *interiorPolygon = interiorPolygons.firstObject;
XCTAssertEqual(interiorPolygon.pointCount, 4UL);
CLLocationCoordinate2D interiorPolygonCoordinates[4];
[interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)];
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]);
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]);
XCTAssertEqual(polygonShape.pointCount, 4UL);
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
XCTAssertNotEqual(polygonCoordinates, nil);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]);
NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons;
XCTAssertEqual(interiorPolygons.count, 1UL);
MLNPolygon *interiorPolygon = interiorPolygons.firstObject;
XCTAssertEqual(interiorPolygon.pointCount, 4UL);
CLLocationCoordinate2D interiorPolygonCoordinates[4];
[interiorPolygon getCoordinates:interiorPolygonCoordinates
range:NSMakeRange(0, interiorPolygon.pointCount)];
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]);
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]],
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]);
}
- (void)testClusterGeometryConversion {
mbgl::Point<double> point = { -90.066667, 29.95 };
mbgl::Feature pointFeature { point };
pointFeature.id = { UINT64_MAX };
pointFeature.properties["cluster"] = true;
pointFeature.properties["cluster_id"] = 1ULL;
pointFeature.properties["point_count"] = 5ULL;
mbgl::Point<double> point = {-90.066667, 29.95};
mbgl::Feature pointFeature{point};
pointFeature.id = {UINT64_MAX};
pointFeature.properties["cluster"] = true;
pointFeature.properties["cluster_id"] = 1ULL;
pointFeature.properties["point_count"] = 5ULL;
id<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);
XCTAssert(cluster);
XCTAssert(cluster.clusterIdentifier == 1);
XCTAssert(cluster.clusterPointCount == 5);
id<MLNCluster> cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster);
XCTAssert(cluster);
XCTAssert(cluster.clusterIdentifier == 1);
XCTAssert(cluster.clusterPointCount == 5);
XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]);
XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]);
}
- (void)testPropertyConversion {
std::vector<mbgl::Feature> features;
std::vector<mbgl::Feature> features;
mbgl::Point<double> point = { -90.066667, 29.95 };
mbgl::Feature pointFeature { point };
pointFeature.id = { UINT64_MAX };
pointFeature.properties["null"] = mapbox::feature::null_value;
pointFeature.properties["bool"] = true;
pointFeature.properties["unsigned int"] = UINT64_MAX;
pointFeature.properties["int"] = INT64_MIN;
pointFeature.properties["double"] = DBL_MAX;
pointFeature.properties["string"] = std::string("🚏");
std::vector<bool> vector;
vector.push_back(true);
vector.push_back(false);
vector.push_back(true);
features.push_back(pointFeature);
mbgl::Point<double> point = {-90.066667, 29.95};
mbgl::Feature pointFeature{point};
pointFeature.id = {UINT64_MAX};
pointFeature.properties["null"] = mapbox::feature::null_value;
pointFeature.properties["bool"] = true;
pointFeature.properties["unsigned int"] = UINT64_MAX;
pointFeature.properties["int"] = INT64_MIN;
pointFeature.properties["double"] = DBL_MAX;
pointFeature.properties["string"] = std::string("🚏");
std::vector<bool> vector;
vector.push_back(true);
vector.push_back(false);
vector.push_back(true);
features.push_back(pointFeature);
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes");
NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes");
MLNShape <MLNFeature> *shape = shapes.firstObject;
XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]);
XCTAssertTrue([shape isKindOfClass:[MLNShape class]]);
MLNShape<MLNFeature> *shape = shapes.firstObject;
XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]);
XCTAssertTrue([shape isKindOfClass:[MLNShape class]]);
NSNumber *identifier = shape.identifier;
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]], @"Feature identifier should be NSNumber");
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0, @"Feature identifier should be 64-bit unsigned integer");
NSNumber *identifier = shape.identifier;
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]],
@"Feature identifier should be NSNumber");
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0,
@"Feature identifier should be 64-bit unsigned integer");
NSNull *null = [shape attributeForKey:@"null"];
XCTAssertNotNil(null);
XCTAssertTrue([null isKindOfClass:[NSNull class]]);
XCTAssertEqual(null, shape.attributes[@"null"]);
NSNull *null = [shape attributeForKey:@"null"];
XCTAssertNotNil(null);
XCTAssertTrue([null isKindOfClass:[NSNull class]]);
XCTAssertEqual(null, shape.attributes[@"null"]);
NSNumber *boolean = [shape attributeForKey:@"bool"];
XCTAssertNotNil(boolean);
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber");
XCTAssertTrue(boolean.boolValue);
XCTAssertEqual(boolean, shape.attributes[@"bool"]);
NSNumber *boolean = [shape attributeForKey:@"bool"];
XCTAssertNotNil(boolean);
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0,
@"Boolean property should be converted to bool NSNumber");
XCTAssertTrue(boolean.boolValue);
XCTAssertEqual(boolean, shape.attributes[@"bool"]);
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
XCTAssertNotNil(unsignedInteger);
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0, @"Unsigned integer property should be converted to unsigned long long NSNumber");
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
XCTAssertNotNil(unsignedInteger);
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0,
@"Unsigned integer property should be converted to unsigned long long NSNumber");
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
NSNumber *integer = [shape attributeForKey:@"int"];
XCTAssertNotNil(integer);
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0, @"Integer property should be converted to long long NSNumber");
XCTAssertEqual(integer.longLongValue, INT64_MIN);
XCTAssertEqual(integer, shape.attributes[@"int"]);
NSNumber *integer = [shape attributeForKey:@"int"];
XCTAssertNotNil(integer);
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0,
@"Integer property should be converted to long long NSNumber");
XCTAssertEqual(integer.longLongValue, INT64_MIN);
XCTAssertEqual(integer, shape.attributes[@"int"]);
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
XCTAssertNotNil(floatingPointNumber);
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0, @"Floating-point number property should be converted to double NSNumber");
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
XCTAssertNotNil(floatingPointNumber);
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0,
@"Floating-point number property should be converted to double NSNumber");
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
NSString *string = [shape attributeForKey:@"string"];
XCTAssertNotNil(string);
XCTAssertTrue([string isKindOfClass:[NSString class]]);
XCTAssertEqualObjects(string, @"🚏");
XCTAssertEqual(string, shape.attributes[@"string"]);
NSString *string = [shape attributeForKey:@"string"];
XCTAssertNotNil(string);
XCTAssertTrue([string isKindOfClass:[NSString class]]);
XCTAssertEqualObjects(string, @"🚏");
XCTAssertEqual(string, shape.attributes[@"string"]);
}
- (void)testPointFeatureGeoJSONDictionary {
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D coordinate = { 10, 10 };
pointFeature.coordinate = coordinate;
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D coordinate = {10, 10};
pointFeature.coordinate = coordinate;
// A GeoJSON feature
// when there are no identifier or properties
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
// A GeoJSON feature
// when there are no identifier or properties
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
// it has the correct type
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"Point",
@"coordinates": @[@(coordinate.longitude), @(coordinate.latitude)]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has no "id" key (or value)
XCTAssertNil(geoJSONFeature[@"id"]);
// it has a null representation of the properties object
XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{});
// it has the correct type
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
// it has the correct geometry
NSDictionary *expectedGeometry =
@{@"type" : @"Point", @"coordinates" : @[ @(coordinate.longitude), @(coordinate.latitude) ]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has no "id" key (or value)
XCTAssertNil(geoJSONFeature[@"id"]);
// it has a null representation of the properties object
XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{});
// when there is a string identifier
pointFeature.identifier = @"string-id";
// when there is a string identifier
pointFeature.identifier = @"string-id";
// it has the identifier in the result
geoJSONFeature = [pointFeature geoJSONDictionary];
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
// it has the identifier in the result
geoJSONFeature = [pointFeature geoJSONDictionary];
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
// when there are properties
pointFeature.attributes = @{@"name": @"name-value"};
// when there are properties
pointFeature.attributes = @{@"name" : @"name-value"};
// it has the properties value in the result
geoJSONFeature = [pointFeature geoJSONDictionary];
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
// it has the properties value in the result
geoJSONFeature = [pointFeature geoJSONDictionary];
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
}
- (void)testPolylineFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coords[] = { coord1, coord2 };
MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coords[] = {coord1, coord2};
MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"LineString",
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"LineString",
@"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
- (void)testPolygonFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coord3 = { 0, 0 };
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3];
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"Polygon",
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"Polygon",
@"coordinates" : @[ @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
] ]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
- (void)testMultiPolylineFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coord3 = { 0, 0 };
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3];
MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3];
MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3];
MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3];
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polyLine1, polyLine2]];
MLNMultiPolylineFeature *multiPolylineFeature =
[MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polyLine1, polyLine2 ]];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiLineString",
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]],
@[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"MultiLineString",
@"coordinates" : @[
@[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
],
@[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
- (void)testMultiPolygonFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coord3 = { 0, 0 };
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3];
MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3];
MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3];
MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3];
MLNMultiPolygonFeature *multiPolylineFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygon1, polygon2]];
MLNMultiPolygonFeature *multiPolylineFeature =
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygon1, polygon2 ]];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiPolygon",
@"coordinates": @[
@[@[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]],
@[@[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"MultiPolygon",
@"coordinates" : @[
@[ @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
] ],
@[ @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
] ]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
- (void)testPointCollectionFeatureGeoJSONDictionary {
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coord3 = { 0, 0 };
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coord3 = {0, 0};
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
MLNPointCollectionFeature *pointCollectionFeature =
[MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"MultiPoint",
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)],
@[@(coord3.longitude), @(coord3.latitude)]]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// A GeoJSON feature
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"MultiPoint",
@"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
@[ @(coord3.longitude), @(coord3.latitude) ]
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
- (void)testShapeCollectionFeatureGeoJSONDictionary {
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D pointCoordinate = { 10, 10 };
pointFeature.coordinate = pointCoordinate;
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
CLLocationCoordinate2D pointCoordinate = {10, 10};
pointFeature.coordinate = pointCoordinate;
CLLocationCoordinate2D coord1 = { 0, 0 };
CLLocationCoordinate2D coord2 = { 10, 10 };
CLLocationCoordinate2D coords[] = { coord1, coord2 };
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
CLLocationCoordinate2D coord1 = {0, 0};
CLLocationCoordinate2D coord2 = {10, 10};
CLLocationCoordinate2D coords[] = {coord1, coord2};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]];
MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[ pointFeature, polylineFeature ]];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
// A GeoJSON feature
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
// it has the correct geometry
NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection",
@"geometries": @[
@{ @"geometry": @{@"type": @"Point",
@"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]},
@"properties": @{},
@"type": @"Feature",
},
@{ @"geometry": @{@"type": @"LineString",
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
@[@(coord2.longitude), @(coord2.latitude)]]},
@"properties": @{},
@"type": @"Feature",
}
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct geometry
NSDictionary *expectedGeometry = @{
@"type" : @"GeometryCollection",
@"geometries" : @[
@{
@"geometry" : @{
@"type" : @"Point",
@"coordinates" : @[ @(pointCoordinate.longitude), @(pointCoordinate.latitude) ]
},
@"properties" : @{},
@"type" : @"Feature",
},
@{
@"geometry" : @{
@"type" : @"LineString",
@"coordinates" : @[
@[ @(coord1.longitude), @(coord1.latitude) ],
@[ @(coord2.longitude), @(coord2.latitude) ]
]
},
@"properties" : @{},
@"type" : @"Feature",
}
]
};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// When the shape collection is created with an empty array of shapes
shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
// When the shape collection is created with an empty array of shapes
shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
// it has the correct (empty) geometry
geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
expectedGeometry = @{@"type": @"GeometryCollection",
@"geometries": @[]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
// it has the correct (empty) geometry
geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
expectedGeometry = @{@"type" : @"GeometryCollection", @"geometries" : @[]};
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
}
@end

View File

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

View File

@@ -11,56 +11,83 @@
@implementation MLNHeatmapColorTests
- (void)testProperties {
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
shape:feature
options:nil];
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID"
source:source];
auto rawLayer = static_cast<mbgl::style::HeatmapLayer*>(layer.rawLayer);
auto rawLayer = static_cast<mbgl::style::HeatmapLayer *>(layer.rawLayer);
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"heatmap-color should be unset initially.");
NSExpression *defaultExpression = layer.heatmapColor;
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"heatmap-color should be unset initially.");
NSExpression *defaultExpression = layer.heatmapColor;
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
layer.heatmapColor = constantExpression;
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
layer.heatmapColor = constantExpression;
mbgl::style::PropertyValue<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 };
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
@"Setting heatmapColor to a constant value expression should update heatmap-color.");
XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
@"heatmapColor should round-trip constant value expressions.");
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
NSExpression *constantExpression2 =
[NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
#if TARGET_OS_IPHONE
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
XCTExpectFailure(
@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
#endif
NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", constantExpression, @{@12: constantExpression2}];
layer.heatmapColor = functionExpression;
NSExpression *functionExpression =
[NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)",
constantExpression, @{@12 : constantExpression2}];
layer.heatmapColor = functionExpression;
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
XCTAssertEqualObjects(layer.heatmapColor, functionExpression,
@"heatmapColor should round-trip expressions depending on $heatmapDensity.");
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should "
@"update heatmap-color.");
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
@"Setting heatmapColor to an expression depending on $heatmapDensity should "
@"update heatmap-color.");
XCTExpectFailure(
@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
XCTAssertEqualObjects(
layer.heatmapColor, functionExpression,
@"heatmapColor should round-trip expressions depending on $heatmapDensity.");
layer.heatmapColor = nil;
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"Unsetting heatmapColor should return heatmap-color to the default value.");
// The contained colors aren’t object equal, even though their descriptions are.
XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description,
@"heatmapColor should return the default value after being unset.");
layer.heatmapColor = nil;
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
@"Unsetting heatmapColor should return heatmap-color to the default value.");
// The contained colors aren’t object equal, even though their descriptions are.
XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description,
@"heatmapColor should return the default value after being unset.");
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor.");
functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a data expression is applied to heatmapColor.");
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}];
functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
functionExpression =
[NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)",
constantExpression, @{@18 : constantExpression}];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
NSInvalidArgumentException,
@"MLNHeatmapLayer should raise an exception if a camera expression "
@"is applied to heatmapColor.");
functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
NSInvalidArgumentException,
@"MLNHeatmapLayer should raise an exception if a data expression is "
@"applied to heatmapColor.");
functionExpression =
[NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression,
@{@18 : constantExpression}];
functionExpression = [NSExpression
expressionWithFormat:
@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
@{@10 : functionExpression}];
XCTAssertThrowsSpecificNamed(
layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException,
@"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a "
@"property that does not support key paths to feature attributes.");
}
@end

View File

@@ -1,14 +1,14 @@
#import <XCTest/XCTest.h>
#import <Mapbox.h>
#import <XCTest/XCTest.h>
#if TARGET_OS_IPHONE
#import "UIImage+MLNAdditions.h"
#define MLNImageResizingModeTile UIImageResizingModeTile
#define MLNImageResizingModeStretch UIImageResizingModeStretch
#import "UIImage+MLNAdditions.h"
#define MLNImageResizingModeTile UIImageResizingModeTile
#define MLNImageResizingModeStretch UIImageResizingModeStretch
#else
#import "NSImage+MLNAdditions.h"
#define MLNImageResizingModeTile NSImageResizingModeTile
#define MLNImageResizingModeStretch NSImageResizingModeStretch
#import "NSImage+MLNAdditions.h"
#define MLNImageResizingModeTile NSImageResizingModeTile
#define MLNImageResizingModeStretch NSImageResizingModeStretch
#endif
@interface MLNImageTests : XCTestCase
@@ -19,115 +19,122 @@
- (void)testStretching {
#if TARGET_OS_IPHONE
CGRect rect = CGRectMake(0, 0, 24, 24);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor);
CGContextStrokeRectWithWidth(context, rect, 2);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGRect rect = CGRectMake(0, 0, 24, 24);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor);
CGContextStrokeRectWithWidth(context, rect, 2);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
#else
NSImage *image = [NSImage imageWithSize:NSMakeSize(24, 24) flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
// A little more fanciful than the iOS version, but we aren’t testing the actual contents of the image anyways.
NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge, NSMaxXEdge};
NSImage *image = [NSImage
imageWithSize:NSMakeSize(24, 24)
flipped:NO
drawingHandler:^BOOL(NSRect dstRect) {
// A little more fanciful than the iOS version, but we aren’t testing the actual contents of
// the image anyways.
NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge,
NSMinXEdge, NSMinYEdge, NSMaxXEdge};
CGFloat grays[] = {NSBlack, NSBlack, NSWhite, NSWhite, NSDarkGray, NSDarkGray};
dstRect = NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0]));
dstRect =
NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0]));
[NSColor.grayColor set];
NSRectFill(dstRect);
return YES;
}];
image.resizingMode = NSImageResizingModeTile;
}];
image.resizingMode = NSImageResizingModeTile;
#endif
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
XCTAssert(!styleImage->getContent());
XCTAssertFalse(styleImage->isSdf());
XCTAssertTrue(styleImage->getStretchX().empty());
XCTAssertTrue(styleImage->getStretchY().empty());
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
XCTAssert(!styleImage->getContent());
XCTAssertFalse(styleImage->isSdf());
XCTAssertTrue(styleImage->getStretchX().empty());
XCTAssertTrue(styleImage->getStretchY().empty());
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile);
XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0);
}
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile);
XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0);
}
}
#if TARGET_OS_IPHONE
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch];
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero
resizingMode:UIImageResizingModeStretch];
#else
image.resizingMode = NSImageResizingModeStretch;
image.capInsets = NSEdgeInsetsZero;
image.resizingMode = NSImageResizingModeStretch;
image.capInsets = NSEdgeInsetsZero;
#endif
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
auto scale = styleImage->getPixelRatio();
XCTAssert(!styleImage->getContent());
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
auto scale = styleImage->getPixelRatio();
XCTAssert(!styleImage->getContent());
auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale));
}
auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale));
}
auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale));
}
auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale));
}
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0);
}
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 0);
XCTAssertEqual(imageAfter.capInsets.left, 0);
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
XCTAssertEqual(imageAfter.capInsets.right, 0);
}
}
#if TARGET_OS_IPHONE
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)];
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)];
#else
image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4);
image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4);
#endif
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
auto scale = styleImage->getPixelRatio();
auto content = styleImage->getContent();
XCTAssert(content);
if (content) {
XCTAssertEqual(content->top, 1 * scale);
XCTAssertEqual(content->left, 2 * scale);
XCTAssertEqual(content->bottom, 21 * scale);
XCTAssertEqual(content->right, 20 * scale);
}
{
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
XCTAssert(styleImage);
if (styleImage) {
auto scale = styleImage->getPixelRatio();
auto content = styleImage->getContent();
XCTAssert(content);
if (content) {
XCTAssertEqual(content->top, 1 * scale);
XCTAssertEqual(content->left, 2 * scale);
XCTAssertEqual(content->bottom, 21 * scale);
XCTAssertEqual(content->right, 20 * scale);
}
auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale));
}
auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale));
}
auto stretchX = styleImage->getStretchX();
XCTAssertEqual(stretchX.size(), 1UL);
if (!stretchX.empty()) {
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale));
}
auto stretchY = styleImage->getStretchY();
XCTAssertEqual(stretchY.size(), 1UL);
if (!stretchY.empty()) {
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale));
}
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 1);
XCTAssertEqual(imageAfter.capInsets.left, 2);
XCTAssertEqual(imageAfter.capInsets.bottom, 3);
XCTAssertEqual(imageAfter.capInsets.right, 4);
}
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
XCTAssertEqual(imageAfter.capInsets.top, 1);
XCTAssertEqual(imageAfter.capInsets.left, 2);
XCTAssertEqual(imageAfter.capInsets.bottom, 3);
XCTAssertEqual(imageAfter.capInsets.right, 4);
}
}
}
@end

View File

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

View File

@@ -10,49 +10,68 @@
@implementation MLNOfflinePackTests
- (void)testInvalidation {
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
XCTAssertEqual(invalidPack.state, MLNOfflinePackStateInvalid, @"Offline pack should be invalid when initialized independently of MLNOfflineStorage.");
XCTAssertEqual(
invalidPack.state, MLNOfflinePackStateInvalid,
@"Offline pack should be invalid when initialized independently of MLNOfflineStorage.");
XCTAssertThrowsSpecificNamed(invalidPack.region, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its region.");
XCTAssertThrowsSpecificNamed(invalidPack.context, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its context.");
XCTAssertThrowsSpecificNamed([invalidPack resume], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being resumed.");
XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being suspended.");
XCTAssertThrowsSpecificNamed(
invalidPack.region, NSException, MLNInvalidOfflinePackException,
@"Invalid offline pack should raise an exception when accessing its region.");
XCTAssertThrowsSpecificNamed(
invalidPack.context, NSException, MLNInvalidOfflinePackException,
@"Invalid offline pack should raise an exception when accessing its context.");
XCTAssertThrowsSpecificNamed(
[invalidPack resume], NSException, MLNInvalidOfflinePackException,
@"Invalid offline pack should raise an exception when being resumed.");
XCTAssertThrowsSpecificNamed(
[invalidPack suspend], NSException, MLNInvalidOfflinePackException,
@"Invalid offline pack should raise an exception when being suspended.");
}
- (void)testInvalidatingAnInvalidPack {
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
XCTAssertThrowsSpecificNamed([invalidPack invalidate], NSException, NSInternalInconsistencyException, @"Invalid offline pack should raise an exception when being invalidated.");
XCTAssertThrowsSpecificNamed(
[invalidPack invalidate], NSException, NSInternalInconsistencyException,
@"Invalid offline pack should raise an exception when being invalidated.");
// Now try again, without asserts
NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self];
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
// Now try again, without asserts
NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self];
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
// Make sure this doesn't crash without asserts
[invalidPack invalidate];
// Make sure this doesn't crash without asserts
[invalidPack invalidate];
[[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
[[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
}
- (void)testProgressBoxing {
MLNOfflinePackProgress progress = {
.countOfResourcesCompleted = 3,
.countOfResourcesExpected = 2,
.countOfBytesCompleted = 7,
.countOfTilesCompleted = 1,
.countOfTileBytesCompleted = 6,
.maximumResourcesExpected = UINT64_MAX,
};
MLNOfflinePackProgress roundTrippedProgress = [NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue;
MLNOfflinePackProgress progress = {
.countOfResourcesCompleted = 3,
.countOfResourcesExpected = 2,
.countOfBytesCompleted = 7,
.countOfTilesCompleted = 1,
.countOfTileBytesCompleted = 6,
.maximumResourcesExpected = UINT64_MAX,
};
MLNOfflinePackProgress roundTrippedProgress =
[NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue;
XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, @"Completed resources should round-trip.");
XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, @"Expected resources should round-trip.");
XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, @"Completed bytes should round-trip.");
XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted, @"Completed tiles should round-trip.");
XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted, @"Completed tile bytes should round-trip.");
XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, @"Maximum expected resources should round-trip.");
XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted,
@"Completed resources should round-trip.");
XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected,
@"Expected resources should round-trip.");
XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted,
@"Completed bytes should round-trip.");
XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted,
@"Completed tiles should round-trip.");
XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted,
@"Completed tile bytes should round-trip.");
XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected,
@"Maximum expected resources should round-trip.");
}
@end

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

View File

@@ -13,322 +13,390 @@
@implementation MLNShapeSourceTests
- (void)testGeoJSONOptionsFromDictionary {
NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]];
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
NSArray *clusterPropertyArray = @[reduceExpression, mapExpression];
NSDictionary *options = @{MLNShapeSourceOptionClustered: @YES,
MLNShapeSourceOptionClusterRadius: @42,
MLNShapeSourceOptionClusterMinPoints: @3,
MLNShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray},
MLNShapeSourceOptionMaximumZoomLevelForClustering: @98,
MLNShapeSourceOptionMaximumZoomLevel: @99,
MLNShapeSourceOptionBuffer: @1976,
MLNShapeSourceOptionSimplificationTolerance: @0.42,
MLNShapeSourceOptionLineDistanceMetrics: @YES,
MLNShapeSourceOptionSynchronousUpdate: @YES};
NSExpression *reduceExpression =
[NSExpression expressionForFunction:@"sum:"
arguments:@[
[NSExpression expressionForKeyPath:@"featureAccumulated"],
[NSExpression expressionForKeyPath:@"sumValue"]
]];
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
NSArray *clusterPropertyArray = @[ reduceExpression, mapExpression ];
NSDictionary *options = @{
MLNShapeSourceOptionClustered : @YES,
MLNShapeSourceOptionClusterRadius : @42,
MLNShapeSourceOptionClusterMinPoints : @3,
MLNShapeSourceOptionClusterProperties : @{@"sumValue" : clusterPropertyArray},
MLNShapeSourceOptionMaximumZoomLevelForClustering : @98,
MLNShapeSourceOptionMaximumZoomLevel : @99,
MLNShapeSourceOptionBuffer : @1976,
MLNShapeSourceOptionSimplificationTolerance : @0.42,
MLNShapeSourceOptionLineDistanceMetrics : @YES,
MLNShapeSourceOptionSynchronousUpdate : @YES
};
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
XCTAssertTrue(mbglOptions->cluster);
XCTAssertEqual(mbglOptions->clusterRadius, 42);
XCTAssertEqual(mbglOptions->clusterMaxZoom, 98);
XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL);
XCTAssertEqual(mbglOptions->maxzoom, 99);
XCTAssertEqual(mbglOptions->buffer, 1976);
XCTAssertEqual(mbglOptions->tolerance, 0.42);
XCTAssertTrue(mbglOptions->lineMetrics);
XCTAssertTrue(!mbglOptions->clusterProperties.empty());
XCTAssertTrue(mbglOptions->synchronousUpdate);
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
XCTAssertTrue(mbglOptions->cluster);
XCTAssertEqual(mbglOptions->clusterRadius, 42);
XCTAssertEqual(mbglOptions->clusterMaxZoom, 98);
XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL);
XCTAssertEqual(mbglOptions->maxzoom, 99);
XCTAssertEqual(mbglOptions->buffer, 1976);
XCTAssertEqual(mbglOptions->tolerance, 0.42);
XCTAssertTrue(mbglOptions->lineMetrics);
XCTAssertTrue(!mbglOptions->clusterProperties.empty());
XCTAssertTrue(mbglOptions->synchronousUpdate);
options = @{MLNShapeSourceOptionClustered: @"number 1"};
XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options));
options = @{MLNShapeSourceOptionClustered : @"number 1"};
XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options));
}
- (void)testNilShape {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil];
XCTAssertNil(source.shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil];
XCTAssertNil(source.shape);
}
- (void)testUnclusterableShape {
NSDictionary *options = @{
MLNShapeSourceOptionClustered: @YES,
};
NSDictionary *options = @{
MLNShapeSourceOptionClustered : @YES,
};
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:[[MLNPointFeature alloc] init] options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id"
shape:[[MLNPointFeature alloc] init]
options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]);
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options];
XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]);
}
- (void)testMLNShapeSourceWithDataMultipleFeatures {
NSString *geoJSON =
@"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": "
@"{},\"geometry\": {\"type\": \"LineString\",\"coordinates\": "
@"[[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:shape
options:nil];
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shape options:nil];
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
XCTAssertNotNil(collection);
XCTAssertEqual(collection.shapes.count, 1UL);
XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
XCTAssertNotNil(collection);
XCTAssertEqual(collection.shapes.count, 1UL);
XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
}
- (void)testMLNShapeSourceWithSingleGeometry {
NSData *data = [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil];
XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]);
NSData *data =
[@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
shape:shape
options:nil];
XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]);
}
- (void)testMLNGeoJSONSourceWithSingleFeature {
NSString *geoJSON = @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}";
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil];
XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNPointFeature *feature = (MLNPointFeature *)source.shape;
XCTAssert([feature.attributes.allKeys containsObject:@"color"]);
NSString *geoJSON =
@"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": "
@"\"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}";
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
XCTAssertNil(error);
XCTAssertNotNil(shape);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
shape:shape
options:nil];
XCTAssertNotNil(source.shape);
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
MLNPointFeature *feature = (MLNPointFeature *)source.shape;
XCTAssert([feature.attributes.allKeys containsObject:@"color"]);
}
- (void)testMLNShapeSourceWithPolylineFeatures {
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:2];
CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 0),
CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates
count:2];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polylineFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:polylineFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]);
}
- (void)testMLNShapeSourceWithPolygonFeatures {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5];
polygonFeature.identifier = @"feature-id";
NSString *stringAttribute = @"string";
NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
NSDictionary *nestedDictionaryValue = @{@"nested-key-1": @"nested-string-value"};
NSArray *arrayValue = @[@"string-value", @2];
NSDictionary *dictionaryValue = @{@"key-1": @"string-value",
@"key-2": @1,
@"key-3": nestedDictionaryValue,
@"key-4": arrayValue};
NSArray *arrayOfArrays = @[@[@1, @"string-value", @[@"jagged"]]];
NSArray *arrayOfDictionaries = @[@{@"key": @"value"}];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5];
polygonFeature.identifier = @"feature-id";
NSString *stringAttribute = @"string";
NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
NSDictionary *nestedDictionaryValue = @{@"nested-key-1" : @"nested-string-value"};
NSArray *arrayValue = @[ @"string-value", @2 ];
NSDictionary *dictionaryValue = @{
@"key-1" : @"string-value",
@"key-2" : @1,
@"key-3" : nestedDictionaryValue,
@"key-4" : arrayValue
};
NSArray *arrayOfArrays = @[ @[ @1, @"string-value", @[ @"jagged" ] ] ];
NSArray *arrayOfDictionaries = @[ @{@"key" : @"value"} ];
polygonFeature.attributes = @{@"name": stringAttribute,
@"bool": boolAttribute,
@"double": doubleAttribute,
@"dictionary-attribute": dictionaryValue,
@"array-attribute": arrayValue,
@"array-of-array-attribute": arrayOfArrays,
@"array-of-dictionary-attribute": arrayOfDictionaries};
polygonFeature.attributes = @{
@"name" : stringAttribute,
@"bool" : boolAttribute,
@"double" : doubleAttribute,
@"dictionary-attribute" : dictionaryValue,
@"array-attribute" : arrayValue,
@"array-of-array-attribute" : arrayOfArrays,
@"array-of-dictionary-attribute" : arrayOfDictionaries
};
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:polygonFeature
options:nil];
XCTAssertNotNil(source.shape);
MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape;
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]);
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"], dictionaryValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"], arrayOfArrays);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"], arrayOfDictionaries);
XCTAssertNotNil(source.shape);
MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape;
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]);
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"],
dictionaryValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"],
arrayOfArrays);
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"],
arrayOfDictionaries);
}
- (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2),
CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:polygonFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]);
}
- (void)testMLNShapeSourceWithMultiPolylineFeatures {
CLLocationCoordinate2D firstCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *firstPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2];
CLLocationCoordinate2D secondCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *secondPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2];
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[firstPolylineFeature, secondPolylineFeature]];
CLLocationCoordinate2D firstCoordinates[] = {CLLocationCoordinate2DMake(0, 0),
CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *firstPolylineFeature =
[MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2];
CLLocationCoordinate2D secondCoordinates[] = {CLLocationCoordinate2DMake(0, 0),
CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *secondPolylineFeature =
[MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2];
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature
multiPolylineWithPolylines:@[ firstPolylineFeature, secondPolylineFeature ]];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolylineFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:multiPolylineFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]);
}
- (void)testMLNShapeSourceWithMultiPolygonFeatures {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2),
CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[firstPolygon, secondPolygon]];
MLNMultiPolygonFeature *multiPolygonFeature =
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ firstPolygon, secondPolygon ]];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolygonFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:multiPolygonFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]);
}
- (void)testMLNShapeSourceWithPointFeature {
MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
shape:pointFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]);
}
- (void)testMLNShapeSourceWithPointCollectionFeature {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointCollectionFeature options:nil];
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPointCollectionFeature *pointCollectionFeature =
[MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
shape:pointCollectionFeature
options:nil];
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
XCTAssertNotNil(source.shape);
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
}
- (void)testMLNShapeSourceWithShapeCollectionFeatures {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2),
CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8),
CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
CLLocationCoordinate2D interiorCoordinates[] = {
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
CLLocationCoordinate2DMake(0.2, 100.2)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
count:5
interiorPolygons:@[ polygon ]];
CLLocationCoordinate2D coordinates_2[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2 count:2];
CLLocationCoordinate2D coordinates_2[] = {CLLocationCoordinate2DMake(0, 0),
CLLocationCoordinate2DMake(10, 10)};
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2
count:2];
MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygonFeature, polygonFeature]];
MLNMultiPolygonFeature *multiPolygonFeature =
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygonFeature, polygonFeature ]];
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polylineFeature, polylineFeature]];
MLNMultiPolylineFeature *multiPolylineFeature =
[MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polylineFeature, polylineFeature ]];
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNPointCollectionFeature *pointCollectionFeature =
[MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
MLNPointFeature *pointFeature = [MLNPointFeature new];
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, pointCollectionFeature, pointFeature]];
MLNShapeCollectionFeature *shapeCollectionFeature =
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[
polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature,
pointCollectionFeature, pointFeature
]];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shapeCollectionFeature options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shape:shapeCollectionFeature
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertNotNil(shape);
XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes");
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertNotNil(shape);
XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes");
}
- (void)testMLNShapeSourceWithFeaturesConvenienceInitializer {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
MLNPolygonFeature *polygonFeature =
[MLNPolygonFeature polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
interiorPolygons:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" features:@[polygonFeature] options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
features:@[ polygonFeature ]
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
// when a shape is included in the features array
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
// when a shape is included in the features array
MLNPolygon *polygon =
[MLNPolygon polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
interiorPolygons:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
XCTAssertThrowsSpecificNamed([[MLNShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[polygon] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer");
XCTAssertThrowsSpecificNamed(
[[MLNShapeSource alloc] initWithIdentifier:@"source-id-invalid"
features:@[ polygon ]
options:nil],
NSException, NSInvalidArgumentException,
@"Shape source should raise an exception if a shape is sent to the features initializer");
#pragma clang diagnostic pop
}
- (void)testMLNShapeSourceWithShapesConvenienceInitializer {
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0),
CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0),
CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
CLLocationCoordinate2DMake(0.0, 100.0)};
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
MLNPolygon *polygon =
[MLNPolygon polygonWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])
interiorPolygons:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shapes:@[polygon] options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
shapes:@[ polygon ]
options:nil];
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
}
@end

View File

@@ -1,13 +1,13 @@
#import <Mapbox.h>
#import "NSBundle+MLNAdditions.h"
#import "MLNVectorTileSource_Private.h"
#import "NSBundle+MLNAdditions.h"
#import <XCTest/XCTest.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#import <Cocoa/Cocoa.h>
#endif
#import <objc/runtime.h>
@@ -19,422 +19,543 @@
@end
@implementation MLNStyleTests {
XCTestExpectation *_styleLoadingExpectation;
XCTestExpectation *_styleLoadingExpectation;
}
- (void)setUp {
[super setUp];
[super setUp];
[MLNSettings useWellKnownTileServer:MLNMapTiler];
[MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"];
[MLNSettings useWellKnownTileServer:MLNMapTiler];
[MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
self.mapView.delegate = self;
if (!self.mapView.style) {
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
withExtension:@"json"];
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
self.mapView.delegate = self;
if (!self.mapView.style) {
_styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
}
- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style {
XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);
XCTAssertNotNil(mapView.style);
XCTAssertEqual(mapView.style, style);
[_styleLoadingExpectation fulfill];
[_styleLoadingExpectation fulfill];
}
- (void)tearDown {
_styleLoadingExpectation = nil;
self.mapView = nil;
_styleLoadingExpectation = nil;
self.mapView = nil;
[super tearDown];
[super tearDown];
}
// TODO: remove backed property _style
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-property-ivar"
- (MLNStyle *)style {
return self.mapView.style;
return self.mapView.style;
}
#pragma clang diagnostic pop
- (void)testName {
XCTAssertNil(self.style.name);
XCTAssertNil(self.style.name);
}
- (void)testSources {
NSSet<MLNSource *> *initialSources = self.style.sources;
if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) {
XCTAssertEqual(self.style.sources.count, 1UL);
} else {
XCTAssertEqual(self.style.sources.count, 0UL);
}
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
[self.style addSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
[self.style removeSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count);
NSSet<MLNSource *> *initialSources = self.style.sources;
if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) {
XCTAssertEqual(self.style.sources.count, 1UL);
} else {
XCTAssertEqual(self.style.sources.count, 0UL);
}
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
[self.style removeSource:shapeSource];
XCTAssertEqual(self.style.sources.count, initialSources.count);
}
- (void)testAddingSourcesTwice {
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
[self.style addSource:shapeSource];
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, MLNRedundantSourceException);
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource];
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException,
MLNRedundantSourceException);
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42];
[self.style addSource:rasterTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, MLNRedundantSourceException);
MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource"
configurationURL:[NSURL URLWithString:@".json"]
tileSize:42];
[self.style addSource:rasterTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException,
MLNRedundantSourceException);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]];
[self.style addSource:vectorTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, MLNRedundantSourceException);
MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource"
configurationURL:[NSURL URLWithString:@".json"]];
[self.style addSource:vectorTileSource];
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException,
MLNRedundantSourceException);
}
- (void)testAddingSourcesWithDuplicateIdentifiers {
MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc]
initWithIdentifier:@"my-source"
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc]
initWithIdentifier:@"my-source"
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
[self.style addSource: source1];
XCTAssertThrowsSpecificNamed([self.style addSource: source2], NSException, MLNRedundantSourceIdentifierException);
[self.style addSource:source1];
XCTAssertThrowsSpecificNamed([self.style addSource:source2], NSException,
MLNRedundantSourceIdentifierException);
}
- (void)testRemovingSourcesBeforeAddingThem {
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil];
[self.style removeSource:rasterTileSource];
[self.style addSource:rasterTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:rasterTileSource];
[self.style addSource:rasterTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil];
[self.style removeSource:shapeSource];
[self.style addSource:shapeSource];
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source"
shape:nil
options:nil];
[self.style removeSource:shapeSource];
[self.style addSource:shapeSource];
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil];
[self.style removeSource:vectorTileSource];
[self.style addSource:vectorTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:vectorTileSource];
[self.style addSource:vectorTileSource];
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
}
- (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier {
// Add a raster tile source
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
[self.style addSource:rasterTileSource];
// Add a raster tile source
MLNRasterTileSource *rasterTileSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterTileSource];
// Attempt to remove an image source with the same identifier as the raster tile source
MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"some-identifier" coordinateQuad: { } URL:[NSURL URLWithString:@"http://host/image.png"]];
[self.style removeSource:imageSource];
// The raster tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MLNRasterTileSource class]]);
// Attempt to remove an image source with the same identifier as the raster tile source
MLNImageSource *imageSource =
[[MLNImageSource alloc] initWithIdentifier:@"some-identifier"
coordinateQuad:{}
URL:[NSURL URLWithString:@"http://host/image.png"]];
[self.style removeSource:imageSource];
// The raster tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier]
isMemberOfClass:[MLNRasterTileSource class]]);
// Remove the raster tile source
[self.style removeSource:rasterTileSource];
// Remove the raster tile source
[self.style removeSource:rasterTileSource];
// Add the shape source
[self.style addSource:imageSource];
// Add the shape source
[self.style addSource:imageSource];
// Attempt to remove a vector tile source with the same identifer as the shape source
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
[self.style removeSource:vectorTileSource];
// The image source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNImageSource class]]);
// Attempt to remove a vector tile source with the same identifer as the shape source
MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style removeSource:vectorTileSource];
// The image source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
isMemberOfClass:[MLNImageSource class]]);
// Remove the image source
[self.style removeSource:imageSource];
// Remove the image source
[self.style removeSource:imageSource];
// Add the vector tile source
[self.style addSource:vectorTileSource];
// Add the vector tile source
[self.style addSource:vectorTileSource];
// Attempt to remove the previously created raster tile source that has the same identifer as the shape source
[self.style removeSource:rasterTileSource];
// The vector tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]);
// Attempt to remove the previously created raster tile source that has the same identifer as the
// shape source
[self.style removeSource:rasterTileSource];
// The vector tile source should still be added
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
isMemberOfClass:[MLNVectorTileSource class]]);
}
- (void)testRemovingSourceInUse {
// Add a raster tile source
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
[self.style addSource:vectorTileSource];
// Add a raster tile source
MLNVectorTileSource *vectorTileSource =
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
tileURLTemplates:@[]
options:nil];
[self.style addSource:vectorTileSource];
// Add a layer using it
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:vectorTileSource];
[self.style addLayer:fillLayer];
// Add a layer using it
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:vectorTileSource];
[self.style addLayer:fillLayer];
// Attempt to remove the raster tile source
NSError *error;
BOOL result = [self.style removeSource:vectorTileSource error:&error];
// Attempt to remove the raster tile source
NSError *error;
BOOL result = [self.style removeSource:vectorTileSource error:&error];
XCTAssertFalse(result);
XCTAssertEqualObjects(error.domain, MLNErrorDomain);
XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove);
XCTAssertFalse(result);
XCTAssertEqualObjects(error.domain, MLNErrorDomain);
XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove);
// Ensure it is still there
XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]);
// Ensure it is still there
XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier]
isMemberOfClass:[MLNVectorTileSource class]]);
}
- (void)testLayers {
NSArray<MLNStyleLayer *> *initialLayers = self.style.layers;
if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
XCTAssertEqual(self.style.layers.count, 1UL);
} else {
XCTAssertEqual(self.style.layers.count, 0UL);
}
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
[self.style addSource:shapeSource];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:shapeSource];
[self.style addLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
[self.style removeLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count);
NSArray<MLNStyleLayer *> *initialLayers = self.style.layers;
if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
XCTAssertEqual(self.style.layers.count, 1UL);
} else {
XCTAssertEqual(self.style.layers.count, 0UL);
}
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
[self.style addSource:shapeSource];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:shapeSource];
[self.style addLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
[self.style removeLayer:fillLayer];
XCTAssertEqual(self.style.layers.count, initialLayers.count);
}
- (void)testAddingLayersTwice {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
shape:nil
options:nil];
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
[self.style addLayer:backgroundLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, MLNRedundantLayerException);
MLNBackgroundStyleLayer *backgroundLayer =
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
[self.style addLayer:backgroundLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException,
MLNRedundantLayerException);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" source:source];
[self.style addLayer:circleLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, MLNRedundantLayerException);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer"
source:source];
[self.style addLayer:circleLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException,
MLNRedundantLayerException);
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:source];
[self.style addLayer:fillLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, MLNRedundantLayerException);
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
source:source];
[self.style addLayer:fillLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException,
MLNRedundantLayerException);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source];
[self.style addLayer:lineLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, MLNRedundantLayerException);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer"
source:source];
[self.style addLayer:lineLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException,
MLNRedundantLayerException);
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" source:source];
[self.style addLayer:rasterLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, MLNRedundantLayerException);
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer"
source:source];
[self.style addLayer:rasterLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException,
MLNRedundantLayerException);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" source:source];
[self.style addLayer:symbolLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, MLNRedundantLayerException);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer"
source:source];
[self.style addLayer:symbolLayer];
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException,
MLNRedundantLayerException);
}
- (void)testAddingLayersWithDuplicateIdentifiers {
// Just some source
MLNVectorTileSource *source = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
[self.style addSource: source];
// Just some source
MLNVectorTileSource *source = [[MLNVectorTileSource alloc]
initWithIdentifier:@"my-source"
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
[self.style addSource:source];
// Add initial layer
MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source];
[self.style addLayer:initial];
// Add initial layer
MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source];
[self.style addLayer:initial];
// Try to add the duplicate
XCTAssertThrowsSpecificNamed([self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]], NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] belowLayer:initial],NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] aboveLayer:initial], NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException");
// Try to add the duplicate
XCTAssertThrowsSpecificNamed(
[self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source]],
NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed(
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source]
belowLayer:initial],
NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed(
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source]
aboveLayer:initial],
NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed(
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
source:source]
atIndex:0],
NSException, @"MLNRedundantLayerIdentifierException");
XCTAssertThrowsSpecificNamed(
[self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"]
atIndex:0],
NSException, @"MLNRedundantLayerIdentifierException");
}
- (void)testRemovingLayerBeforeAddingSameLayer {
{
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding" shape:nil options:nil];
{
MLNShapeSource *source =
[[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding"
shape:nil
options:nil];
// Attempting to find a layer with identifier will trigger an exception if the source associated with the layer is not added
[self.style addSource:source];
// Attempting to find a layer with identifier will trigger an exception if the source associated
// with the layer is not added
[self.style addSource:source];
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:source];
[self.style removeLayer:fillLayer];
[self.style addLayer:fillLayer];
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer"
source:source];
[self.style removeLayer:fillLayer];
[self.style addLayer:fillLayer];
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
[self.style removeLayer:symbolLayer];
[self.style addLayer:symbolLayer];
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
MLNSymbolStyleLayer *symbolLayer =
[[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
[self.style removeLayer:symbolLayer];
[self.style addLayer:symbolLayer];
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source];
[self.style removeLayer:lineLayer];
[self.style addLayer:lineLayer];
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer"
source:source];
[self.style removeLayer:lineLayer];
[self.style addLayer:lineLayer];
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
[self.style removeLayer:circleLayer];
[self.style addLayer:circleLayer];
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
MLNCircleStyleLayer *circleLayer =
[[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
[self.style removeLayer:circleLayer];
[self.style addLayer:circleLayer];
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
[self.style removeLayer:backgroundLayer];
[self.style addLayer:backgroundLayer];
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
}
MLNBackgroundStyleLayer *backgroundLayer =
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
[self.style removeLayer:backgroundLayer];
[self.style addLayer:backgroundLayer];
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
}
{
MLNRasterTileSource *rasterSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil];
[self.style addSource:rasterSource];
{
MLNRasterTileSource *rasterSource =
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
tileURLTemplates:@[]
options:nil];
[self.style addSource:rasterSource];
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
[self.style removeLayer:rasterLayer];
[self.style addLayer:rasterLayer];
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
}
MLNRasterStyleLayer *rasterLayer =
[[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
[self.style removeLayer:rasterLayer];
[self.style addLayer:rasterLayer];
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
}
}
- (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier {
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" shape:nil options:nil];
[self.style addSource:source];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier"
shape:nil
options:nil];
[self.style addSource:source];
// Add a fill layer
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier" source:source];
[self.style addLayer:fillLayer];
// Add a fill layer
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier"
source:source];
[self.style addLayer:fillLayer];
// Attempt to remove a line layer with the same identifier as the fill layer
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier source:source];
[self.style removeLayer:lineLayer];
// Attempt to remove a line layer with the same identifier as the fill layer
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier
source:source];
[self.style removeLayer:lineLayer];
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] isMemberOfClass:[MLNFillStyleLayer class]]);
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier]
isMemberOfClass:[MLNFillStyleLayer class]]);
}
- (NSString *)stringWithContentsOfStyleHeader {
NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL
URLByAppendingPathComponent:@"Headers" isDirectory:YES]
URLByAppendingPathComponent:@"MLNStyle.h"];
NSError *styleHeaderError;
NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL usedEncoding:nil error:&styleHeaderError];
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
return styleHeader;
NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL
URLByAppendingPathComponent:@"Headers"
isDirectory:YES] URLByAppendingPathComponent:@"MLNStyle.h"];
NSError *styleHeaderError;
NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL
usedEncoding:nil
error:&styleHeaderError];
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
return styleHeader;
}
- (void)testImages {
NSString *imageName = @"TrackingLocationMask";
NSString *imageName = @"TrackingLocationMask";
#if TARGET_OS_IPHONE
MLNImage *image = [MLNImage imageNamed:imageName
inBundle:[NSBundle bundleForClass:[self class]]
compatibleWithTraitCollection:nil];
MLNImage *image = [MLNImage imageNamed:imageName
inBundle:[NSBundle bundleForClass:[self class]]
compatibleWithTraitCollection:nil];
#else
MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
#endif
XCTAssertNotNil(image);
XCTAssertNotNil(image);
[self.style setImage:image forName:imageName];
MLNImage *styleImage = [self.style imageForName:imageName];
[self.style setImage:image forName:imageName];
MLNImage *styleImage = [self.style imageForName:imageName];
XCTAssertNotNil(styleImage);
XCTAssertEqual(image.size.width, styleImage.size.width);
XCTAssertEqual(image.size.height, styleImage.size.height);
XCTAssertNotNil(styleImage);
XCTAssertEqual(image.size.width, styleImage.size.width);
XCTAssertEqual(image.size.height, styleImage.size.height);
}
- (void)testLayersOrder {
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"];
NSURL *url = [NSURL fileURLWithPath:filePath];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" URL:url options:nil];
[self.style addSource:source];
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam"
ofType:@"geojson"];
NSURL *url = [NSURL fileURLWithPath:filePath];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
URL:url
options:nil];
[self.style addSource:source];
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" source:source];
[self.style addLayer:layer1];
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1"
source:source];
[self.style addLayer:layer1];
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" source:source];
[self.style addLayer:layer3];
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3"
source:source];
[self.style addLayer:layer3];
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" source:source];
[self.style insertLayer:layer2 aboveLayer:layer1];
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2"
source:source];
[self.style insertLayer:layer2 aboveLayer:layer1];
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" source:source];
[self.style insertLayer:layer4 aboveLayer:layer3];
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4"
source:source];
[self.style insertLayer:layer4 aboveLayer:layer3];
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" source:source];
[self.style insertLayer:layer0 belowLayer:layer1];
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0"
source:source];
[self.style insertLayer:layer0 belowLayer:layer1];
NSArray<MLNStyleLayer *> *layers = [self.style layers];
NSUInteger startIndex = 0;
if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
startIndex++;
}
NSArray<MLNStyleLayer *> *layers = [self.style layers];
NSUInteger startIndex = 0;
if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
startIndex++;
}
XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier);
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
}
// MARK: Localization tests
- (void)testLanguageMatching {
{
NSArray *preferences = @[@"en"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[@"en-US"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[@"fr"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
}
{
NSArray *preferences = @[@"zh-Hans"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
}
{
NSArray *preferences = @[@"zh-Hans", @"en"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
}
{
NSArray *preferences = @[@"zh-Hant"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
}
{
NSArray *preferences = @[@"en", @"fr", @"el"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[@"tlh"];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
}
{
NSArray *preferences = @[@"tlh", @"en"];
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[@"mul"];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
}
{
NSArray *preferences = @[ @"en" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[ @"en-US" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[ @"fr" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
}
{
NSArray *preferences = @[ @"zh-Hans" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
}
{
NSArray *preferences = @[ @"zh-Hans", @"en" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
}
{
NSArray *preferences = @[ @"zh-Hant" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
}
{
NSArray *preferences = @[ @"en", @"fr", @"el" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[ @"tlh" ];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
}
{
NSArray *preferences = @[ @"tlh", @"en" ];
XCTAssertEqualObjects(
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
}
{
NSArray *preferences = @[ @"mul" ];
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
}
}
// MARK: Transition tests
- (void)testTransition
{
MLNTransition transitionTest = MLNTransitionMake(5, 4);
- (void)testTransition {
MLNTransition transitionTest = MLNTransitionMake(5, 4);
self.style.transition = transitionTest;
self.style.transition = transitionTest;
XCTAssert(self.style.transition.delay == transitionTest.delay);
XCTAssert(self.style.transition.duration == transitionTest.duration);
XCTAssert(self.style.transition.delay == transitionTest.delay);
XCTAssert(self.style.transition.duration == transitionTest.duration);
}
- (void)testPerformsPlacementTransitions
{
XCTAssertTrue(self.style.performsPlacementTransitions, @"The default value for enabling placement transitions should be YES.");
- (void)testPerformsPlacementTransitions {
XCTAssertTrue(self.style.performsPlacementTransitions,
@"The default value for enabling placement transitions should be YES.");
self.style.performsPlacementTransitions = NO;
XCTAssertFalse(self.style.performsPlacementTransitions, @"Enabling placement transitions should be NO.");
self.style.performsPlacementTransitions = NO;
XCTAssertFalse(self.style.performsPlacementTransitions,
@"Enabling placement transitions should be NO.");
}
- (void)testStyleJSON {
// Test getting style JSON
NSString *styleJSON = self.style.styleJSON;
XCTAssertNotNil(styleJSON, @"Style JSON should not be nil");
// Test getting style JSON
NSString *styleJSON = self.style.styleJSON;
XCTAssertNotNil(styleJSON, @"Style JSON should not be nil");
// Verify the JSON is valid
NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
XCTAssertNil(error, @"Style JSON should be valid JSON");
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], @"Style JSON should represent a dictionary");
// Verify the JSON is valid
NSError *error = nil;
id jsonObject =
[NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
XCTAssertNil(error, @"Style JSON should be valid JSON");
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]],
@"Style JSON should represent a dictionary");
}
@end

View File

@@ -1,9 +1,9 @@
#import <XCTest/XCTest.h>
#import <Mapbox.h>
#import "MLNVectorTileSource.h"
#import "MLNTileSource_Private.h"
#import "MLNGeometry_Private.h"
#import "MLNTileSource_Private.h"
#import "MLNVectorTileSource.h"
#include <mbgl/util/tileset.hpp>
@@ -14,180 +14,194 @@
@implementation MLNTileSetTests
namespace {
// a tile set that provides an mbgl tile set
NSArray *tileURLTemplates = @[@"tile.1", @"tile.2", @"tile.3"];
// a tile set that provides an mbgl tile set
NSArray *tileURLTemplates = @[ @"tile.1", @"tile.2", @"tile.3" ];
}
} // namespace
- (void)testTileSetFromTileURLTemplates {
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil);
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil);
// has the correct URL templates
XCTAssertEqual(tileSet.tiles.size(), 3UL);
XCTAssertEqual(tileSet.tiles[0], "tile.1");
XCTAssertEqual(tileSet.tiles[1], "tile.2");
XCTAssertEqual(tileSet.tiles[2], "tile.3");
// has the correct URL templates
XCTAssertEqual(tileSet.tiles.size(), 3UL);
XCTAssertEqual(tileSet.tiles[0], "tile.1");
XCTAssertEqual(tileSet.tiles[1], "tile.2");
XCTAssertEqual(tileSet.tiles[2], "tile.3");
// has the default scheme
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ);
// has the default scheme
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ);
// when the tile set has no min or max zoom level set
// the mbgl object has default values for min and max zoom level
XCTAssertEqual(tileSet.zoomRange.min, 0);
XCTAssertEqual(tileSet.zoomRange.max, 22);
// when the tile set has no min or max zoom level set
// the mbgl object has default values for min and max zoom level
XCTAssertEqual(tileSet.zoomRange.min, 0);
XCTAssertEqual(tileSet.zoomRange.max, 22);
// when the tile set has min and/or max zoom level set
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
MLNTileSourceOptionMinimumZoomLevel: @1,
MLNTileSourceOptionMaximumZoomLevel: @2,
});
// when the tile set has min and/or max zoom level set
tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionMinimumZoomLevel : @1,
MLNTileSourceOptionMaximumZoomLevel : @2,
});
// the mbgl object reflects the set values for min and max zoom level
XCTAssertEqual(tileSet.zoomRange.min, 1);
XCTAssertEqual(tileSet.zoomRange.max, 2);
// the mbgl object reflects the set values for min and max zoom level
XCTAssertEqual(tileSet.zoomRange.min, 1);
XCTAssertEqual(tileSet.zoomRange.max, 2);
// when the tile set has a bounds set
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78));
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
MLNTileSourceOptionCoordinateBounds: @(bounds),
});
// when the tile set has a bounds set
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34),
CLLocationCoordinate2DMake(56, 78));
tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionCoordinateBounds : @(bounds),
});
// the mbgl object reflects the set values for the bounds
XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
if (tileSet.bounds) {
MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
}
// the mbgl object reflects the set values for the bounds
XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
if (tileSet.bounds) {
MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
}
// when the tile set has an attribution
NSString *attribution = @"my tileset © ©️🎈";
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionHTMLString: attribution,
});
// when the tile set has an attribution
NSString *attribution = @"my tileset © ©️🎈";
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionHTMLString : attribution,
});
// the attribution is reflected by the mbgl tileset
XCTAssertEqual(tileSet.attribution, attribution.UTF8String);
// the attribution is reflected by the mbgl tileset
XCTAssertEqual(tileSet.attribution, attribution.UTF8String);
// when the tile set has attribution infos
MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc] initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
// when the tile set has attribution infos
MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc]
initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
#if TARGET_OS_IPHONE
UIColor *redColor = [UIColor redColor];
UIColor *redColor = [UIColor redColor];
#else
// CSS uses the sRGB color space.
// AppKit incorrectly uses calibrated RGB when exporting HTML, so input
// calibrated RGB to ensure round-tripping.
// <rdar://problem/46115233> <http://www.openradar.me/46115233>
NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
// CSS uses the sRGB color space.
// AppKit incorrectly uses calibrated RGB when exporting HTML, so input
// calibrated RGB to ensure round-tripping.
// <rdar://problem/46115233> <http://www.openradar.me/46115233>
NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
#endif
NSAttributedString *gl = [[NSAttributedString alloc] initWithString:@"GL" attributes:@{
NSBackgroundColorAttributeName: redColor,
}];
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionInfos: @[mapboxInfo, glInfo],
});
NSAttributedString *gl =
[[NSAttributedString alloc] initWithString:@"GL"
attributes:@{
NSBackgroundColorAttributeName : redColor,
}];
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionAttributionInfos : @[ mapboxInfo, glInfo ],
});
// the attribution is reflected by the mbgl tileset
// the attribution is reflected by the mbgl tileset
#if TARGET_OS_IPHONE
NSString *html;
if (@available(iOS 13.0, *)) {
// TODO: investigate visual impact
// iOS 13 evidently changes font size from points to pixels
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px\">"
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
} else {
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");
}
NSString *html;
if (@available(iOS 13.0, *)) {
// TODO: investigate visual impact
// 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\">"
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
@"font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
} else {
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
NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">"
@"<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");
NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">"
@"<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");
#endif
XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html);
XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html);
// when the tile coordinate system is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem: @(MLNTileCoordinateSystemTMS),
});
// when the tile coordinate system is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem : @(MLNTileCoordinateSystemTMS),
});
// the scheme is reflected by the mbgl tileset
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
// the scheme is reflected by the mbgl tileset
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
// when the tile coordinate system is changed using an NSValue
MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem: [NSValue value:&tms withObjCType:@encode(MLNTileCoordinateSystem)],
});
// when the tile coordinate system is changed using an NSValue
MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionTileCoordinateSystem : [NSValue value:&tms
withObjCType:@encode(MLNTileCoordinateSystem)],
});
// the scheme is reflected by the mbgl tileset
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
// the scheme is reflected by the mbgl tileset
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
}
- (void)testTileSetFromTileURLTemplatesRasterEncodings {
mbgl::Tileset tileSet;
mbgl::Tileset tileSet;
// when the DEM encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding: @(MLNDEMEncodingTerrarium),
});
// when the DEM encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding : @(MLNDEMEncodingTerrarium),
});
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding);
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding);
// when the raster encoding is changed using an NSValue
MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MLNDEMEncoding)],
});
// when the raster encoding is changed using an NSValue
MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNTileSourceOptionDEMEncoding : [NSValue value:&terrarium
withObjCType:@encode(MLNDEMEncoding)],
});
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding);
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
XCTAssertFalse(tileSet.vectorEncoding);
}
- (void)testTileSetFromTileURLTemplatesVectorEncodings {
mbgl::Tileset tileSet;
mbgl::Tileset tileSet;
// when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMapbox),
});
// when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMapbox),
});
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox);
XCTAssertFalse(tileSet.rasterEncoding);
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox);
XCTAssertFalse(tileSet.rasterEncoding);
// when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMLT),
});
// when the raster encoding is changed using an NSNumber
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMLT),
});
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding);
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding);
// when the raster encoding is changed using an NSValue
MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding: [NSValue value:&mlt withObjCType:@encode(MLNVectorTileSourceEncoding)],
});
// when the raster encoding is changed using an NSValue
MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT;
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
MLNVectorTileSourceOptionEncoding : [NSValue value:&mlt
withObjCType:@encode(MLNVectorTileSourceEncoding)],
});
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding);
// the encoding is reflected by the mbgl tileset
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
XCTAssertFalse(tileSet.rasterEncoding);
}
- (void)testInvalidTileSet {
// a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels throws an exception
XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
MLNTileSourceOptionMinimumZoomLevel: @10,
MLNTileSourceOptionMaximumZoomLevel: @9,
}), NSException, NSInvalidArgumentException);
// a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels
// throws an exception
XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
MLNTileSourceOptionMinimumZoomLevel : @10,
MLNTileSourceOptionMaximumZoomLevel : @9,
}),
NSException, NSInvalidArgumentException);
}
@end

View File

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

View File

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

View File

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

View File

@@ -10,129 +10,130 @@ namespace mbgl {
using namespace mtl;
class MetalRenderableResource final: public mtl::RenderableResource {
class MetalRenderableResource final : public mtl::RenderableResource {
public:
MetalRenderableResource(MetalBackend &backend):
rendererBackend(backend),
commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())),
swapchain(NS::TransferPtr(CA::MetalLayer::layer()))
{
swapchain->setDevice(backend.getDevice().get());
MetalRenderableResource(MetalBackend& backend)
: rendererBackend(backend),
commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())),
swapchain(NS::TransferPtr(CA::MetalLayer::layer())) {
swapchain->setDevice(backend.getDevice().get());
}
void setBackendSize(mbgl::Size size_) {
size = size_;
swapchain->setDrawableSize(
{static_cast<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_) {
size = size_;
swapchain->setDrawableSize({static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)});
buffersInvalid = true;
if (depthTexture) {
depthTexture->create();
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
depthTarget->setTexture(
static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
}
}
mbgl::Size getSize() const {
return size;
if (stencilTexture) {
stencilTexture->create();
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(
static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
}
}
}
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)};
void swap() override {
commandBuffer->presentDrawable(surface.get());
commandBuffer->commit();
commandBuffer.reset();
renderPassDescriptor.reset();
}
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer());
renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor());
renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture());
const mtl::RendererBackend& getBackend() const override { return rendererBackend; }
if (buffersInvalid || !depthTexture || !stencilTexture) {
buffersInvalid = false;
depthTexture = rendererBackend.getContext().createTexture2D();
depthTexture->setSize(texSize);
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
depthTexture->setSamplerConfiguration(
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(depthTexture.get())->setUsage(
MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; }
stencilTexture = rendererBackend.getContext().createTexture2D();
stencilTexture->setSize(texSize);
stencilTexture->setFormat(gfx::TexturePixelType::Stencil, gfx::TextureChannelDataType::UnsignedByte);
stencilTexture->setSamplerConfiguration(
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
static_cast<mtl::Texture2D*>(stencilTexture.get())
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
}
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
}
if (depthTexture) {
depthTexture->create();
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
depthTarget->setTexture(static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
}
}
if (stencilTexture) {
stencilTexture->create();
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
stencilTarget->setTexture(static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
}
}
}
const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
return renderPassDescriptor;
}
void swap() override {
commandBuffer->presentDrawable(surface.get());
commandBuffer->commit();
commandBuffer.reset();
renderPassDescriptor.reset();
}
const mtl::RendererBackend& getBackend() const override {
return rendererBackend;
}
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override {
return commandBuffer;
}
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
}
const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
return renderPassDescriptor;
}
const CAMetalLayerPtr& getSwapchain() const {
return swapchain;
}
const CAMetalLayerPtr& getSwapchain() const { return swapchain; }
private:
MetalBackend &rendererBackend;
MTLCommandQueuePtr commandQueue;
MTLCommandBufferPtr commandBuffer;
MTLRenderPassDescriptorPtr renderPassDescriptor;
CAMetalDrawablePtr surface;
CAMetalLayerPtr swapchain;
gfx::Texture2DPtr depthTexture;
gfx::Texture2DPtr stencilTexture;
mbgl::Size size;
bool buffersInvalid = true;
MetalBackend& rendererBackend;
MTLCommandQueuePtr commandQueue;
MTLCommandBufferPtr commandBuffer;
MTLRenderPassDescriptorPtr renderPassDescriptor;
CAMetalDrawablePtr surface;
CAMetalLayerPtr swapchain;
gfx::Texture2DPtr depthTexture;
gfx::Texture2DPtr stencilTexture;
mbgl::Size size;
bool buffersInvalid = true;
};
} // namespace mbgl
} // namespace mbgl
MetalBackend::MetalBackend(NSWindow *window):
mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique),
mbgl::gfx::Renderable(mbgl::Size{0, 0}, std::make_unique<mbgl::MetalRenderableResource>(*this))
{
window.contentView.layer = (__bridge CALayer *)getDefaultRenderable().getResource<mbgl::MetalRenderableResource>().getSwapchain().get();
window.contentView.wantsLayer = YES;
MetalBackend::MetalBackend(NSWindow* window)
: mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique),
mbgl::gfx::Renderable(mbgl::Size{0, 0},
std::make_unique<mbgl::MetalRenderableResource>(*this)) {
window.contentView.layer = (__bridge CALayer*)getDefaultRenderable()
.getResource<mbgl::MetalRenderableResource>()
.getSwapchain()
.get();
window.contentView.wantsLayer = YES;
}
mbgl::gfx::Renderable &MetalBackend::getDefaultRenderable() {
return *this;
}
mbgl::gfx::Renderable& MetalBackend::getDefaultRenderable() { return *this; }
void MetalBackend::activate() {}
void MetalBackend::deactivate() {}
void MetalBackend::updateAssumedState() {}
void MetalBackend::setSize(mbgl::Size size_) {
getResource<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 "MLNTestUtility.h"
#import "../../../darwin/src/MLNGeometry_Private.h"
#include <mbgl/map/camera.hpp>
@@ -10,90 +10,93 @@
@implementation MLNCameraTransitionFinishTests
- (void)testEaseToCompletionHandler {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Completion block should be called"];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
[self.mapView setCamera:camera
withDuration:0.0
animationTimingFunction:nil
completionHandler:^{
[self.mapView setCamera:camera
withDuration:0.0
animationTimingFunction:nil
completionHandler:^{
[expectation fulfill];
}];
}];
[self waitForExpectations:@[expectation] timeout:0.5];
[self waitForExpectations:@[ expectation ] timeout:0.5];
}
- (void)testEaseToCompletionHandlerAnimated {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Completion block should be called"];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
[self.mapView setCamera:camera
withDuration:0.3
animationTimingFunction:nil
completionHandler:^{
[self.mapView setCamera:camera
withDuration:0.3
animationTimingFunction:nil
completionHandler:^{
[expectation fulfill];
}];
}];
[self waitForExpectations:@[expectation] timeout:0.5];
[self waitForExpectations:@[ expectation ] timeout:0.5];
}
- (void)testFlyToCompletionHandler {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Completion block should be called"];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
[self.mapView flyToCamera:camera
withDuration:0.0
completionHandler:^{
[expectation fulfill];
}];
[self.mapView flyToCamera:camera
withDuration:0.0
completionHandler:^{
[expectation fulfill];
}];
[self waitForExpectations:@[expectation] timeout:0.5];
[self waitForExpectations:@[ expectation ] timeout:0.5];
}
- (void)testFlyToCompletionHandlerAnimated {
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
CLLocationCoordinate2DMake(1.0, 1.0));
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Completion block should be called"];
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
[self.mapView flyToCamera:camera
withDuration:0.3
completionHandler:^{
[expectation fulfill];
}];
[self.mapView flyToCamera:camera
withDuration:0.3
completionHandler:^{
[expectation fulfill];
}];
[self waitForExpectations:@[expectation] timeout:0.5];
[self waitForExpectations:@[ expectation ] timeout:0.5];
}
@end
// MARK: - camera transitions with NaN values
@interface MLNMapView (MLNCameraTransitionFinishNaNTests)
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets;
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
edgePadding:(UIEdgeInsets)insets;
@end
@interface MLNCameraTransitionNaNZoomMapView: MLNMapView
@interface MLNCameraTransitionNaNZoomMapView : MLNMapView
@end
@implementation MLNCameraTransitionNaNZoomMapView
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets {
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets];
options.zoom = NAN;
return options;
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
edgePadding:(UIEdgeInsets)insets {
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera
edgePadding:insets];
options.zoom = NAN;
return options;
}
@end
@@ -103,6 +106,6 @@
@implementation MLNCameraTransitionFinishNaNTests
- (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL {
return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL];
return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL];
}
@end

View File

@@ -1,6 +1,6 @@
#import "../../../darwin/src/MLNGeometry_Private.h"
#import "MLNMapViewIntegrationTest.h"
#import "MLNTestUtility.h"
#import "../../../darwin/src/MLNGeometry_Private.h"
@interface MLNCameraTransitionTests : MLNMapViewIntegrationTest
@end
@@ -8,387 +8,350 @@
@implementation MLNCameraTransitionTests
- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__weak MLNCameraTransitionTests *weakself = self;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself;
[expectation fulfill];
if (!strongSelf) return;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
dispatch_async(dispatch_get_main_queue(), ^{
[mapView resetNorth];
});
}
};
[expectation fulfill];
[self.mapView setDirection:90 animated:YES];
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
dispatch_async(dispatch_get_main_queue(), ^{
[mapView resetNorth];
});
}
};
[self.mapView setDirection:90 animated:YES];
// loop, render, and wait
[self waitForExpectations:@[expectation] timeout:10];
// loop, render, and wait
[self waitForExpectations:@[ expectation ] timeout:10];
}
- (void)testSetAndResetNorthInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__weak MLNCameraTransitionTests *weakself = self;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself;
[expectation fulfill];
if (!strongSelf) return;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
NSLog(@"Reset to north");
[mapView resetNorth];
}
};
[expectation fulfill];
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (mapView.direction != 0.0) {
NSLog(@"Reset to north");
[mapView resetNorth];
}
};
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
}
- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
// Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO];
// Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 1;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 1;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__block BOOL startedReset = NO;
__block BOOL finishedReset = NO;
__weak MLNCameraTransitionTests *weakself = self;
__block BOOL startedReset = NO;
__block BOOL finishedReset = NO;
self.regionIsChanging = ^(MLNMapView *mapView) {
MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return;
self.regionIsChanging = ^(MLNMapView *mapView) {
MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return;
if (!startedReset) {
NSLog(@"Reset to north, interrupting the previous transition");
startedReset = YES;
[mapView resetNorth];
finishedReset = YES;
}
};
if (!startedReset) {
NSLog(@"Reset to north, interrupting the previous transition");
startedReset = YES;
[mapView resetNorth];
finishedReset = YES;
}
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
if (!strongSelf) return;
MLNTestAssert(strongSelf, startedReset);
MLNTestAssert(strongSelf, startedReset);
if (finishedReset) {
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
[expectation fulfill];
} else {
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
}
};
if (finishedReset) {
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
[expectation fulfill];
}
else {
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
}
};
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
@"Camera should have reset to north. %0.3f", self.mapView.direction);
}
- (void)testSetCenterCancelsTransitions {
XCTestExpectation *cameraIsInDCExpectation = [self expectationWithDescription:@"camera reset to DC"];
XCTestExpectation *cameraIsInDCExpectation =
[self expectationWithDescription:@"camera reset to DC"];
CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487);
CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487);
CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
double zoomLevel = 15.0;
double zoomLevel = 15.0;
[self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
[self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
[self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
[self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
__weak MLNCameraTransitionTests *weakself = self;
__weak MLNCameraTransitionTests *weakself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC),
dispatch_get_main_queue(),
^{
MLNCameraTransitionTests *strongSelf = weakself;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
MLNCameraTransitionTests *strongSelf = weakself;
[strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
MLNTestAssertEqualWithAccuracy(strongSelf,
dc.latitude,
strongSelf.mapView.centerCoordinate.latitude,
0.0005,
@"setting center coordinate should cancel transitions");
MLNTestAssertEqualWithAccuracy(strongSelf,
dc.longitude,
strongSelf.mapView.centerCoordinate.longitude,
0.0005,
@"setting center coordinate should cancel transitions");
[cameraIsInDCExpectation fulfill];
});
[strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
MLNTestAssertEqualWithAccuracy(strongSelf, dc.latitude,
strongSelf.mapView.centerCoordinate.latitude, 0.0005,
@"setting center coordinate should cancel transitions");
MLNTestAssertEqualWithAccuracy(strongSelf, dc.longitude,
strongSelf.mapView.centerCoordinate.longitude, 0.0005,
@"setting center coordinate should cancel transitions");
[cameraIsInDCExpectation fulfill];
});
[self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0];
[self waitForExpectations:@[ cameraIsInDCExpectation ] timeout:10.0];
}
- (void)testSetCenterCoordinateInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__block NSInteger delegateCallCount = 0;
__weak MLNCameraTransitionTests *weakself = self;
__block NSInteger delegateCallCount = 0;
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (!strongSelf) return;
CLLocationCoordinate2D center = mapView.centerCoordinate;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
switch (delegateCallCount) {
case 0: {
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target");
CLLocationCoordinate2D center = mapView.centerCoordinate;
MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
@"center coordinate latitude should be at target");
switch(delegateCallCount) {
case 0:
{
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf,
target.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target");
// Now set another coordinate.
// Should take MLNAnimationDuration seconds (0.3s)
[mapView setCenterCoordinate:target2 animated:YES];
break;
}
MLNTestAssertEqualWithAccuracy(strongSelf,
target.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target");
case 1: {
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target2");
// Now set another coordinate.
// Should take MLNAnimationDuration seconds (0.3s)
[mapView setCenterCoordinate:target2 animated:YES];
break;
}
MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
@"center coordinate latitude should be at target2");
break;
}
case 1:
{
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2");
default:
MLNTestFail(strongSelf);
break;
}
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target2");
break;
delegateCallCount++;
}
[expectation fulfill];
};
default:
MLNTestFail(strongSelf);
break;
}
delegateCallCount++;
[expectation fulfill];
};
// Should take MLNAnimationDuration seconds (0.3)
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
// Should take MLNAnimationDuration seconds (0.3)
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
}
- (void)testFlyToCameraInDelegateMethod {
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
__weak MLNCameraTransitionTests *weakself = self;
__block NSInteger delegateCallCount = 0;
expectation.expectedFulfillmentCount = 3;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__block NSInteger delegateCallCount = 0;
expectation.expectedFulfillmentCount = 3;
expectation.assertForOverFulfill = YES;
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
__block BOOL runloop = YES;
__block BOOL runloop = YES;
NSTimeInterval stop0 = CACurrentMediaTime();
__block NSTimeInterval stop1 = 0.0;
__block NSTimeInterval stop2 = 0.0;
NSTimeInterval stop0 = CACurrentMediaTime();
__block NSTimeInterval stop1 = 0.0;
__block NSTimeInterval stop2 = 0.0;
double zoomLevel = 5.0;
double altitude =
MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
double zoomLevel = 5.0;
double altitude = MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
MLNCameraTransitionTests *strongSelf = weakself;
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
if (!strongSelf) return;
MLNCameraTransitionTests *strongSelf = weakself;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
if (!strongSelf) return;
CLLocationCoordinate2D center = mapView.centerCoordinate;
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
switch (delegateCallCount) {
case 0: {
stop1 = CACurrentMediaTime();
CLLocationCoordinate2D center = mapView.centerCoordinate;
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target");
switch(delegateCallCount) {
case 0:
{
stop1 = CACurrentMediaTime();
MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
@"center coordinate latitude should be at target");
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf,
target.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target");
// Now set another coordinate.
MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2
altitude:altitude
pitch:0.0
heading:0.0];
MLNTestAssertEqualWithAccuracy(strongSelf,
target.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target");
// flyToCamera can take a while...
[mapView flyToCamera:camera
completionHandler:^{
MLNTestAssert(strongSelf, !runloop,
@"Completion block should be called after delegate method");
[expectation fulfill];
stop2 = CACurrentMediaTime();
}];
break;
}
// Now set another coordinate.
MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2
altitude:altitude
pitch:0.0
heading:0.0];
case 1: {
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
@"center coordinate longitude should be at target2");
// flyToCamera can take a while...
[mapView flyToCamera:camera completionHandler:^{
MLNTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method");
[expectation fulfill];
stop2 = CACurrentMediaTime();
}];
break;
}
MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
@"center coordinate latitude should be at target2");
case 1:
{
// Our center coordinate should match our target (assuming we're not
// constrained by zoom level)
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.longitude,
center.longitude,
0.0005,
@"center coordinate longitude should be at target2");
runloop = NO;
break;
}
MLNTestAssertEqualWithAccuracy(strongSelf,
target2.latitude,
center.latitude,
0.0005,
@"center coordinate latitude should be at target2");
default:
MLNTestFail(strongSelf);
break;
}
runloop = NO;
break;
}
delegateCallCount++;
default:
MLNTestFail(strongSelf);
break;
}
[expectation fulfill];
};
delegateCallCount++;
// Should take MLNAnimationDuration
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
[expectation fulfill];
};
[self waitForExpectations:@[ expectation ] timeout:10];
// Should take MLNAnimationDuration
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
[self waitForExpectations:@[expectation] timeout:10];
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", (long)delegateCallCount); // Once for the setDirection and once for the reset north
XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld",
(long)delegateCallCount); // Once for the setDirection and once for the reset north
}
// MARK: - Pending tests
- (void)testContinuallyResettingNorthInIsChangingPENDING{
// See https://github.com/mapbox/mapbox-gl-native/pull/11614
// This test currently fails, unsurprisingly, since we're continually
// setting the camera to the same parameters during its update.
//
// Possible solutions/expectations:
// - If you set camera parameters that match the *current* target parameters
// then the transition could be a no-op. We'd need to consider any completion
// block
// - Ideally we would detect this case and disallow it.
- (void)testContinuallyResettingNorthInIsChangingPENDING {
// See https://github.com/mapbox/mapbox-gl-native/pull/11614
// This test currently fails, unsurprisingly, since we're continually
// setting the camera to the same parameters during its update.
//
// Possible solutions/expectations:
// - If you set camera parameters that match the *current* target parameters
// then the transition could be a no-op. We'd need to consider any completion
// block
// - Ideally we would detect this case and disallow it.
// Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO];
// Reset to non-zero, prior to testing
[self.mapView setDirection:45 animated:NO];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
self.regionIsChanging = ^(MLNMapView *mapView) {
[mapView resetNorth];
};
self.regionIsChanging = ^(MLNMapView *mapView) {
[mapView resetNorth];
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill];
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill];
};
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
[self.mapView setDirection:90 animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
@"Camera should have reset to north. %0.3f", self.mapView.direction);
}
- (void)testContinuallySettingCoordinateInIsChangingPENDING {
// See above comment in `-testContinuallyResettingNorthInIsChangingPENDING`
// See above comment in `-testContinuallyResettingNorthInIsChangingPENDING`
// Reset to non-zero, prior to testing
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
// Reset to non-zero, prior to testing
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
expectation.expectedFulfillmentCount = 2;
expectation.assertForOverFulfill = YES;
__weak MLNCameraTransitionTests *weakself = self;
__weak MLNCameraTransitionTests *weakself = self;
self.regionIsChanging = ^(MLNMapView *mapView) {
[weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
};
self.regionIsChanging = ^(MLNMapView *mapView) {
[weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill];
};
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
[expectation fulfill];
};
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
[self waitForExpectations:@[expectation] timeout:10];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
[self waitForExpectations:@[ expectation ] timeout:10];
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
@"Camera should have reset to north. %0.3f", self.mapView.direction);
}
@end

View File

@@ -1,6 +1,6 @@
#import "MLNMapView+Impl.h"
#import "MLNMapViewIntegrationTest.h"
#import "MLNMapView_Private.h"
#import "MLNMapView+Impl.h"
#include <mbgl/gfx/renderable.hpp>
@@ -12,240 +12,247 @@
// MARK: - Tests
- (void)waitForMapViewToBeRendered {
[self waitForMapViewToBeRenderedWithTimeout:10];
[self waitForMapViewToBeRenderedWithTimeout:10];
}
// This test does not strictly need to be in this test file/target. Including here for convenience.
- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
__weak id weakLayer = layer;
layer = nil;
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
__weak id weakLayer = layer;
layer = nil;
XCTAssertNil(weakLayer);
XCTAssertNil(weakLayer);
}
- (void)testAddingRemovingOpenGLLayerWithoutRendering {
XCTAssertNotNil(self.style);
XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{
__weak id weakLayer = nil;
void (^addRemoveGLLayer)(void) = ^{
__weak id weakLayer = nil;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
weakLayer = layer;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
weakLayer = layer;
// Nil the layer prior to remove to ensure it's being retained
layer = nil;
[self.style removeLayer:weakLayer];
}
// Nil the layer prior to remove to ensure it's being retained
layer = nil;
[self.style removeLayer:weakLayer];
}
XCTAssertNil(weakLayer);
};
XCTAssertNil(weakLayer);
};
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
}
- (void)testReusingOpenGLLayerIdentifier {
__weak MLNCustomStyleLayer *weakLayer2;
__weak MLNCustomStyleLayer *weakLayer2;
@autoreleasepool {
MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer1 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer1];
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer2 = layer2;
XCTAssertNotNil(layer2);
XCTAssert(layer1 != layer2);
[self.style insertLayer:layer2 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer2];
XCTAssertNil([layer1 style]);
XCTAssertNil([layer2 style]);
}
// At this point, layer2 (and layer1) should still be around,
// since the render process needs to keep a reference to them.
XCTAssertNotNil(weakLayer2);
// Let render loop run enough to release the layers
@autoreleasepool {
MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer1 atIndex:0];
[self waitForMapViewToBeRendered];
XCTAssertNil(weakLayer2);
[self.style removeLayer:layer1];
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer2 = layer2;
XCTAssertNotNil(layer2);
XCTAssert(layer1 != layer2);
[self.style insertLayer:layer2 atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer2];
XCTAssertNil([layer1 style]);
XCTAssertNil([layer2 style]);
}
// At this point, layer2 (and layer1) should still be around,
// since the render process needs to keep a reference to them.
XCTAssertNotNil(weakLayer2);
// Let render loop run enough to release the layers
[self waitForMapViewToBeRendered];
XCTAssertNil(weakLayer2);
}
- (void)testAddingRemovingOpenGLLayer {
XCTAssertNotNil(self.style);
XCTAssertNotNil(self.style);
void(^addRemoveGLLayer)(void) = ^{
void (^addRemoveGLLayer)(void) = ^{
__weak id retrievedLayer = nil;
__weak id retrievedLayer = nil;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
[self waitForMapViewToBeRendered];
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
XCTAssertNotNil(retrievedLayer);
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
XCTAssertNotNil(retrievedLayer);
[self.style removeLayer:retrievedLayer];
[self waitForMapViewToBeRendered];
}
[self.style removeLayer:retrievedLayer];
[self waitForMapViewToBeRendered];
}
XCTAssertNil(retrievedLayer);
};
XCTAssertNil(retrievedLayer);
};
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
addRemoveGLLayer();
}
- (void)testReusingOpenGLLayer {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
[self.style insertLayer:layer atIndex:0];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
[self.style removeLayer:layer];
[self waitForMapViewToBeRendered];
}
- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle {
__weak id weakLayer;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
__weak id weakLayer;
@autoreleasepool {
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
}
MLNStyleLayer *layer2 = weakLayer;
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
}
layer2 = nil;
XCTAssertNil(weakLayer);
MLNStyleLayer *layer2 = weakLayer;
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
layer2 = nil;
XCTAssertNil(weakLayer);
}
- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged {
__weak MLNCustomStyleLayer *weakLayer;
__weak MLNCustomStyleLayer *weakLayer;
@autoreleasepool {
{
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
}
@autoreleasepool {
{
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[self.style insertLayer:layer atIndex:0];
layer = nil;
}
}
XCTAssertNotNil(weakLayer);
XCTAssertNotNil(weakLayer);
[self waitForMapViewToBeRendered];
[self waitForMapViewToBeRendered];
MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:10];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
withExtension:@"json"];
self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:10];
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed
XCTAssert(weakLayer == layer2);
XCTAssertNotNil(weakLayer);
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been
// NULLed
XCTAssert(weakLayer == layer2);
XCTAssertNotNil(weakLayer);
// Asking the style for the layer should return nil
MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
XCTAssertNil(layer3);
// Asking the style for the layer should return nil
MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
XCTAssertNil(layer3);
}
- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs {
__weak id weakLayer;
__weak id weakLayer;
@autoreleasepool {
@autoreleasepool {
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
withExtension:@"json"];
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds
styleURL:styleURL];
mapView2.delegate = self;
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL];
mapView2.delegate = self;
XCTAssertNil(mapView2.style);
XCTAssertNil(mapView2.style);
self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self waitForExpectationsWithTimeout:10 handler:nil];
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[mapView2.style insertLayer:layer atIndex:0];
layer = nil;
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
weakLayer = layer;
[mapView2.style insertLayer:layer atIndex:0];
layer = nil;
[self waitForMapViewToBeRendered];
}
XCTAssertNil(weakLayer);
[self waitForMapViewToBeRendered];
}
XCTAssertNil(weakLayer);
}
- (void)testMLNMapViewImplHasCorrectSize {
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:1];
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
withExtension:@"json"];
self.styleLoadingExpectation =
[self expectationWithDescription:@"Map view should finish loading style."];
[self.mapView setStyleURL:styleURL];
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:1];
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl];
CGFloat scaleFactor = [UIScreen mainScreen].scale;
mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
mbgl::Size viewSize = {
static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)
};
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl];
CGFloat scaleFactor = [UIScreen mainScreen].scale;
mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
mbgl::Size viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
// Test that mapView and default renderable have the same size.
XCTAssertTrue(renderableSize == viewSize);
// Test that mapView and default renderable have the same size.
XCTAssertTrue(renderableSize == viewSize);
CLLocationCoordinate2D coordinates[] = {
CLLocationCoordinate2DMake(9.6315313, 52.4133574),
CLLocationCoordinate2DMake(24.9410248, 60.1733244)};
CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(9.6315313, 52.4133574),
CLLocationCoordinate2DMake(24.9410248, 60.1733244)};
MLNPointCollectionFeature *points = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0])];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap" shape:points options:nil];
[self.style addSource:source];
MLNPointCollectionFeature *points = [MLNPointCollectionFeature
pointCollectionWithCoordinates:coordinates
count:sizeof(coordinates) / sizeof(coordinates[0])];
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap"
shape:points
options:nil];
[self.style addSource:source];
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source];
[self.style addLayer:heatmapLayer];
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer"
source:source];
[self.style addLayer:heatmapLayer];
// Test that heatmap layer can create a texture and be successfully rendered.
[self waitForMapViewToBeRendered];
// Test that heatmap layer can create a texture and be successfully rendered.
[self waitForMapViewToBeRendered];
// Resize frame of the view.
[self.mapView setFrame: CGRect{self.mapView.bounds.origin, {256, 256}}];
// Resize frame of the view.
[self.mapView setFrame:CGRect{self.mapView.bounds.origin, {256, 256}}];
// Force sync re-layout.
[self.mapView layoutIfNeeded];
// Force sync re-layout.
[self.mapView layoutIfNeeded];
// Test that mapView and default renderable have the same size after re-layout.
renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
viewSize = { static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor) };
XCTAssertTrue(renderableSize == viewSize);
[self waitForMapViewToBeRendered];
// Test that mapView and default renderable have the same size after re-layout.
renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
XCTAssertTrue(renderableSize == viewSize);
[self waitForMapViewToBeRendered];
}
@end

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -7,18 +7,17 @@
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
#ifndef MLN_LOGGING_DISABLED
[MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault;
[MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault;
#endif
[MLNSettings useWellKnownTileServer:MLNMapTiler];
[MLNSettings useWellKnownTileServer:MLNMapTiler];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [MBXBenchViewController new];
[self.window makeKeyAndVisible];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = [MBXBenchViewController new];
[self.window makeKeyAndVisible];
return YES;
return YES;
}
@end

View File

@@ -19,31 +19,30 @@
frameRenderingTime:(double)frameRenderingTime;
@end
class BenchMapObserver : public mbgl::MapObserver {
public:
BenchMapObserver() = delete;
BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {}
virtual ~BenchMapObserver() = default;
BenchMapObserver() = delete;
BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {}
virtual ~BenchMapObserver() = default;
void onDidFinishRenderingFrame(const RenderFrameStatus& status) override final {
//NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3);
//NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3);
void onDidFinishRenderingFrame(const RenderFrameStatus &status) override final {
// NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3);
// NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3);
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
[mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered
frameEncodingTime:status.renderingStats.encodingTime
frameRenderingTime:status.renderingStats.renderingTime];
}
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
[mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered
frameEncodingTime:status.renderingStats.encodingTime
frameRenderingTime:status.renderingStats.renderingTime];
}
protected:
__weak id<BenchMapDelegate> mapDelegate = nullptr;
__weak id<BenchMapDelegate> mapDelegate = nullptr;
};
@interface MBXBenchViewController () <BenchMapDelegate> {
std::unique_ptr<BenchMapObserver> observer;
std::unique_ptr<mbgl::HeadlessFrontend> frontend;
std::unique_ptr<mbgl::Map> map;
std::unique_ptr<BenchMapObserver> observer;
std::unique_ptr<mbgl::HeadlessFrontend> frontend;
std::unique_ptr<mbgl::Map> map;
}
@property (nonatomic) UIImageView *imageView;
@@ -54,125 +53,129 @@ protected:
// MARK: - Setup
+ (void)initialize
{
if (self == [MBXBenchViewController class])
{
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"MBXUserTrackingMode": @(MLNUserTrackingModeNone),
@"MBXShowsUserLocation": @NO,
@"MBXDebug": @NO,
}];
}
+ (void)initialize {
if (self == [MBXBenchViewController class]) {
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
@"MBXUserTrackingMode" : @(MLNUserTrackingModeNone),
@"MBXShowsUserLocation" : @NO,
@"MBXDebug" : @NO,
}];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
#ifdef LOG_TO_DOCUMENTS_DIR
[self setAndRedirectLogFileToDocuments];
#endif
#ifdef LOG_TO_DOCUMENTS_DIR
[self setAndRedirectLogFileToDocuments];
#endif
// Use a local style and local assets if they’ve been downloaded.
NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" withExtension:@"pbf" subdirectory:@"tiles/tiles/v3/5/7"];
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles" withExtension:@"json" subdirectory:@"tiles"];
const std::vector<std::string> styles = {
"maptiler://maps/streets",
};
constexpr auto styleIndex = 0;
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]];
NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
// Use a local style and local assets if they’ve been downloaded.
NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11"
withExtension:@"pbf"
subdirectory:@"tiles/tiles/v3/5/7"];
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles"
withExtension:@"json"
subdirectory:@"tiles"];
const std::vector<std::string> styles = {
"maptiler://maps/streets",
};
constexpr auto styleIndex = 0;
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json"
: [NSString stringWithCString:styles[styleIndex].c_str()
encoding:NSUTF8StringEncoding]];
NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.imageView];
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
self.imageView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.imageView];
mbgl::Size viewSize = { static_cast<uint32_t>(self.view.bounds.size.width),
static_cast<uint32_t>(self.view.bounds.size.height) };
auto pixelRatio = [[UIScreen mainScreen] scale];
mbgl::Size viewSize = {static_cast<uint32_t>(self.view.bounds.size.width),
static_cast<uint32_t>(self.view.bounds.size.height)};
auto pixelRatio = [[UIScreen mainScreen] scale];
observer = std::make_unique<BenchMapObserver>(self);
frontend = std::make_unique<mbgl::HeadlessFrontend>(
viewSize,
pixelRatio,
mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
mbgl::gfx::ContextMode::Unique,
/* localFontFamily */ std::nullopt,
/* invalidateOnUpdate */ false
);
observer = std::make_unique<BenchMapObserver>(self);
frontend = std::make_unique<mbgl::HeadlessFrontend>(
viewSize, pixelRatio, mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
mbgl::gfx::ContextMode::Unique,
/* localFontFamily */ std::nullopt,
/* invalidateOnUpdate */ false);
mbgl::MapOptions mapOptions;
mapOptions.withMapMode(mbgl::MapMode::Continuous)
.withSize(viewSize)
.withPixelRatio(pixelRatio)
.withConstrainMode(mbgl::ConstrainMode::None)
.withViewportMode(mbgl::ViewportMode::Default)
.withCrossSourceCollisions(true);
mbgl::MapOptions mapOptions;
mapOptions.withMapMode(mbgl::MapMode::Continuous)
.withSize(viewSize)
.withPixelRatio(pixelRatio)
.withConstrainMode(mbgl::ConstrainMode::None)
.withViewportMode(mbgl::ViewportMode::Default)
.withCrossSourceCollisions(true);
mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal];
mbgl::ResourceOptions resourceOptions;
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
.withTileServerOptions(*tileServerOptions);
mbgl::ClientOptions clientOptions;
mbgl::TileServerOptions *tileServerOptions =
[[MLNSettings sharedSettings] tileServerOptionsInternal];
mbgl::ResourceOptions resourceOptions;
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
.withTileServerOptions(*tileServerOptions);
mbgl::ClientOptions clientOptions;
auto apiKey = [[MLNSettings sharedSettings] apiKey];
if (apiKey) {
resourceOptions.withApiKey([apiKey UTF8String]);
}
auto apiKey = [[MLNSettings sharedSettings] apiKey];
if (apiKey) {
resourceOptions.withApiKey([apiKey UTF8String]);
}
map = std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
map->setSize(viewSize);
map->setDebug(mbgl::MapDebugOptions::NoDebug);
map->getStyle().loadURL([url.absoluteString UTF8String]);
map =
std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
map->setSize(viewSize);
map->setDebug(mbgl::MapDebugOptions::NoDebug);
map->getStyle().loadURL([url.absoluteString UTF8String]);
[self startBenchmarkIteration];
[self startBenchmarkIteration];
}
// For setting the filename
NSDate* const currentDate = [NSDate date];
NSDate *const currentDate = [NSDate date];
/*!
\description
Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings > Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR`
Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the
Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings
> Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR`
*/
- (void)setAndRedirectLogFileToDocuments
{
NSString* const name = UIDevice.currentDevice.name;
#if TARGET_IPHONE_SIMULATOR
NSString* const DeviceMode = @"Simulator";
#else
NSString* const DeviceMode = @"Device";
#endif
- (void)setAndRedirectLogFileToDocuments {
NSString *const name = UIDevice.currentDevice.name;
#if TARGET_IPHONE_SIMULATOR
NSString *const DeviceMode = @"Simulator";
#else
NSString *const DeviceMode = @"Device";
#endif
// Set log file name date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd-HHmm"];
NSString *dateString = [formatter stringFromDate:currentDate];
// Set log file name date
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd-HHmm"];
NSString *dateString = [formatter stringFromDate:currentDate];
// Set the log file name
NSString* filename = [NSString stringWithFormat: @"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString];
// Set the log file name
NSString *filename =
[NSString stringWithFormat:@"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString];
// Set log file path
NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent: filename];
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
NSLog(@" open \"%@\"", pathForLog);
// Set log file path
NSArray *allPaths =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:filename];
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
NSLog(@" open \"%@\"", pathForLog);
[self redirectLogToDocuments :pathForLog];
[self redirectLogToDocuments:pathForLog];
}
- (void)redirectLogToDocuments: (NSString*) fileName
{
// write to file in append "a+" mode
freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
- (void)redirectLogToDocuments:(NSString *)fileName {
// write to file in append "a+" mode
freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
}
size_t idx = 0;
@@ -181,163 +184,153 @@ int frames = 0;
double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0;
std::chrono::steady_clock::time_point started;
std::vector<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 {
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments;
namespace mbgl {
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments;
}
- (void)renderFrame
{
mbgl::gfx::BackendScope guard{*(frontend->getBackend())};
- (void)renderFrame {
mbgl::gfx::BackendScope guard{*(frontend->getBackend())};
frontend->renderFrame();
frontend->renderFrame();
auto image = frontend->readStillImage();
auto image = frontend->readStillImage();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(
image.data.get(),
image.size.width,
image.size.height,
8,
4 * image.size.width,
colorSpace,
kCGImageAlphaPremultipliedLast
);
CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext =
CGBitmapContextCreate(image.data.get(), image.size.width, image.size.height, 8,
4 * image.size.width, colorSpace, kCGImageAlphaPremultipliedLast);
CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext);
self.imageView.image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
self.imageView.image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
}
- (void)startBenchmarkIteration
{
if (mbgl::bench::locations.size() > idx) {
const auto& location = mbgl::bench::locations[idx];
- (void)startBenchmarkIteration {
if (mbgl::bench::locations.size() > idx) {
const auto &location = mbgl::bench::locations[idx];
mbgl::CameraOptions cameraOptions;
cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude);
cameraOptions.zoom = location.zoom;
cameraOptions.bearing = location.bearing;
mbgl::AnimationOptions animationOptions;
animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
map->easeTo(cameraOptions, animationOptions);
mbgl::CameraOptions cameraOptions;
cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude);
cameraOptions.zoom = location.zoom;
cameraOptions.bearing = location.bearing;
mbgl::AnimationOptions animationOptions;
animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(
std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
map->easeTo(cameraOptions, animationOptions);
state = State::WaitingForAssets;
NSLog(@"Benchmarking \"%s\"", location.name.c_str());
NSLog(@"- Loading assets...");
state = State::WaitingForAssets;
NSLog(@"Benchmarking \"%s\"", location.name.c_str());
NSLog(@"- Loading assets...");
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
} else {
// Do nothing. The benchmark is completed.
NSLog(@"Benchmark completed.");
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
} else {
// Do nothing. The benchmark is completed.
NSLog(@"Benchmark completed.");
NSLog(@"Result:");
size_t colWidth = 0;
for (const auto& row : result) {
colWidth = std::max(row.first.size(), colWidth);
}
NSLog(@"Result:");
size_t colWidth = 0;
for (const auto &row : result) {
colWidth = std::max(row.first.size(), colWidth);
}
double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0;
for (const auto& row : result) {
NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(), 1e3 * row.second.first, 1e3 * row.second.second);
totalFrameEncodingTime += row.second.first;
totalFrameRenderingTime += row.second.second;
}
double totalFrameEncodingTime = 0;
double totalFrameRenderingTime = 0;
for (const auto &row : result) {
NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(),
1e3 * row.second.first, 1e3 * row.second.second);
totalFrameEncodingTime += row.second.first;
totalFrameRenderingTime += row.second.second;
}
NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size());
NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size());
NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size());
NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size());
// NSLog(@"Total uploads: %zu", mbgl::uploadCount);
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
// NSLog(@"Total uploads: %zu", mbgl::uploadCount);
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
#if !defined(NDEBUG)
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
// TODO: This doesn't clean up everything, what are we missing?
map.reset();
observer.reset();
frontend.reset();
#endif // !defined(NDEBUG)
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
// TODO: This doesn't clean up everything, what are we missing?
map.reset();
observer.reset();
frontend.reset();
#endif // !defined(NDEBUG)
// this does not shut the application down correctly,
// and results in an assertion failure in thread-local code
//exit(0);
// this does not shut the application down correctly,
// and results in an assertion failure in thread-local code
// exit(0);
// Use the UIApplication lifecycle instead.
// Terminating an app programmatically is strongly discouraged by Apple.
// Combined with the plist setting "Application does not run in background" suspend allows
// the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait.
UIApplication *app = [UIApplication sharedApplication];
if ([app respondsToSelector:@selector(suspend)])
{
[app performSelector:@selector(suspend)];
}
// Use the UIApplication lifecycle instead.
// Terminating an app programmatically is strongly discouraged by Apple.
// Combined with the plist setting "Application does not run in background" suspend allows
// the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait.
UIApplication *app = [UIApplication sharedApplication];
if ([app respondsToSelector:@selector(suspend)]) {
[app performSelector:@selector(suspend)];
}
}
}
- (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered
frameEncodingTime:(double)frameEncodingTime
frameRenderingTime:(double)frameRenderingTime
{
if (state == State::Benchmarking)
{
frames++;
totalFrameEncodingTime += frameEncodingTime;
totalFrameRenderingTime += frameRenderingTime;
frameRenderingTime:(double)frameRenderingTime {
if (state == State::Benchmarking) {
frames++;
totalFrameEncodingTime += frameEncodingTime;
totalFrameRenderingTime += frameRenderingTime;
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count();
if (duration >= benchmarkDuration * 1e6)
{
state = State::None;
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - started)
.count();
if (duration >= benchmarkDuration * 1e6) {
state = State::None;
// Report FPS
const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime));
NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames);
// Report FPS
const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
result.emplace_back(mbgl::bench::locations[idx].name,
std::make_pair(frameEncodingTime, frameRenderingTime));
NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)",
frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames);
// Start benchmarking the next location.
idx++;
[self startBenchmarkIteration];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
}
return;
// Start benchmarking the next location.
idx++;
[self startBenchmarkIteration];
} else {
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
}
else if (state == State::WaitingForAssets)
{
if (map->isFullyLoaded())
{
// Start the benchmarking timer.
frames = 0;
totalFrameEncodingTime = 0;
totalFrameRenderingTime = 0;
state = State::Benchmarking;
started = std::chrono::steady_clock::now();
NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return;
return;
} else if (state == State::WaitingForAssets) {
if (map->isFullyLoaded()) {
// Start the benchmarking timer.
frames = 0;
totalFrameEncodingTime = 0;
totalFrameRenderingTime = 0;
state = State::Benchmarking;
started = std::chrono::steady_clock::now();
NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self renderFrame];
});
return;
}
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
@end

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