ios: apply clang-format to Objective-C++ sources (#4060)
This commit is contained in:
@@ -20,4 +20,5 @@ Language: Cpp
|
|||||||
Language: ObjC
|
Language: ObjC
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
ObjCSpaceAfterProperty: true
|
ObjCSpaceAfterProperty: true
|
||||||
|
AccessModifierOffset: -2
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ repos:
|
|||||||
rev: v20.1.8
|
rev: v20.1.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
files: '.*\.(hpp|cpp|h)'
|
files: '.*\.(hpp|cpp|h|mm)'
|
||||||
exclude: 'vendor/.*'
|
exclude: 'vendor/.*'
|
||||||
|
|
||||||
- repo: https://github.com/keith/pre-commit-buildifier
|
- repo: https://github.com/keith/pre-commit-buildifier
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
#import "ExampleCustomDrawableStyleLayer.h"
|
#import "ExampleCustomDrawableStyleLayer.h"
|
||||||
#import "MLNStyleLayer.h"
|
|
||||||
#import "MLNCustomDrawableStyleLayer.h"
|
#import "MLNCustomDrawableStyleLayer.h"
|
||||||
|
#import "MLNStyleLayer.h"
|
||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
#include <mbgl/util/image+MLNAdditions.hpp>
|
|
||||||
#include <mbgl/layermanager/layer_factory.hpp>
|
#include <mbgl/layermanager/layer_factory.hpp>
|
||||||
#include <mbgl/style/layer.hpp>
|
#include <mbgl/style/layer.hpp>
|
||||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||||
#include <mbgl/util/constants.hpp>
|
#include <mbgl/util/constants.hpp>
|
||||||
|
#include <mbgl/util/image+MLNAdditions.hpp>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
@interface MLNCustomDrawableStyleLayer (Internal)
|
@interface MLNCustomDrawableStyleLayer (Internal)
|
||||||
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer;
|
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer;
|
||||||
@@ -22,271 +22,302 @@ class ExampleCustomDrawableStyleLayerHost;
|
|||||||
@implementation ExampleCustomDrawableStyleLayer
|
@implementation ExampleCustomDrawableStyleLayer
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||||
auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(identifier.UTF8String,
|
auto layer = std::make_unique<mbgl::style::CustomDrawableLayer>(
|
||||||
std::make_unique<ExampleCustomDrawableStyleLayerHost>(self));
|
identifier.UTF8String, std::make_unique<ExampleCustomDrawableStyleLayerHost>(self));
|
||||||
return self = [super initWithPendingLayer:std::move(layer)];
|
return self = [super initWithPendingLayer:std::move(layer)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost {
|
class ExampleCustomDrawableStyleLayerHost : public mbgl::style::CustomDrawableLayerHost {
|
||||||
public:
|
public:
|
||||||
ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) {
|
ExampleCustomDrawableStyleLayerHost(ExampleCustomDrawableStyleLayer *styleLayer) {
|
||||||
layerRef = styleLayer;
|
layerRef = styleLayer;
|
||||||
layer = nil;
|
layer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize() override {
|
||||||
|
if (layerRef == nil)
|
||||||
|
return;
|
||||||
|
else if (layer == nil)
|
||||||
|
layer = layerRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Interface &interface) override {
|
||||||
|
// if we have built our drawable(s) already, either update or skip
|
||||||
|
if (interface.getDrawableCount()) return;
|
||||||
|
|
||||||
|
constexpr float extent = mbgl::util::EXTENT;
|
||||||
|
|
||||||
|
// add classic polylines
|
||||||
|
{
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
|
// set tile
|
||||||
|
interface.setTileID({11, 327, 792});
|
||||||
|
|
||||||
|
constexpr auto numLines = 6;
|
||||||
|
Interface::LineOptions options[numLines]{
|
||||||
|
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
|
||||||
|
/*width=*/8.0f, /*color=*/Color::red()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
|
||||||
|
/*width=*/4.0f, /*color=*/Color::blue()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
|
||||||
|
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
|
||||||
|
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
|
||||||
|
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
|
||||||
|
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
|
||||||
|
/*width=*/0.5f, /*color=*/Color::black()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
|
||||||
|
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
|
||||||
|
};
|
||||||
|
for (auto &opt : options) {
|
||||||
|
opt.geometry.beginCap = style::LineCapType::Butt;
|
||||||
|
opt.geometry.endCap = style::LineCapType::Butt;
|
||||||
|
opt.geometry.joinType = style::LineJoinType::Miter;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto numPoints = 10;
|
||||||
|
GeometryCoordinates polyline;
|
||||||
|
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
|
||||||
|
polyline.emplace_back(ipoint * extent / numPoints,
|
||||||
|
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto index{0}; index < numLines; ++index) {
|
||||||
|
for (auto &p : polyline) {
|
||||||
|
p.y += extent / numLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set property values
|
||||||
|
interface.setLineOptions(options[index]);
|
||||||
|
|
||||||
|
// add polyline
|
||||||
|
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initialize() override {
|
// add wide vector polylines with tile coordinates
|
||||||
if (layerRef == nil) return;
|
{
|
||||||
else if (layer == nil) layer = layerRef;
|
using namespace mbgl;
|
||||||
|
|
||||||
|
// set tile
|
||||||
|
interface.setTileID({11, 327, 792});
|
||||||
|
|
||||||
|
constexpr auto numLines = 6;
|
||||||
|
Interface::LineOptions options[numLines]{
|
||||||
|
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f,
|
||||||
|
/*width=*/8.0f, /*color=*/Color::red()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f,
|
||||||
|
/*width=*/4.0f, /*color=*/Color::blue()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f,
|
||||||
|
/*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f)},
|
||||||
|
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f,
|
||||||
|
/*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f)},
|
||||||
|
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f,
|
||||||
|
/*width=*/0.5f, /*color=*/Color::black()},
|
||||||
|
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f,
|
||||||
|
/*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f)},
|
||||||
|
};
|
||||||
|
for (auto &opt : options) {
|
||||||
|
opt.geometry.beginCap = style::LineCapType::Butt;
|
||||||
|
opt.geometry.endCap = style::LineCapType::Butt;
|
||||||
|
opt.geometry.joinType = style::LineJoinType::Miter;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto numPoints = 10;
|
||||||
|
GeometryCoordinates polyline;
|
||||||
|
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
|
||||||
|
polyline.emplace_back(ipoint * extent / numPoints,
|
||||||
|
std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto index{0}; index < numLines; ++index) {
|
||||||
|
for (auto &p : polyline) {
|
||||||
|
if (0 == index) p.y += 0.25f * extent / numLines;
|
||||||
|
p.y += extent / numLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set property values
|
||||||
|
interface.setLineOptions(options[index]);
|
||||||
|
|
||||||
|
// add polyline
|
||||||
|
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
|
||||||
|
|
||||||
|
// add clone
|
||||||
|
for (auto &p : polyline) {
|
||||||
|
p.y += 0.05f * extent / numLines;
|
||||||
|
}
|
||||||
|
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
|
||||||
|
for (auto &p : polyline) {
|
||||||
|
p.y -= 0.05f * extent / numLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(Interface& interface) override {
|
// add fill polygon
|
||||||
|
{
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
// if we have built our drawable(s) already, either update or skip
|
// set tile
|
||||||
if (interface.getDrawableCount())
|
interface.setTileID({11, 327, 790});
|
||||||
return;
|
|
||||||
|
|
||||||
constexpr float extent = mbgl::util::EXTENT;
|
GeometryCollection geometry{
|
||||||
|
{
|
||||||
|
// ring 1
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ring 2
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// add classic polylines
|
// set properties
|
||||||
{
|
interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// set tile
|
// add fill
|
||||||
interface.setTileID({11, 327, 792});
|
interface.addFill(geometry);
|
||||||
|
|
||||||
constexpr auto numLines = 6;
|
|
||||||
Interface::LineOptions options[numLines] {
|
|
||||||
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
|
|
||||||
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
|
|
||||||
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
|
|
||||||
};
|
|
||||||
for(auto& opt: options) {
|
|
||||||
opt.geometry.beginCap = style::LineCapType::Butt;
|
|
||||||
opt.geometry.endCap = style::LineCapType::Butt;
|
|
||||||
opt.geometry.joinType = style::LineJoinType::Miter;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto numPoints = 10;
|
|
||||||
GeometryCoordinates polyline;
|
|
||||||
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
|
|
||||||
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto index {0}; index < numLines; ++index) {
|
|
||||||
for(auto &p : polyline) {
|
|
||||||
p.y += extent / numLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set property values
|
|
||||||
interface.setLineOptions(options[index]);
|
|
||||||
|
|
||||||
// add polyline
|
|
||||||
interface.addPolyline(polyline, Interface::LineShaderType::Classic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add wide vector polylines with tile coordinates
|
|
||||||
{
|
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// set tile
|
|
||||||
interface.setTileID({11, 327, 792});
|
|
||||||
|
|
||||||
constexpr auto numLines = 6;
|
|
||||||
Interface::LineOptions options[numLines] {
|
|
||||||
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/8.0f, /*color=*/Color::red() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/4.0f, /*opacity=*/1.0f, /*gapWidth=*/2.0f, /*offset=*/-1.0f, /*width=*/4.0f, /*color=*/Color::blue() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/16.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/2.0f, /*width=*/16.0f, /*color=*/Color(1.f, 0.5f, 0, 0.5f) },
|
|
||||||
{/*geometry=*/{}, /*blur=*/2.0f, /*opacity=*/1.0f, /*gapWidth=*/1.0f, /*offset=*/-2.0f, /*width=*/2.0f, /*color=*/Color(1.f, 1.f, 0, 0.3f) },
|
|
||||||
{/*geometry=*/{}, /*blur=*/0.5f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/0.5f, /*width=*/0.5f, /*color=*/Color::black() },
|
|
||||||
{/*geometry=*/{}, /*blur=*/24.0f, /*opacity=*/0.5f, /*gapWidth=*/1.0f, /*offset=*/-5.0f, /*width=*/24.0f, /*color=*/Color(1.f, 0, 1.f, 0.2f) },
|
|
||||||
};
|
|
||||||
for(auto& opt: options) {
|
|
||||||
opt.geometry.beginCap = style::LineCapType::Butt;
|
|
||||||
opt.geometry.endCap = style::LineCapType::Butt;
|
|
||||||
opt.geometry.joinType = style::LineJoinType::Miter;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto numPoints = 10;
|
|
||||||
GeometryCoordinates polyline;
|
|
||||||
for (auto ipoint{0}; ipoint < numPoints; ++ipoint) {
|
|
||||||
polyline.emplace_back(ipoint * extent / numPoints, std::sin(ipoint * 2 * M_PI / numPoints) * extent / numLines / 2.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto index {0}; index < numLines; ++index) {
|
|
||||||
for(auto &p : polyline) {
|
|
||||||
if (0 == index) p.y += 0.25f * extent / numLines;
|
|
||||||
p.y += extent / numLines;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set property values
|
|
||||||
interface.setLineOptions(options[index]);
|
|
||||||
|
|
||||||
// add polyline
|
|
||||||
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
|
|
||||||
|
|
||||||
// add clone
|
|
||||||
for(auto &p : polyline) {
|
|
||||||
p.y += 0.05f * extent / numLines;
|
|
||||||
}
|
|
||||||
interface.addPolyline(polyline, Interface::LineShaderType::WideVector);
|
|
||||||
for(auto &p : polyline) {
|
|
||||||
p.y -= 0.05f * extent / numLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add fill polygon
|
|
||||||
{
|
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// set tile
|
|
||||||
interface.setTileID({11, 327, 790});
|
|
||||||
|
|
||||||
GeometryCollection geometry{
|
|
||||||
{
|
|
||||||
// ring 1
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.7f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 1.0f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.0f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// ring 2
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.15f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.25f), static_cast<int16_t>(extent* 0.45f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// set properties
|
|
||||||
interface.setFillOptions({/*color=*/Color::green(), /*opacity=*/0.5f});
|
|
||||||
|
|
||||||
// add fill
|
|
||||||
interface.addFill(geometry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add symbol
|
|
||||||
{
|
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// set tile
|
|
||||||
interface.setTileID({11, 327, 789});
|
|
||||||
|
|
||||||
GeometryCoordinate position {static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)};
|
|
||||||
|
|
||||||
// load image
|
|
||||||
UIImage *assetImage = [UIImage imageNamed:@"pin"];
|
|
||||||
assert(assetImage.CGImage != NULL);
|
|
||||||
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(MLNPremultipliedImageFromCGImage(assetImage.CGImage));
|
|
||||||
|
|
||||||
// set symbol options
|
|
||||||
Interface::SymbolOptions options;
|
|
||||||
options.texture = interface.context.createTexture2D();
|
|
||||||
options.texture->setImage(image);
|
|
||||||
options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
|
|
||||||
const std::array<std::array<float, 2>, 2> textureCoordinates = {{{0.0f, 0.08f}, {1.0f, 0.9f}}};
|
|
||||||
const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0];
|
|
||||||
const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1];
|
|
||||||
assert(xspan > 0.0f && yspan > 0.0f);
|
|
||||||
options.size = {static_cast<uint32_t>(image->size.width * xspan), static_cast<uint32_t>(image->size.height * yspan)};
|
|
||||||
options.anchor = {0.5f, 0.95f};
|
|
||||||
options.angleDegrees = 45.0f;
|
|
||||||
options.scaleWithMap = true;
|
|
||||||
options.pitchWithMap = false;
|
|
||||||
interface.setSymbolOptions(options);
|
|
||||||
|
|
||||||
// add symbol
|
|
||||||
interface.addSymbol(position, textureCoordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add polylines using wide vectors using geographic coordinates
|
|
||||||
{
|
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// add polyline with geographic coordinates
|
|
||||||
Interface::LineOptions options = {/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/12.0f, /*color=*/{.0f, .0f, .0f, .5f} };
|
|
||||||
options.geometry.beginCap = style::LineCapType::Square;
|
|
||||||
options.geometry.endCap = style::LineCapType::Square;
|
|
||||||
options.geometry.joinType = style::LineJoinType::Bevel;
|
|
||||||
options.geometry.type = FeatureType::LineString;
|
|
||||||
interface.setLineOptions(options);
|
|
||||||
|
|
||||||
LineString<double> polyline_geo {
|
|
||||||
// San Francisco
|
|
||||||
{-122.38186800073211, 37.77466003457463},
|
|
||||||
{-122.3869373450997, 37.774352128895615},
|
|
||||||
{-122.38680767979824, 37.773294612880306},
|
|
||||||
{-122.38476465260224, 37.773350946288765},
|
|
||||||
{-122.38146223043374, 37.77194168964067},
|
|
||||||
{-122.6813560305925, 37.666084247570964},
|
|
||||||
{-122.26765538866474, 37.65037232584494},
|
|
||||||
{-122.42528413673159, 38.020443518012584},
|
|
||||||
// Seattle
|
|
||||||
{-122.34927775216809, 47.62050663596438},
|
|
||||||
// New York
|
|
||||||
{-74.04454331829972, 40.6892168305434},
|
|
||||||
};
|
|
||||||
interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add polylines using wide vectors in tile coordinates
|
|
||||||
{
|
|
||||||
using namespace mbgl;
|
|
||||||
|
|
||||||
// set tile
|
|
||||||
interface.setTileID({11, 327, 790});
|
|
||||||
|
|
||||||
Interface::LineOptions options
|
|
||||||
{/*geometry=*/{}, /*blur=*/0.0f, /*opacity=*/1.0f, /*gapWidth=*/0.0f, /*offset=*/0.0f, /*width=*/7.0f, /*color=*/{1.0f, 0, 0, .5f} };
|
|
||||||
options.geometry.beginCap = style::LineCapType::Round;
|
|
||||||
options.geometry.endCap = style::LineCapType::Round;
|
|
||||||
options.geometry.joinType = style::LineJoinType::Round;
|
|
||||||
|
|
||||||
// add polyline with tile coordinates
|
|
||||||
GeometryCollection polyline_tile{
|
|
||||||
{
|
|
||||||
// ring 1
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.2f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.7f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.5f), static_cast<int16_t>(extent* 1.0f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.0f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// ring 2
|
|
||||||
{static_cast<int16_t>(extent* 0.1f), static_cast<int16_t>(extent* 0.25f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.15f), static_cast<int16_t>(extent* 0.5f)},
|
|
||||||
{static_cast<int16_t>(extent* 0.25f), static_cast<int16_t>(extent* 0.45f)},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
options.geometry.type = FeatureType::Polygon;
|
|
||||||
interface.setLineOptions(options);
|
|
||||||
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
|
|
||||||
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish
|
|
||||||
interface.finish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void deinitialize() override {
|
// add symbol
|
||||||
if (layer == nil) return;
|
{
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
layerRef = layer;
|
// set tile
|
||||||
layer = nil;
|
interface.setTileID({11, 327, 789});
|
||||||
|
|
||||||
|
GeometryCoordinate position{static_cast<int16_t>(extent * 0.5f),
|
||||||
|
static_cast<int16_t>(extent * 0.5f)};
|
||||||
|
|
||||||
|
// load image
|
||||||
|
UIImage *assetImage = [UIImage imageNamed:@"pin"];
|
||||||
|
assert(assetImage.CGImage != NULL);
|
||||||
|
std::shared_ptr<PremultipliedImage> image = std::make_shared<PremultipliedImage>(
|
||||||
|
MLNPremultipliedImageFromCGImage(assetImage.CGImage));
|
||||||
|
|
||||||
|
// set symbol options
|
||||||
|
Interface::SymbolOptions options;
|
||||||
|
options.texture = interface.context.createTexture2D();
|
||||||
|
options.texture->setImage(image);
|
||||||
|
options.texture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
|
||||||
|
gfx::TextureWrapType::Clamp,
|
||||||
|
gfx::TextureWrapType::Clamp});
|
||||||
|
const std::array<std::array<float, 2>, 2> textureCoordinates = {
|
||||||
|
{{0.0f, 0.08f}, {1.0f, 0.9f}}};
|
||||||
|
const float xspan = textureCoordinates[1][0] - textureCoordinates[0][0];
|
||||||
|
const float yspan = textureCoordinates[1][1] - textureCoordinates[0][1];
|
||||||
|
assert(xspan > 0.0f && yspan > 0.0f);
|
||||||
|
options.size = {static_cast<uint32_t>(image->size.width * xspan),
|
||||||
|
static_cast<uint32_t>(image->size.height * yspan)};
|
||||||
|
options.anchor = {0.5f, 0.95f};
|
||||||
|
options.angleDegrees = 45.0f;
|
||||||
|
options.scaleWithMap = true;
|
||||||
|
options.pitchWithMap = false;
|
||||||
|
interface.setSymbolOptions(options);
|
||||||
|
|
||||||
|
// add symbol
|
||||||
|
interface.addSymbol(position, textureCoordinates);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add polylines using wide vectors using geographic coordinates
|
||||||
|
{
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
|
// add polyline with geographic coordinates
|
||||||
|
Interface::LineOptions options = {/*geometry=*/{},
|
||||||
|
/*blur=*/0.0f,
|
||||||
|
/*opacity=*/1.0f,
|
||||||
|
/*gapWidth=*/0.0f,
|
||||||
|
/*offset=*/0.0f,
|
||||||
|
/*width=*/12.0f,
|
||||||
|
/*color=*/{.0f, .0f, .0f, .5f}};
|
||||||
|
options.geometry.beginCap = style::LineCapType::Square;
|
||||||
|
options.geometry.endCap = style::LineCapType::Square;
|
||||||
|
options.geometry.joinType = style::LineJoinType::Bevel;
|
||||||
|
options.geometry.type = FeatureType::LineString;
|
||||||
|
interface.setLineOptions(options);
|
||||||
|
|
||||||
|
LineString<double> polyline_geo{
|
||||||
|
// San Francisco
|
||||||
|
{-122.38186800073211, 37.77466003457463},
|
||||||
|
{-122.3869373450997, 37.774352128895615},
|
||||||
|
{-122.38680767979824, 37.773294612880306},
|
||||||
|
{-122.38476465260224, 37.773350946288765},
|
||||||
|
{-122.38146223043374, 37.77194168964067},
|
||||||
|
{-122.6813560305925, 37.666084247570964},
|
||||||
|
{-122.26765538866474, 37.65037232584494},
|
||||||
|
{-122.42528413673159, 38.020443518012584},
|
||||||
|
// Seattle
|
||||||
|
{-122.34927775216809, 47.62050663596438},
|
||||||
|
// New York
|
||||||
|
{-74.04454331829972, 40.6892168305434},
|
||||||
|
};
|
||||||
|
interface.addPolyline(polyline_geo, Interface::LineShaderType::WideVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add polylines using wide vectors in tile coordinates
|
||||||
|
{
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
|
// set tile
|
||||||
|
interface.setTileID({11, 327, 790});
|
||||||
|
|
||||||
|
Interface::LineOptions options{/*geometry=*/{},
|
||||||
|
/*blur=*/0.0f,
|
||||||
|
/*opacity=*/1.0f,
|
||||||
|
/*gapWidth=*/0.0f,
|
||||||
|
/*offset=*/0.0f,
|
||||||
|
/*width=*/7.0f,
|
||||||
|
/*color=*/{1.0f, 0, 0, .5f}};
|
||||||
|
options.geometry.beginCap = style::LineCapType::Round;
|
||||||
|
options.geometry.endCap = style::LineCapType::Round;
|
||||||
|
options.geometry.joinType = style::LineJoinType::Round;
|
||||||
|
|
||||||
|
// add polyline with tile coordinates
|
||||||
|
GeometryCollection polyline_tile{
|
||||||
|
{
|
||||||
|
// ring 1
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.2f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.7f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.5f), static_cast<int16_t>(extent * 1.0f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.0f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ring 2
|
||||||
|
{static_cast<int16_t>(extent * 0.1f), static_cast<int16_t>(extent * 0.25f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.15f), static_cast<int16_t>(extent * 0.5f)},
|
||||||
|
{static_cast<int16_t>(extent * 0.25f), static_cast<int16_t>(extent * 0.45f)},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
options.geometry.type = FeatureType::Polygon;
|
||||||
|
interface.setLineOptions(options);
|
||||||
|
interface.addPolyline(polyline_tile[0], Interface::LineShaderType::WideVector);
|
||||||
|
interface.addPolyline(polyline_tile[1], Interface::LineShaderType::WideVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish
|
||||||
|
interface.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void deinitialize() override {
|
||||||
|
if (layer == nil) return;
|
||||||
|
|
||||||
|
layerRef = layer;
|
||||||
|
layer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
__weak ExampleCustomDrawableStyleLayer * layerRef;
|
__weak ExampleCustomDrawableStyleLayer *layerRef;
|
||||||
ExampleCustomDrawableStyleLayer * layer = nil;
|
ExampleCustomDrawableStyleLayer *layer = nil;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,29 +2,24 @@
|
|||||||
|
|
||||||
@implementation PluginLayerExample
|
@implementation PluginLayerExample
|
||||||
|
|
||||||
|
|
||||||
// This is the layer type in the style that is used
|
// This is the layer type in the style that is used
|
||||||
+(MLNPluginLayerCapabilities *)layerCapabilities {
|
+ (MLNPluginLayerCapabilities *)layerCapabilities {
|
||||||
|
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
|
||||||
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
|
tempResult.layerID = @"plugin-layer-test";
|
||||||
tempResult.layerID = @"plugin-layer-test";
|
tempResult.requiresPass3D = YES;
|
||||||
tempResult.requiresPass3D = YES;
|
return tempResult;
|
||||||
return tempResult;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The overrides
|
// The overrides
|
||||||
-(void)onRenderLayer {
|
- (void)onRenderLayer {
|
||||||
NSLog(@"PluginLayerExample: On Render Layer");
|
NSLog(@"PluginLayerExample: On Render Layer");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)onUpdateLayer {
|
- (void)onUpdateLayer {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
||||||
// NSLog(@"Layer Properties: %@", layerProperties);
|
// NSLog(@"Layer Properties: %@", layerProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -10,224 +10,208 @@
|
|||||||
#import <MetalKit/MetalKit.h>
|
#import <MetalKit/MetalKit.h>
|
||||||
#import "Mapbox.h"
|
#import "Mapbox.h"
|
||||||
|
|
||||||
typedef struct
|
typedef struct {
|
||||||
{
|
vector_float2 position;
|
||||||
vector_float2 position;
|
vector_float4 color;
|
||||||
vector_float4 color;
|
|
||||||
} Vertex;
|
} Vertex;
|
||||||
|
|
||||||
@interface PluginLayerExampleMetalRendering () {
|
@interface PluginLayerExampleMetalRendering () {
|
||||||
// The render pipeline state
|
// The render pipeline state
|
||||||
id<MTLRenderPipelineState> _pipelineState;
|
id<MTLRenderPipelineState> _pipelineState;
|
||||||
id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
|
id<MTLDepthStencilState> _depthStencilStateWithoutStencil;
|
||||||
|
|
||||||
// Properties
|
|
||||||
float _offsetX;
|
|
||||||
float _offsetY;
|
|
||||||
float _scale;
|
|
||||||
float _r, _g, _b, _a;
|
|
||||||
|
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
float _offsetX;
|
||||||
|
float _offsetY;
|
||||||
|
float _scale;
|
||||||
|
float _r, _g, _b, _a;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@implementation PluginLayerExampleMetalRendering
|
@implementation PluginLayerExampleMetalRendering
|
||||||
|
|
||||||
|
|
||||||
// This is a static class method that is used by the MLNMapView to create the
|
// This is a static class method that is used by the MLNMapView to create the
|
||||||
// required marshalling classes to interface with the MapLibre core.
|
// required marshalling classes to interface with the MapLibre core.
|
||||||
+(MLNPluginLayerCapabilities *)layerCapabilities {
|
+ (MLNPluginLayerCapabilities *)layerCapabilities {
|
||||||
|
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
|
||||||
|
tempResult.layerID = @"plugin-layer-metal-rendering";
|
||||||
|
tempResult.requiresPass3D = YES;
|
||||||
|
|
||||||
MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init];
|
// Define the paint properties that this layer implements and
|
||||||
tempResult.layerID = @"plugin-layer-metal-rendering";
|
// what types they are
|
||||||
tempResult.requiresPass3D = YES;
|
tempResult.layerProperties = @[
|
||||||
|
// The scale property
|
||||||
|
[MLNPluginLayerProperty propertyWithName:@"scale"
|
||||||
|
propertyType:MLNPluginLayerPropertyTypeSingleFloat
|
||||||
|
defaultValue:@(1.0)],
|
||||||
|
|
||||||
// Define the paint properties that this layer implements and
|
// The fill color property
|
||||||
// what types they are
|
[MLNPluginLayerProperty propertyWithName:@"fill-color"
|
||||||
tempResult.layerProperties = @[
|
propertyType:MLNPluginLayerPropertyTypeColor
|
||||||
// The scale property
|
defaultValue:[UIColor blueColor]]
|
||||||
[MLNPluginLayerProperty propertyWithName:@"scale"
|
|
||||||
propertyType:MLNPluginLayerPropertyTypeSingleFloat
|
|
||||||
defaultValue:@(1.0)],
|
|
||||||
|
|
||||||
// The fill color property
|
];
|
||||||
[MLNPluginLayerProperty propertyWithName:@"fill-color"
|
|
||||||
propertyType:MLNPluginLayerPropertyTypeColor
|
|
||||||
defaultValue:[UIColor blueColor]]
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
return tempResult;
|
|
||||||
|
|
||||||
|
return tempResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)createShaders:(MLNMapView *)mapView {
|
- (void)createShaders:(MLNMapView *)mapView {
|
||||||
MLNBackendResource* resource = [mapView backendResource];
|
MLNBackendResource *resource = [mapView backendResource];
|
||||||
|
|
||||||
NSString *shaderSource = @
|
NSString *shaderSource =
|
||||||
|
@
|
||||||
" #include <metal_stdlib>\n"
|
" #include <metal_stdlib>\n"
|
||||||
" using namespace metal;\n"
|
" using namespace metal;\n"
|
||||||
" typedef struct\n"
|
" typedef struct\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" vector_float2 position;\n"
|
" vector_float2 position;\n"
|
||||||
" vector_float4 color;\n"
|
" vector_float4 color;\n"
|
||||||
" } Vertex;\n"
|
" } Vertex;\n"
|
||||||
" struct RasterizerData\n"
|
" struct RasterizerData\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" float4 position [[position]];\n"
|
" float4 position [[position]];\n"
|
||||||
" float4 color;\n"
|
" float4 color;\n"
|
||||||
" };\n"
|
" };\n"
|
||||||
" vertex RasterizerData\n"
|
" vertex RasterizerData\n"
|
||||||
" vertexShader(uint vertexID [[vertex_id]],\n"
|
" vertexShader(uint vertexID [[vertex_id]],\n"
|
||||||
" constant Vertex *vertices [[buffer(0)]],\n"
|
" constant Vertex *vertices [[buffer(0)]],\n"
|
||||||
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
|
" constant vector_uint2 *viewportSizePointer [[buffer(1)]])\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" RasterizerData out;\n"
|
" RasterizerData out;\n"
|
||||||
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
|
" float2 pixelSpacePosition = vertices[vertexID].position.xy;\n"
|
||||||
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
|
" vector_float2 viewportSize = vector_float2(*viewportSizePointer);\n"
|
||||||
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
|
" out.position = vector_float4(0.0, 0.0, 0.0, 1.0);\n"
|
||||||
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
|
" out.position.xy = pixelSpacePosition / (viewportSize / 2.0);\n"
|
||||||
" out.color = vertices[vertexID].color;\n"
|
" out.color = vertices[vertexID].color;\n"
|
||||||
" return out;\n"
|
" return out;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
|
" fragment float4 fragmentShader(RasterizerData in [[stage_in]])\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" return in.color;\n"
|
" return in.color;\n"
|
||||||
" }\n";
|
" }\n";
|
||||||
|
|
||||||
|
NSError *error = nil;
|
||||||
|
id<MTLDevice> _device = resource.device;
|
||||||
|
id<MTLLibrary> library = [_device newLibraryWithSource:shaderSource options:nil error:&error];
|
||||||
|
NSAssert(library, @"Error compiling shaders: %@", error);
|
||||||
|
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"];
|
||||||
|
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"];
|
||||||
|
|
||||||
NSError *error = nil;
|
// Configure a pipeline descriptor that is used to create a pipeline state.
|
||||||
id<MTLDevice> _device = resource.device;
|
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
||||||
id<MTLLibrary> library = [_device newLibraryWithSource:shaderSource options:nil error:&error];
|
pipelineStateDescriptor.label = @"Simple Pipeline";
|
||||||
NSAssert(library, @"Error compiling shaders: %@", error);
|
pipelineStateDescriptor.vertexFunction = vertexFunction;
|
||||||
id<MTLFunction> vertexFunction = [library newFunctionWithName:@"vertexShader"];
|
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
|
||||||
id<MTLFunction> fragmentFunction = [library newFunctionWithName:@"fragmentShader"];
|
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
|
||||||
|
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
|
||||||
|
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
|
||||||
|
|
||||||
// Configure a pipeline descriptor that is used to create a pipeline state.
|
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
|
||||||
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
|
error:&error];
|
||||||
pipelineStateDescriptor.label = @"Simple Pipeline";
|
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
|
||||||
pipelineStateDescriptor.vertexFunction = vertexFunction;
|
|
||||||
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
|
|
||||||
pipelineStateDescriptor.colorAttachments[0].pixelFormat = resource.mtkView.colorPixelFormat;
|
|
||||||
pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
|
|
||||||
pipelineStateDescriptor.stencilAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
|
|
||||||
|
|
||||||
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
|
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
|
||||||
error:&error];
|
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
|
||||||
NSAssert(_pipelineState, @"Failed to create pipeline state: %@", error);
|
depthStencilDescriptor.depthCompareFunction =
|
||||||
|
MTLCompareFunctionAlways; // Or another value as needed
|
||||||
// Notice that we don't configure the stencilTest property, leaving stencil testing disabled
|
depthStencilDescriptor.depthWriteEnabled = NO;
|
||||||
MTLDepthStencilDescriptor *depthStencilDescriptor = [[MTLDepthStencilDescriptor alloc] init];
|
|
||||||
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways; // Or another value as needed
|
|
||||||
depthStencilDescriptor.depthWriteEnabled = NO;
|
|
||||||
|
|
||||||
_depthStencilStateWithoutStencil = [_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
|
|
||||||
|
|
||||||
|
_depthStencilStateWithoutStencil =
|
||||||
|
[_device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Setup the rendering environment
|
// Setup the rendering environment
|
||||||
-(void)setupRendering:(MLNMapView *)mapView {
|
- (void)setupRendering:(MLNMapView *)mapView {
|
||||||
[self createShaders:mapView];
|
[self createShaders:mapView];
|
||||||
if (_scale == 0) {
|
if (_scale == 0) {
|
||||||
_scale = 1;
|
_scale = 1;
|
||||||
}
|
}
|
||||||
// Set the initial
|
// Set the initial
|
||||||
_a = 1;
|
_a = 1;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The overrides
|
// The overrides
|
||||||
-(void)onRenderLayer:(MLNMapView *)mapView
|
- (void)onRenderLayer:(MLNMapView *)mapView
|
||||||
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
|
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
|
||||||
|
MLNBackendResource *resource = [mapView backendResource];
|
||||||
|
|
||||||
MLNBackendResource* resource = [mapView backendResource];
|
if (_pipelineState == nil) {
|
||||||
|
[self setupRendering:mapView];
|
||||||
|
}
|
||||||
|
|
||||||
if (_pipelineState == nil) {
|
if (renderEncoder == nil) {
|
||||||
[self setupRendering:mapView];
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (renderEncoder == nil) {
|
vector_uint2 _viewportSize;
|
||||||
return;
|
_viewportSize.x = resource.mtkView.drawableSize.width;
|
||||||
}
|
_viewportSize.y = resource.mtkView.drawableSize.height;
|
||||||
|
|
||||||
|
Vertex triangleVerticesWithColor[] = {
|
||||||
|
// 2D positions, RGBA colors
|
||||||
|
{{(250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
|
||||||
|
{{(-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale}, {1, 1, 1, _a}},
|
||||||
|
{{(0 + _offsetX) * _scale, (250 + _offsetY) * _scale}, {_r, _g, _b, _a}},
|
||||||
|
};
|
||||||
|
|
||||||
vector_uint2 _viewportSize;
|
[renderEncoder setRenderPipelineState:_pipelineState];
|
||||||
_viewportSize.x = resource.mtkView.drawableSize.width;
|
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
|
||||||
_viewportSize.y = resource.mtkView.drawableSize.height;
|
|
||||||
|
|
||||||
Vertex triangleVerticesWithColor[] = {
|
// Pass in the parameter data.
|
||||||
// 2D positions, RGBA colors
|
[renderEncoder setVertexBytes:triangleVerticesWithColor
|
||||||
{ { (250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a } },
|
length:sizeof(triangleVerticesWithColor)
|
||||||
{ { (-250 + _offsetX) * _scale, (-250 + _offsetY) * _scale }, { 1, 1, 1, _a} },
|
atIndex:0];
|
||||||
{ { (0 + _offsetX) * _scale, (250 + _offsetY) * _scale }, { _r, _g, _b, _a } },
|
|
||||||
};
|
|
||||||
|
|
||||||
[renderEncoder setRenderPipelineState:_pipelineState];
|
[renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:1];
|
||||||
[renderEncoder setDepthStencilState:_depthStencilStateWithoutStencil];
|
|
||||||
|
|
||||||
// Pass in the parameter data.
|
// Draw the triangle.
|
||||||
[renderEncoder setVertexBytes:triangleVerticesWithColor
|
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
|
||||||
length:sizeof(triangleVerticesWithColor)
|
|
||||||
atIndex:0];
|
|
||||||
|
|
||||||
[renderEncoder setVertexBytes:&_viewportSize
|
|
||||||
length:sizeof(_viewportSize)
|
|
||||||
atIndex:1];
|
|
||||||
|
|
||||||
// Draw the triangle.
|
|
||||||
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
|
|
||||||
vertexStart:0
|
|
||||||
vertexCount:3];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
|
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
|
||||||
// This is called before the render call and is a place where
|
// This is called before the render call and is a place where
|
||||||
// any animations/etc can be updated
|
// any animations/etc can be updated
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
||||||
//NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
|
// NSLog(@"Metal Layer Rendering Properties: %@", layerProperties);
|
||||||
|
|
||||||
// These two properties are in the "properties" section of the style sheet and
|
// These two properties are in the "properties" section of the style sheet and
|
||||||
// are passed in at layer creation. These are not expression based properties
|
// are passed in at layer creation. These are not expression based properties
|
||||||
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
|
NSNumber *offsetX = [layerProperties objectForKey:@"offset-x"];
|
||||||
if (offsetX) {
|
if (offsetX) {
|
||||||
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
|
_offsetX = [[layerProperties objectForKey:@"offset-x"] floatValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
|
||||||
|
if (offsetY) {
|
||||||
|
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the paint properties and can be updated each frame or zoom level
|
||||||
|
// change/etc depending on if they are static or expression based/etc.
|
||||||
|
NSNumber *scale = [layerProperties objectForKey:@"scale"];
|
||||||
|
if (scale) {
|
||||||
|
if ([scale isKindOfClass:[NSNumber class]]) {
|
||||||
|
_scale = [scale floatValue];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NSNumber *offsetY = [layerProperties objectForKey:@"offset-y"];
|
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
|
||||||
if (offsetY) {
|
if (fillColor) {
|
||||||
_offsetY = [[layerProperties objectForKey:@"offset-y"] floatValue];
|
// NSLog(@"Fill Color: %@", fillColor);
|
||||||
|
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
|
||||||
|
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
|
||||||
|
NSArray *components = [fillColor componentsSeparatedByString:@","];
|
||||||
|
if ([components count] == 4) {
|
||||||
|
// Convert to float since they come in as 0..255
|
||||||
|
_r = [[components objectAtIndex:0] floatValue] / 255.0;
|
||||||
|
_g = [[components objectAtIndex:1] floatValue] / 255.0;
|
||||||
|
_b = [[components objectAtIndex:2] floatValue] / 255.0;
|
||||||
|
_a = [[components objectAtIndex:3] floatValue];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// These are the paint properties and can be updated each frame or zoom level
|
|
||||||
// change/etc depending on if they are static or expression based/etc.
|
|
||||||
NSNumber *scale = [layerProperties objectForKey:@"scale"];
|
|
||||||
if (scale) {
|
|
||||||
if ([scale isKindOfClass:[NSNumber class]]) {
|
|
||||||
_scale = [scale floatValue];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *fillColor = [layerProperties objectForKey:@"fill-color"];
|
|
||||||
if (fillColor) {
|
|
||||||
//NSLog(@"Fill Color: %@", fillColor);
|
|
||||||
fillColor = [fillColor stringByReplacingOccurrencesOfString:@"rgba(" withString:@""];
|
|
||||||
fillColor = [fillColor stringByReplacingOccurrencesOfString:@")" withString:@""];
|
|
||||||
NSArray *components = [fillColor componentsSeparatedByString:@","];
|
|
||||||
if ([components count] == 4) {
|
|
||||||
// Convert to float since they come in as 0..255
|
|
||||||
_r = [[components objectAtIndex:0] floatValue] / 255.0;
|
|
||||||
_g = [[components objectAtIndex:1] floatValue] / 255.0;
|
|
||||||
_b = [[components objectAtIndex:2] floatValue] / 255.0;
|
|
||||||
_a = [[components objectAtIndex:3] floatValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,59 +9,56 @@ namespace platform {
|
|||||||
|
|
||||||
class Collator::Impl {
|
class Collator::Impl {
|
||||||
public:
|
public:
|
||||||
Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
|
Impl(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
|
||||||
: options((caseSensitive ? 0 : NSCaseInsensitiveSearch) |
|
: options((caseSensitive ? 0 : NSCaseInsensitiveSearch) |
|
||||||
(diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch))
|
(diacriticSensitive ? 0 : NSDiacriticInsensitiveSearch)),
|
||||||
, locale(locale_ ?
|
locale(locale_ ? [[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())]
|
||||||
[[NSLocale alloc] initWithLocaleIdentifier:@((*locale_).c_str())] :
|
: [NSLocale currentLocale]) {}
|
||||||
[NSLocale currentLocale])
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool operator==(const Impl& other) const {
|
bool operator==(const Impl& other) const {
|
||||||
return options == other.options &&
|
return options == other.options &&
|
||||||
[[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]];
|
[[locale localeIdentifier] isEqualToString:[other.locale localeIdentifier]];
|
||||||
}
|
}
|
||||||
|
|
||||||
int compare(const std::string& lhs, const std::string& rhs) const {
|
int compare(const std::string& lhs, const std::string& rhs) const {
|
||||||
NSString* nsLhs = @(lhs.c_str());
|
NSString* nsLhs = @(lhs.c_str());
|
||||||
NSString* nsRhs = @(rhs.c_str());
|
NSString* nsRhs = @(rhs.c_str());
|
||||||
// Limiting the compare range to the length of the LHS seems weird, but
|
// Limiting the compare range to the length of the LHS seems weird, but
|
||||||
// experimentally we've checked that if LHS is a prefix of RHS compare returns -1
|
// experimentally we've checked that if LHS is a prefix of RHS compare returns -1
|
||||||
// https://developer.apple.com/documentation/foundation/nsstring/1414561-compare
|
// https://developer.apple.com/documentation/foundation/nsstring/1414561-compare
|
||||||
NSRange compareRange = NSMakeRange(0, nsLhs.length);
|
NSRange compareRange = NSMakeRange(0, nsLhs.length);
|
||||||
|
|
||||||
return static_cast<int>([nsLhs compare:nsRhs options:options range:compareRange locale:locale]);
|
return static_cast<int>([nsLhs compare:nsRhs options:options range:compareRange locale:locale]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string resolvedLocale() const {
|
||||||
|
// Following documentation at:
|
||||||
|
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9
|
||||||
|
// We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format
|
||||||
|
// may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47
|
||||||
|
// compliant. Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is
|
||||||
|
// used by
|
||||||
|
// `currentLocale`, we don't want to return the underscore.
|
||||||
|
return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"]
|
||||||
|
.UTF8String;
|
||||||
|
}
|
||||||
|
|
||||||
std::string resolvedLocale() const {
|
|
||||||
// Following documentation at:
|
|
||||||
// https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html#//apple_ref/doc/uid/10000171i-CH15-SW9
|
|
||||||
// We expect NSLocale to accept BCP 47 tags as localeIdentifier inputs, but the output format
|
|
||||||
// may append the region tag with an "_". Changing that to a "-" makes the identifier BCP 47 compliant.
|
|
||||||
// Experimentally, "zh-Hans-HK" and "zh-Hans_HK" both round trip -- if the second is used by
|
|
||||||
// `currentLocale`, we don't want to return the underscore.
|
|
||||||
return [[locale localeIdentifier] stringByReplacingOccurrencesOfString:@"_" withString:@"-"].UTF8String;
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
NSStringCompareOptions options;
|
NSStringCompareOptions options;
|
||||||
NSLocale* locale;
|
NSLocale* locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
Collator::Collator(bool caseSensitive, bool diacriticSensitive, const std::optional<std::string>& locale_)
|
Collator::Collator(bool caseSensitive, bool diacriticSensitive,
|
||||||
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_))
|
const std::optional<std::string>& locale_)
|
||||||
{}
|
: impl(std::make_shared<Impl>(caseSensitive, diacriticSensitive, locale_)) {}
|
||||||
|
|
||||||
bool Collator::operator==(const Collator& other) const {
|
bool Collator::operator==(const Collator& other) const { return *impl == *(other.impl); }
|
||||||
return *impl == *(other.impl);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Collator::compare(const std::string& lhs, const std::string& rhs) const {
|
int Collator::compare(const std::string& lhs, const std::string& rhs) const {
|
||||||
return impl->compare(lhs, rhs);
|
return impl->compare(lhs, rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Collator::resolvedLocale() const {
|
std::string Collator::resolvedLocale() const { return impl->resolvedLocale(); }
|
||||||
return impl->resolvedLocale();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace platform
|
} // namespace platform
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
#include <mbgl/storage/resource_options.hpp>
|
#include <mbgl/storage/resource_options.hpp>
|
||||||
#include <mbgl/storage/response.hpp>
|
#include <mbgl/storage/response.hpp>
|
||||||
|
|
||||||
|
#include <mbgl/util/async_request.hpp>
|
||||||
|
#include <mbgl/util/async_task.hpp>
|
||||||
#include <mbgl/util/client_options.hpp>
|
#include <mbgl/util/client_options.hpp>
|
||||||
#include <mbgl/util/http_header.hpp>
|
#include <mbgl/util/http_header.hpp>
|
||||||
#include <mbgl/util/async_task.hpp>
|
|
||||||
#include <mbgl/util/async_request.hpp>
|
|
||||||
#include <mbgl/util/version.hpp>
|
#include <mbgl/util/version.hpp>
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
#include <mbgl/interface/native_apple_interface.h>
|
#include <mbgl/interface/native_apple_interface.h>
|
||||||
#include <mutex>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
@interface MBGLBundleCanary : NSObject
|
@interface MBGLBundleCanary : NSObject
|
||||||
@end
|
@end
|
||||||
@@ -26,422 +26,422 @@ namespace mbgl {
|
|||||||
// Data that is shared between the requesting thread and the thread running the completion handler.
|
// Data that is shared between the requesting thread and the thread running the completion handler.
|
||||||
class HTTPRequestShared {
|
class HTTPRequestShared {
|
||||||
public:
|
public:
|
||||||
HTTPRequestShared(Response& response_, util::AsyncTask& async_)
|
HTTPRequestShared(Response &response_, util::AsyncTask &async_)
|
||||||
: response(response_),
|
: response(response_), async(async_) {}
|
||||||
async(async_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void notify(const Response& response_) {
|
void notify(const Response &response_) {
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
response = response_;
|
response = response_;
|
||||||
async.send();
|
async.send();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
bool cancelled = false;
|
bool cancelled = false;
|
||||||
|
|
||||||
Response& response;
|
Response &response;
|
||||||
util::AsyncTask& async;
|
util::AsyncTask &async;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HTTPRequest : public AsyncRequest {
|
class HTTPRequest : public AsyncRequest {
|
||||||
public:
|
public:
|
||||||
HTTPRequest(FileSource::Callback callback_)
|
HTTPRequest(FileSource::Callback callback_)
|
||||||
: shared(std::make_shared<HTTPRequestShared>(response, async)),
|
: shared(std::make_shared<HTTPRequestShared>(response, async)), callback(callback_) {}
|
||||||
callback(callback_) {
|
|
||||||
}
|
|
||||||
|
|
||||||
~HTTPRequest() override {
|
~HTTPRequest() override {
|
||||||
shared->cancel();
|
shared->cancel();
|
||||||
if (task) {
|
if (task) {
|
||||||
[task cancel];
|
[task cancel];
|
||||||
task = nil;
|
task = nil;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<HTTPRequestShared> shared;
|
std::shared_ptr<HTTPRequestShared> shared;
|
||||||
NSURLSessionDataTask* task = nil;
|
NSURLSessionDataTask *task = nil;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileSource::Callback callback;
|
FileSource::Callback callback;
|
||||||
Response response;
|
Response response;
|
||||||
|
|
||||||
util::AsyncTask async { [this] {
|
util::AsyncTask async{[this] {
|
||||||
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
|
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
|
||||||
auto callback_ = callback;
|
auto callback_ = callback;
|
||||||
auto response_ = response;
|
auto response_ = response;
|
||||||
callback_(response_);
|
callback_(response_);
|
||||||
} };
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
class HTTPFileSource::Impl {
|
class HTTPFileSource::Impl {
|
||||||
public:
|
public:
|
||||||
Impl(const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_)
|
Impl(const ResourceOptions &resourceOptions_, const ClientOptions &clientOptions_)
|
||||||
: resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {
|
: resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {
|
||||||
NSURLSessionConfiguration *sessionConfig = MLNNativeNetworkManager.sharedManager.sessionConfiguration;
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
MLNNativeNetworkManager.sharedManager.sessionConfiguration;
|
||||||
|
session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||||
|
|
||||||
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
|
if (sessionConfig.HTTPAdditionalHeaders[@"User-Agent"] == nil) {
|
||||||
userAgent = getUserAgent();
|
userAgent = getUserAgent();
|
||||||
} else {
|
} else {
|
||||||
userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
|
userAgent = sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setResourceOptions(ResourceOptions options);
|
void setResourceOptions(ResourceOptions options);
|
||||||
ResourceOptions getResourceOptions();
|
ResourceOptions getResourceOptions();
|
||||||
|
|
||||||
void setClientOptions(ClientOptions options);
|
void setClientOptions(ClientOptions options);
|
||||||
ClientOptions getClientOptions();
|
ClientOptions getClientOptions();
|
||||||
|
|
||||||
NSURLSession* session = nil;
|
NSURLSession *session = nil;
|
||||||
NSString* userAgent = nil;
|
NSString *userAgent = nil;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NSString* getUserAgent() const;
|
NSString *getUserAgent() const;
|
||||||
NSBundle* getSDKBundle() const;
|
NSBundle *getSDKBundle() const;
|
||||||
ResourceOptions resourceOptions;
|
ResourceOptions resourceOptions;
|
||||||
ClientOptions clientOptions;
|
ClientOptions clientOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) {
|
void HTTPFileSource::Impl::setResourceOptions(ResourceOptions options) {
|
||||||
resourceOptions = options;
|
resourceOptions = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceOptions HTTPFileSource::Impl::getResourceOptions() {
|
ResourceOptions HTTPFileSource::Impl::getResourceOptions() { return resourceOptions.clone(); }
|
||||||
return resourceOptions.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTTPFileSource::Impl::setClientOptions(ClientOptions options) {
|
void HTTPFileSource::Impl::setClientOptions(ClientOptions options) { clientOptions = options; }
|
||||||
clientOptions = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientOptions HTTPFileSource::Impl::getClientOptions() {
|
ClientOptions HTTPFileSource::Impl::getClientOptions() { return clientOptions.clone(); }
|
||||||
return clientOptions.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *HTTPFileSource::Impl::getUserAgent() const {
|
NSString *HTTPFileSource::Impl::getUserAgent() const {
|
||||||
NSMutableArray *userAgentComponents = [NSMutableArray array];
|
NSMutableArray *userAgentComponents = [NSMutableArray array];
|
||||||
|
|
||||||
NSBundle *appBundle = [NSBundle mainBundle];
|
NSBundle *appBundle = [NSBundle mainBundle];
|
||||||
if (appBundle) {
|
if (appBundle) {
|
||||||
NSString *appName = appBundle.infoDictionary[@"CFBundleName"];
|
NSString *appName = appBundle.infoDictionary[@"CFBundleName"];
|
||||||
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@",
|
[userAgentComponents
|
||||||
appName.length ? appName : appBundle.infoDictionary[@"CFBundleIdentifier"],
|
addObject:[NSString
|
||||||
appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
|
stringWithFormat:@"%@/%@",
|
||||||
} else {
|
appName.length
|
||||||
[userAgentComponents addObject:[NSProcessInfo processInfo].processName];
|
? appName
|
||||||
|
: appBundle.infoDictionary[@"CFBundleIdentifier"],
|
||||||
|
appBundle.infoDictionary[@"CFBundleShortVersionString"]]];
|
||||||
|
} else {
|
||||||
|
[userAgentComponents addObject:[NSProcessInfo processInfo].processName];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle();
|
||||||
|
if (sdkBundle) {
|
||||||
|
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
|
||||||
|
if (versionString) {
|
||||||
|
[userAgentComponents
|
||||||
|
addObject:[NSString stringWithFormat:@"%@/%@", sdkBundle.infoDictionary[@"CFBundleName"],
|
||||||
|
versionString]];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NSBundle *sdkBundle = HTTPFileSource::Impl::getSDKBundle();
|
// Avoid %s here because it inserts hidden bidirectional markers on macOS when the system
|
||||||
if (sdkBundle) {
|
// language is set to a right-to-left language.
|
||||||
NSString *versionString = sdkBundle.infoDictionary[@"CFBundleShortVersionString"];
|
[userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
|
||||||
if (versionString) {
|
@(mbgl::version::revision)]];
|
||||||
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@",
|
|
||||||
sdkBundle.infoDictionary[@"CFBundleName"], versionString]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Avoid %s here because it inserts hidden bidirectional markers on macOS when the system
|
NSString *systemName = @"Darwin";
|
||||||
// language is set to a right-to-left language.
|
|
||||||
[userAgentComponents addObject:[NSString stringWithFormat:@"MapLibreNative/0.0.0 (%@)",
|
|
||||||
@(mbgl::version::revision)]];
|
|
||||||
|
|
||||||
NSString *systemName = @"Darwin";
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
systemName = @"iOS";
|
systemName = @"iOS";
|
||||||
#elif TARGET_OS_MAC
|
#elif TARGET_OS_MAC
|
||||||
systemName = @"macOS";
|
systemName = @"macOS";
|
||||||
#elif TARGET_OS_WATCH
|
#elif TARGET_OS_WATCH
|
||||||
systemName = @"watchOS";
|
systemName = @"watchOS";
|
||||||
#elif TARGET_OS_TV
|
#elif TARGET_OS_TV
|
||||||
systemName = @"tvOS";
|
systemName = @"tvOS";
|
||||||
#endif
|
#endif
|
||||||
#if TARGET_OS_SIMULATOR
|
#if TARGET_OS_SIMULATOR
|
||||||
systemName = [systemName stringByAppendingString:@" Simulator"];
|
systemName = [systemName stringByAppendingString:@" Simulator"];
|
||||||
#endif
|
#endif
|
||||||
NSString *systemVersion = nil;
|
NSString *systemVersion = nil;
|
||||||
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
|
if ([NSProcessInfo instancesRespondToSelector:@selector(operatingSystemVersion)]) {
|
||||||
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
|
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
|
||||||
systemVersion = [NSString stringWithFormat:@"%ld.%ld.%ld",
|
systemVersion =
|
||||||
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion];
|
[NSString stringWithFormat:@"%ld.%ld.%ld", (long)osVersion.majorVersion,
|
||||||
}
|
(long)osVersion.minorVersion, (long)osVersion.patchVersion];
|
||||||
if (systemVersion) {
|
}
|
||||||
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
|
if (systemVersion) {
|
||||||
}
|
[userAgentComponents addObject:[NSString stringWithFormat:@"%@/%@", systemName, systemVersion]];
|
||||||
|
}
|
||||||
|
|
||||||
NSString *cpu = nil;
|
NSString *cpu = nil;
|
||||||
#if TARGET_CPU_X86
|
#if TARGET_CPU_X86
|
||||||
cpu = @"x86";
|
cpu = @"x86";
|
||||||
#elif TARGET_CPU_X86_64
|
#elif TARGET_CPU_X86_64
|
||||||
cpu = @"x86_64";
|
cpu = @"x86_64";
|
||||||
#elif TARGET_CPU_ARM
|
#elif TARGET_CPU_ARM
|
||||||
cpu = @"arm";
|
cpu = @"arm";
|
||||||
#elif TARGET_CPU_ARM64
|
#elif TARGET_CPU_ARM64
|
||||||
cpu = @"arm64";
|
cpu = @"arm64";
|
||||||
#endif
|
#endif
|
||||||
if (cpu) {
|
if (cpu) {
|
||||||
[userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]];
|
[userAgentComponents addObject:[NSString stringWithFormat:@"(%@)", cpu]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [userAgentComponents componentsJoinedByString:@" "];
|
return [userAgentComponents componentsJoinedByString:@" "];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSBundle *HTTPFileSource::Impl::getSDKBundle() const {
|
NSBundle *HTTPFileSource::Impl::getSDKBundle() const {
|
||||||
NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]];
|
NSBundle *bundle = [NSBundle bundleForClass:[MBGLBundleCanary class]];
|
||||||
if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
|
if (bundle && ![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
|
||||||
// For static frameworks, the class is contained in the application bundle rather than the
|
// For static frameworks, the class is contained in the application bundle rather than the
|
||||||
// framework bundle.
|
// framework bundle.
|
||||||
bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath
|
bundle = [NSBundle bundleWithPath:[bundle.privateFrameworksPath
|
||||||
stringByAppendingPathComponent:@"Mapbox.framework"]];
|
stringByAppendingPathComponent:@"Mapbox.framework"]];
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPFileSource::HTTPFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions)
|
HTTPFileSource::HTTPFileSource(const ResourceOptions &resourceOptions,
|
||||||
: impl(std::make_unique<Impl>(resourceOptions, clientOptions)) {
|
const ClientOptions &clientOptions)
|
||||||
}
|
: impl(std::make_unique<Impl>(resourceOptions, clientOptions)) {}
|
||||||
|
|
||||||
HTTPFileSource::~HTTPFileSource() = default;
|
HTTPFileSource::~HTTPFileSource() = default;
|
||||||
|
|
||||||
MLN_APPLE_EXPORT
|
MLN_APPLE_EXPORT
|
||||||
BOOL isValidMapboxEndpoint(NSURL *url) {
|
BOOL isValidMapboxEndpoint(NSURL *url) {
|
||||||
return ([url.host isEqualToString:@"mapbox.com"] ||
|
return ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"] ||
|
||||||
[url.host hasSuffix:@".mapbox.com"] ||
|
[url.host isEqualToString:@"mapbox.cn"] || [url.host hasSuffix:@".mapbox.cn"]);
|
||||||
[url.host isEqualToString:@"mapbox.cn"] ||
|
|
||||||
[url.host hasSuffix:@".mapbox.cn"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MLN_APPLE_EXPORT
|
MLN_APPLE_EXPORT
|
||||||
NSURL *resourceURL(const Resource& resource) {
|
NSURL *resourceURL(const Resource &resource) {
|
||||||
|
NSURL *url = [NSURL URLWithString:@(resource.url.c_str())];
|
||||||
NSURL *url = [NSURL URLWithString:@(resource.url.c_str())];
|
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
if (isValidMapboxEndpoint(url)) {
|
if (isValidMapboxEndpoint(url)) {
|
||||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url
|
||||||
NSMutableArray *queryItems = [NSMutableArray array];
|
resolvingAgainstBaseURL:NO];
|
||||||
|
NSMutableArray *queryItems = [NSMutableArray array];
|
||||||
|
|
||||||
if (resource.usage == Resource::Usage::Offline) {
|
if (resource.usage == Resource::Usage::Offline) {
|
||||||
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]];
|
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"offline" value:@"true"]];
|
||||||
}
|
|
||||||
|
|
||||||
if (components.queryItems) {
|
|
||||||
[queryItems addObjectsFromArray:components.queryItems];
|
|
||||||
}
|
|
||||||
|
|
||||||
components.queryItems = queryItems;
|
|
||||||
url = components.URL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (components.queryItems) {
|
||||||
|
[queryItems addObjectsFromArray:components.queryItems];
|
||||||
|
}
|
||||||
|
|
||||||
|
components.queryItems = queryItems;
|
||||||
|
url = components.URL;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, std::function<void(Response)> callback) {
|
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource &resource,
|
||||||
auto request = std::make_unique<HTTPRequest>(std::move(callback));
|
std::function<void(Response)> callback) {
|
||||||
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below.
|
auto request = std::make_unique<HTTPRequest>(std::move(callback));
|
||||||
|
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion
|
||||||
|
// handler block below.
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSURL *url = resourceURL(resource);
|
NSURL *url = resourceURL(resource);
|
||||||
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath];
|
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting URI: %@", url.relativePath];
|
||||||
|
|
||||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
|
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
|
||||||
if (resource.priorEtag) {
|
if (resource.priorEtag) {
|
||||||
[req addValue:@(resource.priorEtag->c_str())
|
[req addValue:@(resource.priorEtag->c_str()) forHTTPHeaderField:@"If-None-Match"];
|
||||||
forHTTPHeaderField:@"If-None-Match"];
|
} else if (resource.priorModified) {
|
||||||
} else if (resource.priorModified) {
|
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
|
||||||
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
|
forHTTPHeaderField:@"If-Modified-Since"];
|
||||||
forHTTPHeaderField:@"If-Modified-Since"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resource.dataRange) {
|
|
||||||
NSString *rangeHeader = [NSString stringWithFormat:@"bytes=%lld-%lld",
|
|
||||||
static_cast<long long>(resource.dataRange->first),
|
|
||||||
static_cast<long long>(resource.dataRange->second)];
|
|
||||||
[req setValue:rangeHeader forHTTPHeaderField:@"Range"];
|
|
||||||
}
|
|
||||||
|
|
||||||
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
|
|
||||||
|
|
||||||
const bool isTile = resource.kind == mbgl::Resource::Kind::Tile;
|
|
||||||
|
|
||||||
if (isTile) {
|
|
||||||
[MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"];
|
|
||||||
}
|
|
||||||
|
|
||||||
__block NSURLSession *session = nil;
|
|
||||||
|
|
||||||
// Use the delegate's session if there is one, otherwise use the one that
|
|
||||||
// was created when this class was constructed.
|
|
||||||
MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager;
|
|
||||||
if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) {
|
|
||||||
session = [networkManager.delegate sessionForNetworkManager:networkManager];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
session = impl->session;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(session);
|
|
||||||
|
|
||||||
if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) {
|
|
||||||
req = [networkManager.delegate willSendRequest:req];
|
|
||||||
}
|
|
||||||
|
|
||||||
request->task = [session
|
|
||||||
dataTaskWithRequest:req
|
|
||||||
completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) {
|
|
||||||
session = nil;
|
|
||||||
|
|
||||||
if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
|
|
||||||
MLNInternalNetworkResponse *networkResponse = [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error];
|
|
||||||
networkResponse = [networkManager.delegate didReceiveResponse:networkResponse];
|
|
||||||
data = networkResponse.data;
|
|
||||||
res = networkResponse.response;
|
|
||||||
error = networkResponse.error;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (error && [error code] == NSURLErrorCancelled) {
|
|
||||||
[MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res];
|
|
||||||
Response response;
|
|
||||||
using Error = Response::Error;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
[MLNNativeNetworkManager.sharedManager errorLog:@"Requesting: %@ failed with error: %@", req.URL, error.debugDescription];
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
response.data =
|
|
||||||
std::make_shared<std::string>((const char*)[data bytes], [data length]);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ([error code]) {
|
|
||||||
case NSURLErrorBadServerResponse: // 5xx errors
|
|
||||||
response.error = std::make_unique<Error>(
|
|
||||||
Error::Reason::Server, [[error localizedDescription] UTF8String]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NSURLErrorNetworkConnectionLost:
|
|
||||||
case NSURLErrorCannotFindHost:
|
|
||||||
case NSURLErrorCannotConnectToHost:
|
|
||||||
case NSURLErrorDNSLookupFailed:
|
|
||||||
case NSURLErrorNotConnectedToInternet:
|
|
||||||
case NSURLErrorInternationalRoamingOff:
|
|
||||||
case NSURLErrorCallIsActive:
|
|
||||||
case NSURLErrorDataNotAllowed:
|
|
||||||
case NSURLErrorTimedOut:
|
|
||||||
response.error = std::make_unique<Error>(
|
|
||||||
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
response.error = std::make_unique<Error>(
|
|
||||||
Error::Reason::Other, [[error localizedDescription] UTF8String]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
||||||
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
|
|
||||||
[MLNNativeNetworkManager.sharedManager debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath, responseCode];
|
|
||||||
|
|
||||||
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
|
|
||||||
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
|
|
||||||
if (cache_control) {
|
|
||||||
const auto cc = http::CacheControl::parse([cache_control UTF8String]);
|
|
||||||
response.expires = cc.toTimePoint();
|
|
||||||
response.mustRevalidate = cc.mustRevalidate;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *expires = [headers objectForKey:@"Expires"];
|
|
||||||
if (expires) {
|
|
||||||
response.expires = util::parseTimestamp([expires UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
|
|
||||||
if (last_modified) {
|
|
||||||
response.modified = util::parseTimestamp([last_modified UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *etag = [headers objectForKey:@"ETag"];
|
|
||||||
if (etag) {
|
|
||||||
response.etag = std::string([etag UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (responseCode == 200 || responseCode == 206) {
|
|
||||||
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
|
|
||||||
} else if (responseCode == 204 || (responseCode == 404 && isTile)) {
|
|
||||||
response.noContent = true;
|
|
||||||
} else if (responseCode == 304) {
|
|
||||||
response.notModified = true;
|
|
||||||
} else if (responseCode == 404) {
|
|
||||||
response.error =
|
|
||||||
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
|
|
||||||
} else if (responseCode == 429) {
|
|
||||||
// Get the standard header
|
|
||||||
std::optional<std::string> retryAfter;
|
|
||||||
NSString *retryAfterHeader = headers[@"Retry-After"];
|
|
||||||
if (retryAfterHeader) {
|
|
||||||
retryAfter = std::string([retryAfterHeader UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback mapbox specific header
|
|
||||||
std::optional<std::string> xRateLimitReset;
|
|
||||||
NSString *xReset = headers[@"x-rate-limit-reset"];
|
|
||||||
if (xReset) {
|
|
||||||
xRateLimitReset = std::string([xReset UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.error = std::make_unique<Error>(Error::Reason::RateLimit, "HTTP status code 429", http::parseRetryHeaders(retryAfter, xRateLimitReset));
|
|
||||||
} else if (responseCode >= 500 && responseCode < 600) {
|
|
||||||
response.error =
|
|
||||||
std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } +
|
|
||||||
std::to_string(responseCode));
|
|
||||||
} else {
|
|
||||||
response.error =
|
|
||||||
std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } +
|
|
||||||
std::to_string(responseCode));
|
|
||||||
}
|
|
||||||
} else if ([url isFileURL]) {
|
|
||||||
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
|
|
||||||
} else {
|
|
||||||
// This should never happen.
|
|
||||||
response.error = std::make_unique<Error>(Error::Reason::Other,
|
|
||||||
"Response class is not NSHTTPURLResponse");
|
|
||||||
}
|
|
||||||
|
|
||||||
shared->notify(response);
|
|
||||||
}];
|
|
||||||
|
|
||||||
[request->task resume];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::move(request);
|
if (resource.dataRange) {
|
||||||
|
NSString *rangeHeader = [NSString
|
||||||
|
stringWithFormat:@"bytes=%lld-%lld", static_cast<long long>(resource.dataRange->first),
|
||||||
|
static_cast<long long>(resource.dataRange->second)];
|
||||||
|
[req setValue:rangeHeader forHTTPHeaderField:@"Range"];
|
||||||
|
}
|
||||||
|
|
||||||
|
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
|
||||||
|
|
||||||
|
const bool isTile = resource.kind == mbgl::Resource::Kind::Tile;
|
||||||
|
|
||||||
|
if (isTile) {
|
||||||
|
[MLNNativeNetworkManager.sharedManager startDownloadEvent:url.relativePath type:@"tile"];
|
||||||
|
}
|
||||||
|
|
||||||
|
__block NSURLSession *session = nil;
|
||||||
|
|
||||||
|
// Use the delegate's session if there is one, otherwise use the one that
|
||||||
|
// was created when this class was constructed.
|
||||||
|
MLNNativeNetworkManager *networkManager = MLNNativeNetworkManager.sharedManager;
|
||||||
|
if ([networkManager.delegate respondsToSelector:@selector(sessionForNetworkManager:)]) {
|
||||||
|
session = [networkManager.delegate sessionForNetworkManager:networkManager];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
session = impl->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(session);
|
||||||
|
|
||||||
|
if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) {
|
||||||
|
req = [networkManager.delegate willSendRequest:req];
|
||||||
|
}
|
||||||
|
|
||||||
|
request->task = [session
|
||||||
|
dataTaskWithRequest:req
|
||||||
|
completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) {
|
||||||
|
session = nil;
|
||||||
|
|
||||||
|
if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
|
||||||
|
MLNInternalNetworkResponse *networkResponse =
|
||||||
|
[MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error];
|
||||||
|
networkResponse = [networkManager.delegate didReceiveResponse:networkResponse];
|
||||||
|
data = networkResponse.data;
|
||||||
|
res = networkResponse.response;
|
||||||
|
error = networkResponse.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && [error code] == NSURLErrorCancelled) {
|
||||||
|
[MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[MLNNativeNetworkManager.sharedManager stopDownloadEventForResponse:res];
|
||||||
|
Response response;
|
||||||
|
using Error = Response::Error;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
[MLNNativeNetworkManager.sharedManager
|
||||||
|
errorLog:@"Requesting: %@ failed with error: %@", req.URL,
|
||||||
|
error.debugDescription];
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
response.data =
|
||||||
|
std::make_shared<std::string>((const char *)[data bytes], [data length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ([error code]) {
|
||||||
|
case NSURLErrorBadServerResponse: // 5xx errors
|
||||||
|
response.error = std::make_unique<Error>(
|
||||||
|
Error::Reason::Server, [[error localizedDescription] UTF8String]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NSURLErrorNetworkConnectionLost:
|
||||||
|
case NSURLErrorCannotFindHost:
|
||||||
|
case NSURLErrorCannotConnectToHost:
|
||||||
|
case NSURLErrorDNSLookupFailed:
|
||||||
|
case NSURLErrorNotConnectedToInternet:
|
||||||
|
case NSURLErrorInternationalRoamingOff:
|
||||||
|
case NSURLErrorCallIsActive:
|
||||||
|
case NSURLErrorDataNotAllowed:
|
||||||
|
case NSURLErrorTimedOut:
|
||||||
|
response.error = std::make_unique<Error>(
|
||||||
|
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
response.error = std::make_unique<Error>(
|
||||||
|
Error::Reason::Other, [[error localizedDescription] UTF8String]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||||
|
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
|
||||||
|
[MLNNativeNetworkManager.sharedManager
|
||||||
|
debugLog:@"Requesting %@ returned responseCode: %lu", res.URL.relativePath,
|
||||||
|
responseCode];
|
||||||
|
|
||||||
|
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
|
||||||
|
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
|
||||||
|
if (cache_control) {
|
||||||
|
const auto cc = http::CacheControl::parse([cache_control UTF8String]);
|
||||||
|
response.expires = cc.toTimePoint();
|
||||||
|
response.mustRevalidate = cc.mustRevalidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *expires = [headers objectForKey:@"Expires"];
|
||||||
|
if (expires) {
|
||||||
|
response.expires = util::parseTimestamp([expires UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
|
||||||
|
if (last_modified) {
|
||||||
|
response.modified = util::parseTimestamp([last_modified UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *etag = [headers objectForKey:@"ETag"];
|
||||||
|
if (etag) {
|
||||||
|
response.etag = std::string([etag UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseCode == 200 || responseCode == 206) {
|
||||||
|
response.data =
|
||||||
|
std::make_shared<std::string>((const char *)[data bytes], [data length]);
|
||||||
|
} else if (responseCode == 204 || (responseCode == 404 && isTile)) {
|
||||||
|
response.noContent = true;
|
||||||
|
} else if (responseCode == 304) {
|
||||||
|
response.notModified = true;
|
||||||
|
} else if (responseCode == 404) {
|
||||||
|
response.error =
|
||||||
|
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
|
||||||
|
} else if (responseCode == 429) {
|
||||||
|
// Get the standard header
|
||||||
|
std::optional<std::string> retryAfter;
|
||||||
|
NSString *retryAfterHeader = headers[@"Retry-After"];
|
||||||
|
if (retryAfterHeader) {
|
||||||
|
retryAfter = std::string([retryAfterHeader UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback mapbox specific header
|
||||||
|
std::optional<std::string> xRateLimitReset;
|
||||||
|
NSString *xReset = headers[@"x-rate-limit-reset"];
|
||||||
|
if (xReset) {
|
||||||
|
xRateLimitReset = std::string([xReset UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.error =
|
||||||
|
std::make_unique<Error>(Error::Reason::RateLimit, "HTTP status code 429",
|
||||||
|
http::parseRetryHeaders(retryAfter, xRateLimitReset));
|
||||||
|
} else if (responseCode >= 500 && responseCode < 600) {
|
||||||
|
response.error = std::make_unique<Error>(
|
||||||
|
Error::Reason::Server,
|
||||||
|
std::string{"HTTP status code "} + std::to_string(responseCode));
|
||||||
|
} else {
|
||||||
|
response.error =
|
||||||
|
std::make_unique<Error>(Error::Reason::Other, std::string{"HTTP status code "} +
|
||||||
|
std::to_string(responseCode));
|
||||||
|
}
|
||||||
|
} else if ([url isFileURL]) {
|
||||||
|
response.data =
|
||||||
|
std::make_shared<std::string>((const char *)[data bytes], [data length]);
|
||||||
|
} else {
|
||||||
|
// This should never happen.
|
||||||
|
response.error = std::make_unique<Error>(Error::Reason::Other,
|
||||||
|
"Response class is not NSHTTPURLResponse");
|
||||||
|
}
|
||||||
|
|
||||||
|
shared->notify(response);
|
||||||
|
}];
|
||||||
|
|
||||||
|
[request->task resume];
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPFileSource::setResourceOptions(ResourceOptions options) {
|
void HTTPFileSource::setResourceOptions(ResourceOptions options) {
|
||||||
impl->setResourceOptions(options.clone());
|
impl->setResourceOptions(options.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceOptions HTTPFileSource::getResourceOptions() {
|
ResourceOptions HTTPFileSource::getResourceOptions() { return impl->getResourceOptions(); }
|
||||||
return impl->getResourceOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HTTPFileSource::setClientOptions(ClientOptions options) {
|
void HTTPFileSource::setClientOptions(ClientOptions options) {
|
||||||
impl->setClientOptions(options.clone());
|
impl->setClientOptions(options.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientOptions HTTPFileSource::getClientOptions() {
|
ClientOptions HTTPFileSource::getClientOptions() { return impl->getClientOptions(); }
|
||||||
return impl->getClientOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -12,85 +12,85 @@ using CGColorSpaceHandle = CFHandle<CGColorSpaceRef, CGColorSpaceRef, CGColorSpa
|
|||||||
using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>;
|
using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>;
|
||||||
|
|
||||||
CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src) {
|
CGImageRef CGImageCreateWithMLNPremultipliedImage(mbgl::PremultipliedImage&& src) {
|
||||||
// We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking
|
// We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking
|
||||||
// over ownership of the memory.
|
// over ownership of the memory.
|
||||||
CGDataProviderHandle provider(CGDataProviderCreateWithData(
|
CGDataProviderHandle provider(CGDataProviderCreateWithData(
|
||||||
NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) {
|
NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) {
|
||||||
delete[] reinterpret_cast<const decltype(src.data)::element_type*>(data);
|
delete[] reinterpret_cast<const decltype(src.data)::element_type*>(data);
|
||||||
}));
|
}));
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we successfully created the provider, it will take over management of the memory segment.
|
// If we successfully created the provider, it will take over management of the memory segment.
|
||||||
src.data.release();
|
src.data.release();
|
||||||
|
|
||||||
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
||||||
if (!colorSpace) {
|
if (!colorSpace) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const size_t bitsPerComponent = 8;
|
constexpr const size_t bitsPerComponent = 8;
|
||||||
constexpr const size_t bytesPerPixel = 4;
|
constexpr const size_t bytesPerPixel = 4;
|
||||||
constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel;
|
constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel;
|
||||||
const size_t bytesPerRow = bytesPerPixel * src.size.width;
|
const size_t bytesPerRow = bytesPerPixel * src.size.width;
|
||||||
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
|
||||||
|
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
||||||
|
|
||||||
return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel,
|
return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel, bytesPerRow,
|
||||||
bytesPerRow, *colorSpace,
|
*colorSpace, bitmapInfo, *provider, NULL, false, kCGRenderingIntentDefault);
|
||||||
bitmapInfo, *provider,
|
|
||||||
NULL, false, kCGRenderingIntentDefault);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) {
|
mbgl::PremultipliedImage MLNPremultipliedImageFromCGImage(CGImageRef src) {
|
||||||
const size_t width = CGImageGetWidth(src);
|
const size_t width = CGImageGetWidth(src);
|
||||||
const size_t height = CGImageGetHeight(src);
|
const size_t height = CGImageGetHeight(src);
|
||||||
|
|
||||||
mbgl::PremultipliedImage image({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
|
mbgl::PremultipliedImage image({static_cast<uint32_t>(width), static_cast<uint32_t>(height)});
|
||||||
|
|
||||||
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
||||||
if (!colorSpace) {
|
if (!colorSpace) {
|
||||||
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
|
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const size_t bitsPerComponent = 8;
|
constexpr const size_t bitsPerComponent = 8;
|
||||||
constexpr const size_t bytesPerPixel = 4;
|
constexpr const size_t bytesPerPixel = 4;
|
||||||
const size_t bytesPerRow = bytesPerPixel * width;
|
const size_t bytesPerRow = bytesPerPixel * width;
|
||||||
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
|
||||||
|
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
||||||
|
|
||||||
CGContextHandle context(CGBitmapContextCreate(
|
CGContextHandle context(CGBitmapContextCreate(image.data.get(), width, height, bitsPerComponent,
|
||||||
image.data.get(), width, height, bitsPerComponent, bytesPerRow, *colorSpace, bitmapInfo));
|
bytesPerRow, *colorSpace, bitmapInfo));
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw std::runtime_error("CGBitmapContextCreate failed");
|
throw std::runtime_error("CGBitmapContextCreate failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
CGContextSetBlendMode(*context, kCGBlendModeCopy);
|
CGContextSetBlendMode(*context, kCGBlendModeCopy);
|
||||||
CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src);
|
CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src);
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
PremultipliedImage decodeImage(const std::string& source) {
|
PremultipliedImage decodeImage(const std::string& source) {
|
||||||
CFDataHandle data(CFDataCreateWithBytesNoCopy(
|
CFDataHandle data(CFDataCreateWithBytesNoCopy(
|
||||||
kCFAllocatorDefault, reinterpret_cast<const unsigned char*>(source.data()), source.size(),
|
kCFAllocatorDefault, reinterpret_cast<const unsigned char*>(source.data()), source.size(),
|
||||||
kCFAllocatorNull));
|
kCFAllocatorNull));
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw std::runtime_error("CFDataCreateWithBytesNoCopy failed");
|
throw std::runtime_error("CFDataCreateWithBytesNoCopy failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL));
|
CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL));
|
||||||
if (!imageSource) {
|
if (!imageSource) {
|
||||||
throw std::runtime_error("CGImageSourceCreateWithData failed");
|
throw std::runtime_error("CGImageSourceCreateWithData failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL));
|
CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL));
|
||||||
if (!image) {
|
if (!image) {
|
||||||
throw std::runtime_error("CGImageSourceCreateImageAtIndex failed");
|
throw std::runtime_error("CGImageSourceCreateImageAtIndex failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return MLNPremultipliedImageFromCGImage(*image);
|
return MLNPremultipliedImageFromCGImage(*image);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
#include <mbgl/text/local_glyph_rasterizer.hpp>
|
#include <mbgl/text/local_glyph_rasterizer.hpp>
|
||||||
|
#include <mbgl/util/constants.hpp>
|
||||||
#include <mbgl/util/i18n.hpp>
|
#include <mbgl/util/i18n.hpp>
|
||||||
#include <mbgl/util/logging.hpp>
|
#include <mbgl/util/logging.hpp>
|
||||||
#include <mbgl/util/platform.hpp>
|
#include <mbgl/util/platform.hpp>
|
||||||
#include <mbgl/util/constants.hpp>
|
|
||||||
|
|
||||||
#include <mbgl/interface/native_apple_interface.h>
|
#include <mbgl/interface/native_apple_interface.h>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <CoreText/CoreText.h>
|
#import <CoreText/CoreText.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
#import <ImageIO/ImageIO.h>
|
#import <ImageIO/ImageIO.h>
|
||||||
|
|
||||||
#import "CFHandle.hpp"
|
#import "CFHandle.hpp"
|
||||||
@@ -63,114 +63,117 @@ using CTLineRefHandle = CFHandle<CTLineRef, CFTypeRef, CFRelease>;
|
|||||||
*/
|
*/
|
||||||
class LocalGlyphRasterizer::Impl {
|
class LocalGlyphRasterizer::Impl {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
Creates a new rasterizer with the given font names as a fallback.
|
Creates a new rasterizer with the given font names as a fallback.
|
||||||
|
|
||||||
The fallback font names can also be specified in the style as a font stack
|
The fallback font names can also be specified in the style as a font stack
|
||||||
or in the `MLNIdeographicFontFamilyName` key of
|
or in the `MLNIdeographicFontFamilyName` key of
|
||||||
`NSUserDefaults.standardUserDefaults`. The font stack takes precedence,
|
`NSUserDefaults.standardUserDefaults`. The font stack takes precedence,
|
||||||
followed by the `MLNIdeographicFontFamilyName` user default, then finally
|
followed by the `MLNIdeographicFontFamilyName` user default, then finally
|
||||||
the `fallbackFontNames_` parameter as a last resort.
|
the `fallbackFontNames_` parameter as a last resort.
|
||||||
|
|
||||||
@param fallbackFontNames_ A list of font names, one per line. Each font
|
@param fallbackFontNames_ A list of font names, one per line. Each font
|
||||||
name can be the PostScript name or display name of a specific font face
|
name can be the PostScript name or display name of a specific font face
|
||||||
or a font family name. Set this parameter to `nullptr` to disable local
|
or a font family name. Set this parameter to `nullptr` to disable local
|
||||||
glyph rasterization globally. The system font is a good default value to
|
glyph rasterization globally. The system font is a good default value to
|
||||||
pass into this constructor.
|
pass into this constructor.
|
||||||
*/
|
*/
|
||||||
Impl(const std::optional<std::string> fallbackFontNames_)
|
Impl(const std::optional<std::string> fallbackFontNames_) {
|
||||||
{
|
fallbackFontNames =
|
||||||
fallbackFontNames = [[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"];
|
[[NSUserDefaults standardUserDefaults] stringArrayForKey:@"MLNIdeographicFontFamilyName"];
|
||||||
if (fallbackFontNames_) {
|
if (fallbackFontNames_) {
|
||||||
fallbackFontNames = [fallbackFontNames ?: @[] arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str()) componentsSeparatedByString:@"\n"]];
|
fallbackFontNames = [fallbackFontNames ?: @[]
|
||||||
}
|
arrayByAddingObjectsFromArray:[@(fallbackFontNames_->c_str())
|
||||||
|
componentsSeparatedByString:@"\n"]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns whether local glyph rasterization is enabled globally.
|
||||||
|
|
||||||
|
The developer can disable local glyph rasterization by specifying no
|
||||||
|
fallback font names.
|
||||||
|
*/
|
||||||
|
bool isEnabled() { return fallbackFontNames; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
Creates a font descriptor representing the given font stack and any global
|
||||||
|
fallback fonts.
|
||||||
|
|
||||||
|
@param fontStack The font stack that takes precedence.
|
||||||
|
@returns A font descriptor representing the first font in the font stack
|
||||||
|
with a cascade list representing the rest of the fonts in the font stack
|
||||||
|
and any fallback fonts. The font descriptor is not cached.
|
||||||
|
|
||||||
|
@post The caller is responsible for releasing the font descriptor.
|
||||||
|
*/
|
||||||
|
CTFontDescriptorRef createFontDescriptor(const FontStack &fontStack) {
|
||||||
|
NSMutableArray *fontNames =
|
||||||
|
[NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count];
|
||||||
|
for (auto &fontName : fontStack) {
|
||||||
|
// Per the MapLibre Style Spec, the text-font property comes
|
||||||
|
// with these last resort fonts by default, but they shouldn’t take
|
||||||
|
// precedence over any application or system fallback font that may
|
||||||
|
// be more appropriate to the current device.
|
||||||
|
if (fontName != util::LAST_RESORT_ALPHABETIC_FONT &&
|
||||||
|
fontName != util::LAST_RESORT_PAN_UNICODE_FONT) {
|
||||||
|
[fontNames addObject:@(fontName.c_str())];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[fontNames addObjectsFromArray:fallbackFontNames];
|
||||||
|
|
||||||
|
if (!fontNames.count) {
|
||||||
|
NSDictionary *fontAttributes = @{
|
||||||
|
(NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
|
||||||
|
};
|
||||||
|
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Apply the first font name to the returned font descriptor; apply the
|
||||||
Returns whether local glyph rasterization is enabled globally.
|
// rest of the font names to the cascade list.
|
||||||
|
CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject;
|
||||||
|
CFMutableArrayRefHandle fallbackDescriptors(
|
||||||
|
CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks));
|
||||||
|
for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) {
|
||||||
|
NSDictionary *fontAttributes = @{
|
||||||
|
(NSString *)kCTFontSizeAttribute : @(util::ONE_EM),
|
||||||
|
// This attribute is technically supposed to be a font’s
|
||||||
|
// PostScript name, but Core Text will fall back to matching
|
||||||
|
// font display names and font family names.
|
||||||
|
(NSString *)kCTFontNameAttribute : name,
|
||||||
|
};
|
||||||
|
|
||||||
The developer can disable local glyph rasterization by specifying no
|
CTFontDescriptorRefHandle descriptor(
|
||||||
fallback font names.
|
CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
|
||||||
*/
|
CFArrayAppendValue(*fallbackDescriptors, *descriptor);
|
||||||
bool isEnabled() { return fallbackFontNames; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a font descriptor representing the given font stack and any global
|
|
||||||
fallback fonts.
|
|
||||||
|
|
||||||
@param fontStack The font stack that takes precedence.
|
|
||||||
@returns A font descriptor representing the first font in the font stack
|
|
||||||
with a cascade list representing the rest of the fonts in the font stack
|
|
||||||
and any fallback fonts. The font descriptor is not cached.
|
|
||||||
|
|
||||||
@post The caller is responsible for releasing the font descriptor.
|
|
||||||
*/
|
|
||||||
CTFontDescriptorRef createFontDescriptor(const FontStack& fontStack) {
|
|
||||||
NSMutableArray *fontNames = [NSMutableArray arrayWithCapacity:fontStack.size() + fallbackFontNames.count];
|
|
||||||
for (auto& fontName : fontStack) {
|
|
||||||
// Per the MapLibre Style Spec, the text-font property comes
|
|
||||||
// with these last resort fonts by default, but they shouldn’t take
|
|
||||||
// precedence over any application or system fallback font that may
|
|
||||||
// be more appropriate to the current device.
|
|
||||||
if (fontName != util::LAST_RESORT_ALPHABETIC_FONT && fontName != util::LAST_RESORT_PAN_UNICODE_FONT) {
|
|
||||||
[fontNames addObject:@(fontName.c_str())];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[fontNames addObjectsFromArray:fallbackFontNames];
|
|
||||||
|
|
||||||
if (!fontNames.count) {
|
|
||||||
NSDictionary *fontAttributes = @{
|
|
||||||
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM),
|
|
||||||
};
|
|
||||||
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the first font name to the returned font descriptor; apply the
|
|
||||||
// rest of the font names to the cascade list.
|
|
||||||
CFStringRef mainFontName = (__bridge CFStringRef)fontNames.firstObject;
|
|
||||||
CFMutableArrayRefHandle fallbackDescriptors(CFArrayCreateMutable(kCFAllocatorDefault, fontNames.count, &kCFTypeArrayCallBacks));
|
|
||||||
for (NSString *name in [fontNames subarrayWithRange:NSMakeRange(1, fontNames.count - 1)]) {
|
|
||||||
NSDictionary *fontAttributes = @{
|
|
||||||
(NSString *)kCTFontSizeAttribute: @(util::ONE_EM),
|
|
||||||
// This attribute is technically supposed to be a font’s
|
|
||||||
// PostScript name, but Core Text will fall back to matching
|
|
||||||
// font display names and font family names.
|
|
||||||
(NSString *)kCTFontNameAttribute: name,
|
|
||||||
};
|
|
||||||
|
|
||||||
CTFontDescriptorRefHandle descriptor(CTFontDescriptorCreateWithAttributes((CFDictionaryRef)fontAttributes));
|
|
||||||
CFArrayAppendValue(*fallbackDescriptors, *descriptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
CFStringRef keys[] = {
|
|
||||||
kCTFontSizeAttribute,
|
|
||||||
kCTFontNameAttribute,
|
|
||||||
kCTFontCascadeListAttribute,
|
|
||||||
};
|
|
||||||
CFTypeRef values[] = {
|
|
||||||
(__bridge CFNumberRef)@(util::ONE_EM),
|
|
||||||
mainFontName,
|
|
||||||
*fallbackDescriptors,
|
|
||||||
};
|
|
||||||
|
|
||||||
CFDictionaryRefHandle attributes(
|
|
||||||
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
|
|
||||||
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
|
|
||||||
&kCFTypeDictionaryKeyCallBacks,
|
|
||||||
&kCFTypeDictionaryValueCallBacks));
|
|
||||||
return CTFontDescriptorCreateWithAttributes(*attributes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CFStringRef keys[] = {
|
||||||
|
kCTFontSizeAttribute,
|
||||||
|
kCTFontNameAttribute,
|
||||||
|
kCTFontCascadeListAttribute,
|
||||||
|
};
|
||||||
|
CFTypeRef values[] = {
|
||||||
|
(__bridge CFNumberRef) @(util::ONE_EM),
|
||||||
|
mainFontName,
|
||||||
|
*fallbackDescriptors,
|
||||||
|
};
|
||||||
|
|
||||||
|
CFDictionaryRefHandle attributes(
|
||||||
|
CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values,
|
||||||
|
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
|
||||||
|
&kCFTypeDictionaryValueCallBacks));
|
||||||
|
return CTFontDescriptorCreateWithAttributes(*attributes);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NSArray<NSString *> *fallbackFontNames;
|
NSArray<NSString *> *fallbackFontNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional<std::string>& fontFamily)
|
LocalGlyphRasterizer::LocalGlyphRasterizer(const std::optional<std::string> &fontFamily)
|
||||||
: impl(std::make_unique<Impl>(fontFamily))
|
: impl(std::make_unique<Impl>(fontFamily)) {}
|
||||||
{}
|
|
||||||
|
|
||||||
LocalGlyphRasterizer::~LocalGlyphRasterizer()
|
LocalGlyphRasterizer::~LocalGlyphRasterizer() {}
|
||||||
{}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns whether the rasterizer can rasterize a glyph for the given codepoint.
|
Returns whether the rasterizer can rasterize a glyph for the given codepoint.
|
||||||
@@ -178,11 +181,11 @@ LocalGlyphRasterizer::~LocalGlyphRasterizer()
|
|||||||
@param glyphID A font-agnostic Unicode codepoint, not a glyph index.
|
@param glyphID A font-agnostic Unicode codepoint, not a glyph index.
|
||||||
@returns Whether a glyph for the codepoint can be rasterized.
|
@returns Whether a glyph for the codepoint can be rasterized.
|
||||||
*/
|
*/
|
||||||
bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID) {
|
bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack &, GlyphID glyphID) {
|
||||||
#if MBGL_DARWIN_NO_REMOTE_FONTS
|
#if MBGL_DARWIN_NO_REMOTE_FONTS
|
||||||
return impl->isEnabled();
|
return impl->isEnabled();
|
||||||
#else
|
#else
|
||||||
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled();
|
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID) && impl->isEnabled();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,91 +200,90 @@ bool LocalGlyphRasterizer::canRasterizeGlyph(const FontStack&, GlyphID glyphID)
|
|||||||
@param isBold use kCTFontBoldTrait if it is true.
|
@param isBold use kCTFontBoldTrait if it is true.
|
||||||
@returns An image containing the glyph.
|
@returns An image containing the glyph.
|
||||||
*/
|
*/
|
||||||
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics& metrics, BOOL isBold) {
|
PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics &metrics,
|
||||||
CFStringRefHandle string(CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar*>(&glyphID), 1));
|
BOOL isBold) {
|
||||||
if (!string) {
|
CFStringRefHandle string(
|
||||||
throw std::runtime_error("Unable to create string from codepoint");
|
CFStringCreateWithCharacters(NULL, reinterpret_cast<UniChar *>(&glyphID), 1));
|
||||||
}
|
if (!string) {
|
||||||
|
throw std::runtime_error("Unable to create string from codepoint");
|
||||||
|
}
|
||||||
|
|
||||||
// Create a bold variant of the font
|
// Create a bold variant of the font
|
||||||
CTFontRefHandle boldFont(CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait));
|
CTFontRefHandle boldFont(
|
||||||
if (!boldFont) {
|
CTFontCreateCopyWithSymbolicTraits(font, 0.0, NULL, kCTFontBoldTrait, kCTFontBoldTrait));
|
||||||
CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font));
|
if (!boldFont) {
|
||||||
NSString* familyName = (__bridge NSString *)(*familyNameHandle);
|
CFStringRefHandle familyNameHandle(CTFontCopyFamilyName(font));
|
||||||
std::string stdFamilyName(familyName.UTF8String);
|
NSString *familyName = (__bridge NSString *)(*familyNameHandle);
|
||||||
Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName);
|
std::string stdFamilyName(familyName.UTF8String);
|
||||||
}
|
Log::Error(Event::General, "Unable to create bold font for " + stdFamilyName);
|
||||||
|
}
|
||||||
|
|
||||||
CTFontRef drawFont = isBold && boldFont ? *boldFont : font;
|
CTFontRef drawFont = isBold && boldFont ? *boldFont : font;
|
||||||
|
|
||||||
CFStringRef keys[] = { kCTFontAttributeName };
|
CFStringRef keys[] = {kCTFontAttributeName};
|
||||||
CFTypeRef values[] = { drawFont };
|
CFTypeRef values[] = {drawFont};
|
||||||
|
|
||||||
CFDictionaryRefHandle attributes(
|
CFDictionaryRefHandle attributes(
|
||||||
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
|
CFDictionaryCreate(kCFAllocatorDefault, (const void **)&keys, (const void **)&values,
|
||||||
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
|
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks,
|
||||||
&kCFTypeDictionaryKeyCallBacks,
|
&kCFTypeDictionaryValueCallBacks));
|
||||||
&kCFTypeDictionaryValueCallBacks));
|
if (!attributes) {
|
||||||
if (!attributes) {
|
throw std::runtime_error("Unable to create attributed string attributes dictionary");
|
||||||
throw std::runtime_error("Unable to create attributed string attributes dictionary");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
CFAttributedStringRefHandle attrString(CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
|
CFAttributedStringRefHandle attrString(
|
||||||
if (!attrString) {
|
CFAttributedStringCreate(kCFAllocatorDefault, *string, *attributes));
|
||||||
throw std::runtime_error("Unable to create attributed string");
|
if (!attrString) {
|
||||||
}
|
throw std::runtime_error("Unable to create attributed string");
|
||||||
CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString));
|
}
|
||||||
if (!line) {
|
CTLineRefHandle line(CTLineCreateWithAttributedString(*attrString));
|
||||||
throw std::runtime_error("Unable to create line from attributed string");
|
if (!line) {
|
||||||
}
|
throw std::runtime_error("Unable to create line from attributed string");
|
||||||
|
}
|
||||||
|
|
||||||
Size size(35, 35);
|
Size size(35, 35);
|
||||||
metrics.width = size.width;
|
metrics.width = size.width;
|
||||||
metrics.height = size.height;
|
metrics.height = size.height;
|
||||||
|
|
||||||
PremultipliedImage rgbaBitmap(size);
|
PremultipliedImage rgbaBitmap(size);
|
||||||
|
|
||||||
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
|
||||||
if (!colorSpace) {
|
if (!colorSpace) {
|
||||||
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
|
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr const size_t bitsPerComponent = 8;
|
constexpr const size_t bitsPerComponent = 8;
|
||||||
constexpr const size_t bytesPerPixel = 4;
|
constexpr const size_t bytesPerPixel = 4;
|
||||||
const size_t bytesPerRow = bytesPerPixel * size.width;
|
const size_t bytesPerRow = bytesPerPixel * size.width;
|
||||||
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) | static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
const auto bitmapInfo = static_cast<uint32_t>(kCGBitmapByteOrderDefault) |
|
||||||
|
static_cast<uint32_t>(kCGImageAlphaPremultipliedLast);
|
||||||
|
|
||||||
CGContextHandle context(CGBitmapContextCreate(
|
CGContextHandle context(CGBitmapContextCreate(rgbaBitmap.data.get(), size.width, size.height,
|
||||||
rgbaBitmap.data.get(),
|
bitsPerComponent, bytesPerRow, *colorSpace,
|
||||||
size.width,
|
bitmapInfo));
|
||||||
size.height,
|
if (!context) {
|
||||||
bitsPerComponent,
|
throw std::runtime_error("CGBitmapContextCreate failed");
|
||||||
bytesPerRow,
|
}
|
||||||
*colorSpace,
|
|
||||||
bitmapInfo));
|
|
||||||
if (!context) {
|
|
||||||
throw std::runtime_error("CGBitmapContextCreate failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line);
|
CFArrayRef glyphRuns = CTLineGetGlyphRuns(*line);
|
||||||
CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0);
|
CTRunRef glyphRun = (CTRunRef)CFArrayGetValueAtIndex(glyphRuns, 0);
|
||||||
CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun));
|
CFRange wholeRunRange = CFRangeMake(0, CTRunGetGlyphCount(glyphRun));
|
||||||
std::vector<CGSize> advances(wholeRunRange.length);
|
std::vector<CGSize> advances(wholeRunRange.length);
|
||||||
CTRunGetAdvances(glyphRun, wholeRunRange, advances.data());
|
CTRunGetAdvances(glyphRun, wholeRunRange, advances.data());
|
||||||
metrics.advance = std::round(advances[0].width);
|
metrics.advance = std::round(advances[0].width);
|
||||||
|
|
||||||
// Mimic glyph PBF metrics.
|
// Mimic glyph PBF metrics.
|
||||||
metrics.left = Glyph::borderSize;
|
metrics.left = Glyph::borderSize;
|
||||||
metrics.top = -Glyph::borderSize;
|
metrics.top = -Glyph::borderSize;
|
||||||
|
|
||||||
// Move the text upward to avoid clipping off descenders.
|
// Move the text upward to avoid clipping off descenders.
|
||||||
CGFloat descent;
|
CGFloat descent;
|
||||||
CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL);
|
CTRunGetTypographicBounds(glyphRun, wholeRunRange, NULL, &descent, NULL);
|
||||||
CGContextSetTextPosition(*context, 0.0, descent);
|
CGContextSetTextPosition(*context, 0.0, descent);
|
||||||
|
|
||||||
CTLineDraw(*line, *context);
|
CTLineDraw(*line, *context);
|
||||||
|
|
||||||
return rgbaBitmap;
|
return rgbaBitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -294,34 +296,36 @@ PremultipliedImage drawGlyphBitmap(GlyphID glyphID, CTFontRef font, GlyphMetrics
|
|||||||
@returns A glyph representation of the given codepoint with its bitmap data and
|
@returns A glyph representation of the given codepoint with its bitmap data and
|
||||||
metrics set.
|
metrics set.
|
||||||
*/
|
*/
|
||||||
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID glyphID) {
|
Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack &fontStack, GlyphID glyphID) {
|
||||||
Glyph manufacturedGlyph;
|
Glyph manufacturedGlyph;
|
||||||
CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack));
|
CTFontDescriptorRefHandle descriptor(impl->createFontDescriptor(fontStack));
|
||||||
CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL));
|
CTFontRefHandle font(CTFontCreateWithFontDescriptor(*descriptor, 0.0, NULL));
|
||||||
if (!font) {
|
if (!font) {
|
||||||
return manufacturedGlyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
manufacturedGlyph.id = glyphID;
|
|
||||||
BOOL isBold = NO;
|
|
||||||
// Only check the first font name to detect if the user prefers using bold
|
|
||||||
if (!fontStack.empty()) {
|
|
||||||
std::string lowercaseFont = platform::lowercase(fontStack.front());
|
|
||||||
if (lowercaseFont.find("bold") != std::string::npos && lowercaseFont.find("semibold") == std::string::npos) {
|
|
||||||
isBold = YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PremultipliedImage rgbaBitmap = drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);
|
|
||||||
|
|
||||||
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
|
|
||||||
// Copy alpha values from RGBA bitmap into the AlphaImage output
|
|
||||||
manufacturedGlyph.bitmap = AlphaImage(size);
|
|
||||||
for (uint32_t i = 0; i < size.width * size.height; i++) {
|
|
||||||
manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
|
|
||||||
}
|
|
||||||
|
|
||||||
return manufacturedGlyph;
|
return manufacturedGlyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
manufacturedGlyph.id = glyphID;
|
||||||
|
BOOL isBold = NO;
|
||||||
|
// Only check the first font name to detect if the user prefers using bold
|
||||||
|
if (!fontStack.empty()) {
|
||||||
|
std::string lowercaseFont = platform::lowercase(fontStack.front());
|
||||||
|
if (lowercaseFont.find("bold") != std::string::npos &&
|
||||||
|
lowercaseFont.find("semibold") == std::string::npos) {
|
||||||
|
isBold = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PremultipliedImage rgbaBitmap =
|
||||||
|
drawGlyphBitmap(glyphID, *font, manufacturedGlyph.metrics, isBold);
|
||||||
|
|
||||||
|
Size size(manufacturedGlyph.metrics.width, manufacturedGlyph.metrics.height);
|
||||||
|
// Copy alpha values from RGBA bitmap into the AlphaImage output
|
||||||
|
manufacturedGlyph.bitmap = AlphaImage(size);
|
||||||
|
for (uint32_t i = 0; i < size.width * size.height; i++) {
|
||||||
|
manufacturedGlyph.bitmap.data[i] = rgbaBitmap.data[4 * i + 3];
|
||||||
|
}
|
||||||
|
|
||||||
|
return manufacturedGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
#include <mbgl/util/logging.hpp>
|
|
||||||
#include <mbgl/util/enum.hpp>
|
#include <mbgl/util/enum.hpp>
|
||||||
|
#include <mbgl/util/logging.hpp>
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
void Log::platformRecord(EventSeverity severity, const std::string &msg) {
|
void Log::platformRecord(EventSeverity severity, const std::string &msg) {
|
||||||
NSString *message =
|
NSString *message = [[NSString alloc] initWithBytes:msg.data()
|
||||||
[[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
|
length:msg.size()
|
||||||
NSLog(@"[%s] %@", Enum<EventSeverity>::toString(severity), message);
|
encoding:NSUTF8StringEncoding];
|
||||||
}
|
NSLog(@"[%s] %@", Enum<EventSeverity>::toString(severity), message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#include <mbgl/platform/thread.hpp>
|
||||||
#include <mbgl/util/logging.hpp>
|
#include <mbgl/util/logging.hpp>
|
||||||
#include <mbgl/util/platform.hpp>
|
#include <mbgl/util/platform.hpp>
|
||||||
#include <mbgl/platform/thread.hpp>
|
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
@@ -10,43 +10,41 @@ namespace mbgl {
|
|||||||
namespace platform {
|
namespace platform {
|
||||||
|
|
||||||
std::string getCurrentThreadName() {
|
std::string getCurrentThreadName() {
|
||||||
char name[32] = "unknown";
|
char name[32] = "unknown";
|
||||||
pthread_getname_np(pthread_self(), name, sizeof(name));
|
pthread_getname_np(pthread_self(), name, sizeof(name));
|
||||||
|
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCurrentThreadName(const std::string& name) {
|
void setCurrentThreadName(const std::string& name) {
|
||||||
std::string qualifiedName = "org.maplibre.mbgl." + name;
|
std::string qualifiedName = "org.maplibre.mbgl." + name;
|
||||||
pthread_setname_np(qualifiedName.c_str());
|
pthread_setname_np(qualifiedName.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void makeThreadLowPriority() {
|
void makeThreadLowPriority() {
|
||||||
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
|
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCurrentThreadPriority(double priority) {
|
void setCurrentThreadPriority(double priority) {
|
||||||
if (priority > 1.0 || priority < 0.0) {
|
if (priority > 1.0 || priority < 0.0) {
|
||||||
Log::Warning(Event::General, "Invalid thread priority was provided");
|
Log::Warning(Event::General, "Invalid thread priority was provided");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priority < 0.25) {
|
if (priority < 0.25) {
|
||||||
[NSThread currentThread].qualityOfService = NSQualityOfServiceBackground;
|
[NSThread currentThread].qualityOfService = NSQualityOfServiceBackground;
|
||||||
} else if (priority < 0.5) {
|
} else if (priority < 0.5) {
|
||||||
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
|
[NSThread currentThread].qualityOfService = NSQualityOfServiceUtility;
|
||||||
} else if (priority < 0.75) {
|
} else if (priority < 0.75) {
|
||||||
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated;
|
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInitiated;
|
||||||
} else {
|
} else {
|
||||||
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive;
|
[NSThread currentThread].qualityOfService = NSQualityOfServiceUserInteractive;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void attachThread() {
|
void attachThread() {}
|
||||||
}
|
|
||||||
|
|
||||||
void detachThread() {
|
void detachThread() {}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace platform
|
||||||
}
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -7,24 +7,24 @@ namespace platform {
|
|||||||
|
|
||||||
std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
|
std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
|
||||||
uint8_t minFractionDigits, uint8_t maxFractionDigits) {
|
uint8_t minFractionDigits, uint8_t maxFractionDigits) {
|
||||||
|
// Create a new formatter each time to avoid state pollution between calls
|
||||||
|
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
|
||||||
|
|
||||||
// Create a new formatter each time to avoid state pollution between calls
|
numberFormatter.locale =
|
||||||
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
|
!localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
|
||||||
|
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
|
||||||
|
|
||||||
numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
|
if (currency.empty()) {
|
||||||
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
|
numberFormatter.minimumFractionDigits = minFractionDigits;
|
||||||
|
numberFormatter.maximumFractionDigits = maxFractionDigits;
|
||||||
if (currency.empty()) {
|
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
|
||||||
numberFormatter.minimumFractionDigits = minFractionDigits;
|
} else {
|
||||||
numberFormatter.maximumFractionDigits = maxFractionDigits;
|
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
|
||||||
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
|
}
|
||||||
} else {
|
NSString* formatted = [numberFormatter stringFromNumber:@(number)];
|
||||||
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
|
std::string result = std::string([formatted UTF8String]);
|
||||||
}
|
return result;
|
||||||
NSString *formatted = [numberFormatter stringFromNumber:@(number)];
|
|
||||||
std::string result = std::string([formatted UTF8String]);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace platform
|
} // namespace platform
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -6,26 +6,26 @@ namespace mbgl {
|
|||||||
namespace platform {
|
namespace platform {
|
||||||
|
|
||||||
std::string uppercase(const std::string &string) {
|
std::string uppercase(const std::string &string) {
|
||||||
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
|
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
|
||||||
length:string.size()
|
length:string.size()
|
||||||
encoding:NSUTF8StringEncoding
|
encoding:NSUTF8StringEncoding
|
||||||
freeWhenDone:NO];
|
freeWhenDone:NO];
|
||||||
NSString *uppercase = [original uppercaseString];
|
NSString *uppercase = [original uppercaseString];
|
||||||
std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding],
|
std::string result{[uppercase cStringUsingEncoding:NSUTF8StringEncoding],
|
||||||
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
|
[uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string lowercase(const std::string &string) {
|
std::string lowercase(const std::string &string) {
|
||||||
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
|
NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
|
||||||
length:string.size()
|
length:string.size()
|
||||||
encoding:NSUTF8StringEncoding
|
encoding:NSUTF8StringEncoding
|
||||||
freeWhenDone:NO];
|
freeWhenDone:NO];
|
||||||
NSString *lowercase = [original lowercaseString];
|
NSString *lowercase = [original lowercaseString];
|
||||||
std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding],
|
std::string result{[lowercase cStringUsingEncoding:NSUTF8StringEncoding],
|
||||||
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
|
[lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace platform
|
} // namespace platform
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -2,61 +2,62 @@
|
|||||||
#import <mbgl/util/action_journal_options.hpp>
|
#import <mbgl/util/action_journal_options.hpp>
|
||||||
|
|
||||||
@implementation MLNActionJournalOptions {
|
@implementation MLNActionJournalOptions {
|
||||||
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
|
std::unique_ptr<mbgl::util::ActionJournalOptions> _actionJournalOptionsInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
|
_actionJournalOptionsInternal = std::make_unique<mbgl::util::ActionJournalOptions>();
|
||||||
|
|
||||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||||
_actionJournalOptionsInternal->withPath([paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
|
_actionJournalOptionsInternal->withPath(
|
||||||
}
|
[paths.firstObject stringByAppendingPathComponent:@""].UTF8String);
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::util::ActionJournalOptions)getCoreOptions {
|
- (mbgl::util::ActionJournalOptions)getCoreOptions {
|
||||||
return *_actionJournalOptionsInternal;
|
return *_actionJournalOptionsInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setEnabled:(BOOL)value {
|
- (void)setEnabled:(BOOL)value {
|
||||||
_actionJournalOptionsInternal->enable(value);
|
_actionJournalOptionsInternal->enable(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)enabled {
|
- (BOOL)enabled {
|
||||||
return _actionJournalOptionsInternal->enabled();
|
return _actionJournalOptionsInternal->enabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setPath:(NSString*)value {
|
- (void)setPath:(NSString *)value {
|
||||||
_actionJournalOptionsInternal->withPath(value.UTF8String);
|
_actionJournalOptionsInternal->withPath(value.UTF8String);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)path {
|
- (NSString *)path {
|
||||||
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
|
return [NSString stringWithUTF8String:_actionJournalOptionsInternal->path().c_str()];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLogFileSize:(NSInteger)value {
|
- (void)setLogFileSize:(NSInteger)value {
|
||||||
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
|
_actionJournalOptionsInternal->withLogFileSize(static_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)logFileSize {
|
- (NSInteger)logFileSize {
|
||||||
return _actionJournalOptionsInternal->logFileSize();
|
return _actionJournalOptionsInternal->logFileSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setLogFileCount:(NSInteger)value {
|
- (void)setLogFileCount:(NSInteger)value {
|
||||||
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
|
_actionJournalOptionsInternal->withLogFileCount(static_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)logFileCount {
|
- (NSInteger)logFileCount {
|
||||||
return _actionJournalOptionsInternal->logFileCount();
|
return _actionJournalOptionsInternal->logFileCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setRenderingStatsReportInterval:(NSInteger)value {
|
- (void)setRenderingStatsReportInterval:(NSInteger)value {
|
||||||
_actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value));
|
_actionJournalOptionsInternal->withRenderingStatsReportInterval(static_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)renderingStatsReportInterval {
|
- (NSInteger)renderingStatsReportInterval {
|
||||||
return _actionJournalOptionsInternal->renderingStatsReportInterval();
|
return _actionJournalOptionsInternal->renderingStatsReportInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,186 +1,199 @@
|
|||||||
#import "MLNAttributionInfo_Private.h"
|
#import "MLNAttributionInfo_Private.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#else
|
#else
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#import "MLNSettings.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNMapCamera.h"
|
#import "MLNMapCamera.h"
|
||||||
|
#import "MLNSettings.h"
|
||||||
#import "NSArray+MLNAdditions.h"
|
#import "NSArray+MLNAdditions.h"
|
||||||
#import "NSBundle+MLNAdditions.h"
|
#import "NSBundle+MLNAdditions.h"
|
||||||
#import "NSString+MLNAdditions.h"
|
#import "NSString+MLNAdditions.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
@implementation MLNAttributionInfo
|
@implementation MLNAttributionInfo
|
||||||
|
|
||||||
+ (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString fontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor {
|
+ (NSArray<MLNAttributionInfo *> *)attributionInfosFromHTMLString:(nullable NSString *)htmlString
|
||||||
if (!htmlString) {
|
fontSize:(CGFloat)fontSize
|
||||||
return @[];
|
linkColor:(nullable MLNColor *)linkColor {
|
||||||
}
|
if (!htmlString) {
|
||||||
|
return @[];
|
||||||
|
}
|
||||||
|
|
||||||
NSDictionary *options = @{
|
NSDictionary *options = @{
|
||||||
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
|
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
|
||||||
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding),
|
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
|
||||||
};
|
};
|
||||||
// Apply a bogus, easily detectable style rule to any feedback link, since
|
// Apply a bogus, easily detectable style rule to any feedback link, since
|
||||||
// NSAttributedString doesn’t preserve the class attribute.
|
// NSAttributedString doesn’t preserve the class attribute.
|
||||||
NSMutableString *css = [NSMutableString stringWithString:
|
NSMutableString *css = [NSMutableString
|
||||||
@"html { font-family: -apple-system, -apple-system-font, sans-serif; }"
|
stringWithString:@"html { font-family: -apple-system, -apple-system-font, sans-serif; }"
|
||||||
@".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
|
@".mapbox-improve-map { -webkit-text-stroke-width: 1000px; }"];
|
||||||
if (fontSize) {
|
if (fontSize) {
|
||||||
NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize];
|
NSString *sizeRule = [NSString stringWithFormat:@"font-size: %.1fpx;", fontSize];
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) {
|
if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeMini]) {
|
||||||
sizeRule = @"font: -webkit-mini-control";
|
sizeRule = @"font: -webkit-mini-control";
|
||||||
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) {
|
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeSmall]) {
|
||||||
sizeRule = @"font: -webkit-small-control";
|
sizeRule = @"font: -webkit-small-control";
|
||||||
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) {
|
} else if (fontSize == [NSFont systemFontSizeForControlSize:NSControlSizeRegular]) {
|
||||||
sizeRule = @"font: -webkit-control";
|
sizeRule = @"font: -webkit-control";
|
||||||
}
|
|
||||||
#endif
|
|
||||||
[css appendFormat:@"html { %@ }", sizeRule];
|
|
||||||
}
|
}
|
||||||
if (linkColor) {
|
#endif
|
||||||
CGFloat red;
|
[css appendFormat:@"html { %@ }", sizeRule];
|
||||||
CGFloat green;
|
}
|
||||||
CGFloat blue;
|
if (linkColor) {
|
||||||
CGFloat alpha;
|
CGFloat red;
|
||||||
|
CGFloat green;
|
||||||
|
CGFloat blue;
|
||||||
|
CGFloat alpha;
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
// CSS uses the sRGB color space.
|
// CSS uses the sRGB color space.
|
||||||
if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) {
|
if ([NSColor redColor].colorSpaceName == NSCalibratedRGBColorSpace) {
|
||||||
linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
linkColor = [linkColor colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
|
||||||
} else {
|
} else {
|
||||||
linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
linkColor = [linkColor colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||||
}
|
|
||||||
#endif
|
|
||||||
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
|
|
||||||
[css appendFormat:
|
|
||||||
@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }",
|
|
||||||
red * 100, green * 100, blue * 100, alpha];
|
|
||||||
}
|
}
|
||||||
NSString *styledHTML = [NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
|
#endif
|
||||||
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
|
[linkColor getRed:&red green:&green blue:&blue alpha:&alpha];
|
||||||
|
[css appendFormat:@"a:link { color: rgba(%f%%, %f%%, %f%%, %f); }", red * 100, green * 100,
|
||||||
|
blue * 100, alpha];
|
||||||
|
}
|
||||||
|
NSString *styledHTML =
|
||||||
|
[NSString stringWithFormat:@"<style type='text/css'>%@</style>%@", css, htmlString];
|
||||||
|
NSData *htmlData = [styledHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
__block NSMutableAttributedString *attributedString;
|
__block NSMutableAttributedString *attributedString;
|
||||||
dispatch_block_t initialization = ^{
|
dispatch_block_t initialization = ^{
|
||||||
// This initializer should be called from a global or main queue. https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata
|
// This initializer should be called from a global or main queue.
|
||||||
attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
|
// https://developer.apple.com/documentation/foundation/nsattributedstring/1524613-initwithdata
|
||||||
options:options
|
attributedString = [[NSMutableAttributedString alloc] initWithData:htmlData
|
||||||
documentAttributes:nil
|
options:options
|
||||||
error:NULL];
|
documentAttributes:nil
|
||||||
};
|
error:NULL];
|
||||||
|
};
|
||||||
|
|
||||||
if (![[NSThread currentThread] isMainThread]) {
|
if (![[NSThread currentThread] isMainThread]) {
|
||||||
dispatch_sync(dispatch_get_main_queue(), initialization);
|
dispatch_sync(dispatch_get_main_queue(), initialization);
|
||||||
} else {
|
} else {
|
||||||
initialization();
|
initialization();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithHTML:htmlData
|
NSMutableAttributedString *attributedString =
|
||||||
options:options
|
[[NSMutableAttributedString alloc] initWithHTML:htmlData
|
||||||
documentAttributes:nil];
|
options:options
|
||||||
|
documentAttributes:nil];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NSMutableArray *infos = [NSMutableArray array];
|
NSMutableArray *infos = [NSMutableArray array];
|
||||||
[attributedString enumerateAttribute:NSLinkAttributeName
|
[attributedString
|
||||||
inRange:attributedString.mgl_wholeRange
|
enumerateAttribute:NSLinkAttributeName
|
||||||
options:0
|
inRange:attributedString.mgl_wholeRange
|
||||||
usingBlock:
|
options:0
|
||||||
^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
|
usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
|
||||||
MLNCAssert(!value || [value isKindOfClass:[NSURL class]], @"If present, URL attribute must be an NSURL.");
|
MLNCAssert(!value || [value isKindOfClass:[NSURL class]],
|
||||||
|
@"If present, URL attribute must be an NSURL.");
|
||||||
|
|
||||||
// Detect feedback links by the bogus style rule applied above.
|
// Detect feedback links by the bogus style rule applied above.
|
||||||
NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
|
NSNumber *strokeWidth = [attributedString attribute:NSStrokeWidthAttributeName
|
||||||
atIndex:range.location
|
atIndex:range.location
|
||||||
effectiveRange:NULL];
|
effectiveRange:NULL];
|
||||||
BOOL isFeedbackLink = NO;
|
BOOL isFeedbackLink = NO;
|
||||||
if ([strokeWidth floatValue] > 100) {
|
if ([strokeWidth floatValue] > 100) {
|
||||||
isFeedbackLink = YES;
|
isFeedbackLink = YES;
|
||||||
[attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
|
[attributedString removeAttribute:NSStrokeWidthAttributeName range:range];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up strings by stripping punctuation and whitespace (often present for the web).
|
// Clean up strings by stripping punctuation and whitespace (often present for the
|
||||||
NSMutableCharacterSet *charset = [NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
|
// web).
|
||||||
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
NSMutableCharacterSet *charset =
|
||||||
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
|
[NSMutableCharacterSet whitespaceAndNewlineCharacterSet];
|
||||||
mgl_attributedStringByTrimmingCharactersInSet:charset];
|
[charset formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
|
||||||
|
NSAttributedString *title = [[attributedString attributedSubstringFromRange:range]
|
||||||
|
mgl_attributedStringByTrimmingCharactersInSet:charset];
|
||||||
|
|
||||||
// Omit strings that are empty after cleaning.
|
// Omit strings that are empty after cleaning.
|
||||||
if (!title.length) {
|
if (!title.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the link, because it forces the text to be blue on macOS 10.12
|
// Remove the link, because it forces the text to be blue on macOS 10.12
|
||||||
// and above.
|
// and above.
|
||||||
NSMutableAttributedString *unlinkedTitle = [title mutableCopy];
|
NSMutableAttributedString *unlinkedTitle = [title mutableCopy];
|
||||||
[unlinkedTitle removeAttribute:NSLinkAttributeName range:unlinkedTitle.mgl_wholeRange];
|
[unlinkedTitle removeAttribute:NSLinkAttributeName
|
||||||
|
range:unlinkedTitle.mgl_wholeRange];
|
||||||
|
|
||||||
MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle URL:value];
|
MLNAttributionInfo *info = [[MLNAttributionInfo alloc] initWithTitle:unlinkedTitle
|
||||||
info.feedbackLink = isFeedbackLink;
|
URL:value];
|
||||||
[infos addObject:info];
|
info.feedbackLink = isFeedbackLink;
|
||||||
}];
|
[infos addObject:info];
|
||||||
return infos;
|
}];
|
||||||
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSAttributedString *)attributedStringForAttributionInfos:(NSArray<MLNAttributionInfo *> *)attributionInfos {
|
+ (NSAttributedString *)attributedStringForAttributionInfos:
|
||||||
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
|
(NSArray<MLNAttributionInfo *> *)attributionInfos {
|
||||||
for (MLNAttributionInfo *info in attributionInfos) {
|
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:attributionInfos.count];
|
||||||
NSMutableAttributedString *title = info.title.mutableCopy;
|
for (MLNAttributionInfo *info in attributionInfos) {
|
||||||
if (info.URL) {
|
NSMutableAttributedString *title = info.title.mutableCopy;
|
||||||
[title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange];
|
if (info.URL) {
|
||||||
}
|
[title addAttribute:NSLinkAttributeName value:info.URL range:title.mgl_wholeRange];
|
||||||
[titles addObject:title];
|
|
||||||
}
|
}
|
||||||
return [titles mgl_attributedComponentsJoinedByString:@" "];
|
[titles addObject:title];
|
||||||
|
}
|
||||||
|
return [titles mgl_attributedComponentsJoinedByString:@" "];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL {
|
- (instancetype)initWithTitle:(NSAttributedString *)title URL:(NSURL *)URL {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_title = title;
|
_title = title;
|
||||||
_URL = URL;
|
_URL = URL;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)copyWithZone:(nullable NSZone *)zone
|
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||||
{
|
MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title URL:_URL];
|
||||||
MLNAttributionInfo *info = [[[self class] allocWithZone:zone] initWithTitle:_title
|
info.feedbackLink = _feedbackLink;
|
||||||
URL:_URL];
|
|
||||||
info.feedbackLink = _feedbackLink;
|
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style
|
- (NSAttributedString *)titleWithStyle:(MLNAttributionInfoStyle)style {
|
||||||
{
|
NSString *openStreetMap =
|
||||||
NSString *openStreetMap = NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap", @"OpenStreetMap full name attribution");
|
NSLocalizedStringWithDefaultValue(@"OSM_FULL_NAME", @"Foundation", nil, @"OpenStreetMap",
|
||||||
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM", @"OpenStreetMap short name attribution");
|
@"OpenStreetMap full name attribution");
|
||||||
|
NSString *OSM = NSLocalizedStringWithDefaultValue(@"OSM_SHORT_NAME", @"Foundation", nil, @"OSM",
|
||||||
|
@"OpenStreetMap short name attribution");
|
||||||
|
|
||||||
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithAttributedString:self.title];
|
NSMutableAttributedString *title =
|
||||||
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
|
[[NSMutableAttributedString alloc] initWithAttributedString:self.title];
|
||||||
|
[title removeAttribute:NSUnderlineStyleAttributeName range:NSMakeRange(0, [title.string length])];
|
||||||
|
|
||||||
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
|
BOOL isAbbreviated = (style == MLNAttributionInfoStyleShort);
|
||||||
|
|
||||||
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
|
if ([title.string rangeOfString:@"OpenStreetMap"].location != NSNotFound) {
|
||||||
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap" withString:isAbbreviated ? OSM : openStreetMap
|
[title.mutableString replaceOccurrencesOfString:@"OpenStreetMap"
|
||||||
options:NSCaseInsensitiveSearch
|
withString:isAbbreviated ? OSM : openStreetMap
|
||||||
range:NSMakeRange(0, [title.mutableString length])];
|
options:NSCaseInsensitiveSearch
|
||||||
}
|
range:NSMakeRange(0, [title.mutableString length])];
|
||||||
|
}
|
||||||
|
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)object {
|
- (BOOL)isEqual:(id)object {
|
||||||
return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] && [[object URL] isEqual:self.URL];
|
return [object isKindOfClass:[self class]] && [[object title] isEqual:self.title] &&
|
||||||
|
[[object URL] isEqual:self.URL];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
return self.title.hash + self.URL.hash;
|
return self.title.hash + self.URL.hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,15 +205,15 @@
|
|||||||
if there is no overlap.
|
if there is no overlap.
|
||||||
*/
|
*/
|
||||||
- (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo {
|
- (NSComparisonResult)subsetCompare:(MLNAttributionInfo *)otherInfo {
|
||||||
NSString *title = self.title.string;
|
NSString *title = self.title.string;
|
||||||
NSString *otherTitle = otherInfo.title.string;
|
NSString *otherTitle = otherInfo.title.string;
|
||||||
if ([title containsString:otherTitle]) {
|
if ([title containsString:otherTitle]) {
|
||||||
return NSOrderedDescending;
|
return NSOrderedDescending;
|
||||||
}
|
}
|
||||||
if ([otherTitle containsString:title]) {
|
if ([otherTitle containsString:title]) {
|
||||||
return NSOrderedAscending;
|
return NSOrderedAscending;
|
||||||
}
|
}
|
||||||
return NSOrderedSame;
|
return NSOrderedSame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -208,43 +221,44 @@
|
|||||||
@implementation NSMutableArray (MLNAttributionInfoAdditions)
|
@implementation NSMutableArray (MLNAttributionInfoAdditions)
|
||||||
|
|
||||||
- (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info {
|
- (void)growArrayByAddingAttributionInfo:(MLNAttributionInfo *)info {
|
||||||
__block BOOL didInsertInfo = NO;
|
__block BOOL didInsertInfo = NO;
|
||||||
__block BOOL shouldAddInfo = YES;
|
__block BOOL shouldAddInfo = YES;
|
||||||
[self enumerateObjectsUsingBlock:^(MLNAttributionInfo * _Nonnull existingInfo, NSUInteger idx, BOOL * _Nonnull stop) {
|
[self enumerateObjectsUsingBlock:^(MLNAttributionInfo *_Nonnull existingInfo, NSUInteger idx,
|
||||||
switch ([info subsetCompare:existingInfo]) {
|
BOOL *_Nonnull stop) {
|
||||||
case NSOrderedDescending:
|
switch ([info subsetCompare:existingInfo]) {
|
||||||
// The existing info object is a subset of the one we’re adding.
|
case NSOrderedDescending:
|
||||||
// Replace the existing object the first time we find a subset;
|
// The existing info object is a subset of the one we’re adding.
|
||||||
// remove the existing object every time after that.
|
// Replace the existing object the first time we find a subset;
|
||||||
if (didInsertInfo) {
|
// remove the existing object every time after that.
|
||||||
[self removeObjectAtIndex:idx];
|
if (didInsertInfo) {
|
||||||
} else {
|
[self removeObjectAtIndex:idx];
|
||||||
[self replaceObjectAtIndex:idx withObject:info];
|
} else {
|
||||||
didInsertInfo = YES;
|
[self replaceObjectAtIndex:idx withObject:info];
|
||||||
}
|
didInsertInfo = YES;
|
||||||
break;
|
|
||||||
|
|
||||||
case NSOrderedAscending:
|
|
||||||
// The info object we’re adding is a subset of the existing one.
|
|
||||||
// Don’t add the object and stop looking.
|
|
||||||
shouldAddInfo = NO;
|
|
||||||
*stop = YES;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}];
|
break;
|
||||||
if (shouldAddInfo && !didInsertInfo) {
|
|
||||||
// No overlapping infos were found, so append the info object.
|
case NSOrderedAscending:
|
||||||
[self addObject:info];
|
// The info object we’re adding is a subset of the existing one.
|
||||||
|
// Don’t add the object and stop looking.
|
||||||
|
shouldAddInfo = NO;
|
||||||
|
*stop = YES;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}];
|
||||||
|
if (shouldAddInfo && !didInsertInfo) {
|
||||||
|
// No overlapping infos were found, so append the info object.
|
||||||
|
[self addObject:info];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)growArrayByAddingAttributionInfosFromArray:(NSArray<MLNAttributionInfo *> *)infos {
|
- (void)growArrayByAddingAttributionInfosFromArray:(NSArray<MLNAttributionInfo *> *)infos {
|
||||||
for (MLNAttributionInfo *info in infos) {
|
for (MLNAttributionInfo *info in infos) {
|
||||||
[self growArrayByAddingAttributionInfo:info];
|
[self growArrayByAddingAttributionInfo:info];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
device:(id<MTLDevice>)device
|
device:(id<MTLDevice>)device
|
||||||
renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
|
renderPassDescriptor:(MTLRenderPassDescriptor *)renderPassDescriptor
|
||||||
commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
|
commandBuffer:(id<MTLCommandBuffer>)commandBuffer {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_mtkView = mtkView;
|
_mtkView = mtkView;
|
||||||
_device = device;
|
_device = device;
|
||||||
_renderPassDescriptor = renderPassDescriptor;
|
_renderPassDescriptor = renderPassDescriptor;
|
||||||
_commandBuffer = commandBuffer;
|
_commandBuffer = commandBuffer;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
@implementation MLNBackendResource
|
@implementation MLNBackendResource
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#import "MLNComputedShapeSource_Private.h"
|
#import "MLNComputedShapeSource_Private.h"
|
||||||
|
|
||||||
#import "MLNMapView_Private.h"
|
|
||||||
#import "MLNSource_Private.h"
|
|
||||||
#import "MLNShape_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
|
#import "MLNMapView_Private.h"
|
||||||
#import "MLNShapeCollection.h"
|
#import "MLNShapeCollection.h"
|
||||||
|
#import "MLNShape_Private.h"
|
||||||
|
#import "MLNSource_Private.h"
|
||||||
|
|
||||||
#include <mbgl/map/map.hpp>
|
#include <mbgl/map/map.hpp>
|
||||||
#include <mbgl/style/sources/custom_geometry_source.hpp>
|
#include <mbgl/style/sources/custom_geometry_source.hpp>
|
||||||
@@ -13,65 +13,68 @@
|
|||||||
|
|
||||||
const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException";
|
const MLNExceptionName MLNInvalidDatasourceException = @"MLNInvalidDatasourceException";
|
||||||
|
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates = @"MLNShapeSourceOptionWrapsCoordinates";
|
const MLNShapeSourceOption MLNShapeSourceOptionWrapsCoordinates =
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates = @"MLNShapeSourceOptionClipsCoordinates";
|
@"MLNShapeSourceOptionWrapsCoordinates";
|
||||||
|
const MLNShapeSourceOption MLNShapeSourceOptionClipsCoordinates =
|
||||||
|
@"MLNShapeSourceOptionClipsCoordinates";
|
||||||
|
|
||||||
mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) {
|
mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(
|
||||||
mbgl::style::CustomGeometrySource::Options sourceOptions;
|
NSDictionary<MLNShapeSourceOption, id> *options) {
|
||||||
|
mbgl::style::CustomGeometrySource::Options sourceOptions;
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."];
|
format:@"MLNShapeSourceOptionMinimumZoomLevel must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.zoomRange.min = value.integerValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.zoomRange.min = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.zoomRange.max = value.integerValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.zoomRange.max = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
|
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.tileOptions.buffer = value.integerValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.tileOptions.buffer = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
|
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.tileOptions.tolerance = value.doubleValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.tileOptions.tolerance = value.doubleValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionWrapsCoordinates]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."];
|
format:@"MLNShapeSourceOptionWrapsCoordinates must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.tileOptions.wrap = value.boolValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.tileOptions.wrap = value.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionClipsCoordinates]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."];
|
format:@"MLNShapeSourceOptionClipsCoordinates must be an NSNumber."];
|
||||||
}
|
|
||||||
sourceOptions.tileOptions.clip = value.boolValue;
|
|
||||||
}
|
}
|
||||||
|
sourceOptions.tileOptions.clip = value.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
return sourceOptions;
|
return sourceOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface MLNComputedShapeSource () {
|
@interface MLNComputedShapeSource () {
|
||||||
std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource;
|
std::unique_ptr<mbgl::style::CustomGeometrySource> _pendingSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property (nonatomic, readwrite) NSDictionary *options;
|
@property (nonatomic, readwrite) NSDictionary *options;
|
||||||
@@ -90,164 +93,181 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
|
|||||||
@property (nonatomic, weak, nullable) id<MLNComputedShapeSourceDataSource> dataSource;
|
@property (nonatomic, weak, nullable) id<MLNComputedShapeSourceDataSource> dataSource;
|
||||||
@property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource;
|
@property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource;
|
||||||
|
|
||||||
- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileId;
|
- (instancetype)initForSource:(MLNComputedShapeSource *)source
|
||||||
|
tile:(const mbgl::CanonicalTileID &)tileId;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNComputedShapeSourceFetchOperation
|
@implementation MLNComputedShapeSourceFetchOperation
|
||||||
|
|
||||||
- (instancetype)initForSource:(MLNComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID {
|
- (instancetype)initForSource:(MLNComputedShapeSource *)source
|
||||||
self = [super init];
|
tile:(const mbgl::CanonicalTileID &)tileID {
|
||||||
_z = tileID.z;
|
self = [super init];
|
||||||
_x = tileID.x;
|
_z = tileID.z;
|
||||||
_y = tileID.y;
|
_x = tileID.x;
|
||||||
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
|
_y = tileID.y;
|
||||||
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
|
_dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile;
|
||||||
_dataSource = source.dataSource;
|
_dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds;
|
||||||
mbgl::style::CustomGeometrySource *rawSource = static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
|
_dataSource = source.dataSource;
|
||||||
_rawSource = rawSource;
|
mbgl::style::CustomGeometrySource *rawSource =
|
||||||
return self;
|
static_cast<mbgl::style::CustomGeometrySource *>(source.rawSource);
|
||||||
|
_rawSource = rawSource;
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)main {
|
- (void)main {
|
||||||
if ([self isCancelled]) {
|
if ([self isCancelled]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray<MLNShape <MLNFeature> *> *data;
|
NSArray<MLNShape<MLNFeature> *> *data;
|
||||||
if(!self.dataSource) {
|
if (!self.dataSource) {
|
||||||
data = nil;
|
data = nil;
|
||||||
} else if(self.dataSourceImplementsFeaturesForTile) {
|
} else if (self.dataSourceImplementsFeaturesForTile) {
|
||||||
data = [self.dataSource featuresInTileAtX:self.x
|
data = [self.dataSource featuresInTileAtX:self.x y:self.y zoomLevel:self.z];
|
||||||
y:self.y
|
} else {
|
||||||
zoomLevel:self.z];
|
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
|
||||||
} else {
|
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
|
||||||
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y);
|
data =
|
||||||
mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID);
|
[self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds)
|
||||||
data = [self.dataSource featuresInCoordinateBounds:MLNCoordinateBoundsFromLatLngBounds(tileBounds)
|
zoomLevel:self.z];
|
||||||
zoomLevel:self.z];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(![self isCancelled]) {
|
if (![self isCancelled]) {
|
||||||
mbgl::FeatureCollection featureCollection;
|
mbgl::FeatureCollection featureCollection;
|
||||||
featureCollection.reserve(data.count);
|
featureCollection.reserve(data.count);
|
||||||
for (MLNShape <MLNFeature> * feature in data) {
|
for (MLNShape<MLNFeature> *feature in data) {
|
||||||
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
|
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
||||||
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
||||||
@"This will be logged only once.");
|
@"This will be logged only once.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
|
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
|
||||||
featureCollection.push_back(geoJsonObject);
|
featureCollection.push_back(geoJsonObject);
|
||||||
}
|
|
||||||
const auto geojson = mbgl::GeoJSON{featureCollection};
|
|
||||||
|
|
||||||
// Note: potential race condition with `cancel`
|
|
||||||
if(![self isCancelled]) {
|
|
||||||
if (auto *rawSource = self.rawSource) {
|
|
||||||
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const auto geojson = mbgl::GeoJSON{featureCollection};
|
||||||
|
|
||||||
|
// Note: potential race condition with `cancel`
|
||||||
|
if (![self isCancelled]) {
|
||||||
|
if (auto *rawSource = self.rawSource) {
|
||||||
|
rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)cancel {
|
- (void)cancel {
|
||||||
[super cancel];
|
[super cancel];
|
||||||
self.rawSource = NULL;
|
self.rawSource = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNComputedShapeSource
|
@implementation MLNComputedShapeSource
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
|
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
||||||
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
|
NSOperationQueue *requestQueue = [[NSOperationQueue alloc] init];
|
||||||
requestQueue.qualityOfService = NSQualityOfServiceUtility;
|
requestQueue.name = [NSString stringWithFormat:@"mgl.MLNComputedShapeSource.%@", identifier];
|
||||||
requestQueue.maxConcurrentOperationCount = 4;
|
requestQueue.qualityOfService = NSQualityOfServiceUtility;
|
||||||
|
requestQueue.maxConcurrentOperationCount = 4;
|
||||||
|
|
||||||
auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options);
|
auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options);
|
||||||
sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) {
|
sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
|
||||||
NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID];
|
NSOperation *operation = [[MLNComputedShapeSourceFetchOperation alloc] initForSource:self
|
||||||
[requestQueue addOperation:operation];
|
tile:tileID];
|
||||||
};
|
[requestQueue addOperation:operation];
|
||||||
|
};
|
||||||
|
|
||||||
sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) {
|
sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID &tileID) {
|
||||||
for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) {
|
for (MLNComputedShapeSourceFetchOperation *operation in requestQueue.operations) {
|
||||||
if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) {
|
if (operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) {
|
||||||
[operation cancel];
|
[operation cancel];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto source = std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
|
|
||||||
|
|
||||||
if (self = [super initWithPendingSource:std::move(source)]) {
|
|
||||||
_requestQueue = requestQueue;
|
|
||||||
}
|
}
|
||||||
return self;
|
};
|
||||||
|
|
||||||
|
auto source =
|
||||||
|
std::make_unique<mbgl::style::CustomGeometrySource>(identifier.UTF8String, sourceOptions);
|
||||||
|
|
||||||
|
if (self = [super initWithPendingSource:std::move(source)]) {
|
||||||
|
_requestQueue = requestQueue;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
if (self = [self initWithIdentifier:identifier options:options]) {
|
dataSource:(id<MLNComputedShapeSourceDataSource>)dataSource
|
||||||
[self setDataSource:dataSource];
|
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
||||||
}
|
if (self = [self initWithIdentifier:identifier options:options]) {
|
||||||
return self;
|
[self setDataSource:dataSource];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
[self.requestQueue cancelAllOperations];
|
[self.requestQueue cancelAllOperations];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setFeatures:(NSArray<MLNShape <MLNFeature> *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel {
|
- (void)setFeatures:(NSArray<MLNShape<MLNFeature> *> *)features
|
||||||
MLNAssertStyleSourceIsValid();
|
inTileAtX:(NSUInteger)x
|
||||||
mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
|
y:(NSUInteger)y
|
||||||
mbgl::FeatureCollection featureCollection;
|
zoomLevel:(NSUInteger)zoomLevel {
|
||||||
featureCollection.reserve(features.count);
|
MLNAssertStyleSourceIsValid();
|
||||||
for (MLNShape <MLNFeature> * feature in features) {
|
mbgl::CanonicalTileID tileID =
|
||||||
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
|
mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y);
|
||||||
featureCollection.push_back(geoJsonObject);
|
mbgl::FeatureCollection featureCollection;
|
||||||
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
|
featureCollection.reserve(features.count);
|
||||||
static dispatch_once_t onceToken;
|
for (MLNShape<MLNFeature> *feature in features) {
|
||||||
dispatch_once(&onceToken, ^{
|
mbgl::GeoJSONFeature geoJsonObject = [feature geoJSONObject].get<mbgl::GeoJSONFeature>();
|
||||||
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
featureCollection.push_back(geoJsonObject);
|
||||||
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
if ([feature isMemberOfClass:[MLNShapeCollection class]]) {
|
||||||
@"This will be logged only once.");
|
static dispatch_once_t onceToken;
|
||||||
});
|
dispatch_once(&onceToken, ^{
|
||||||
}
|
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
||||||
|
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
||||||
|
@"This will be logged only once.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const auto geojson = mbgl::GeoJSON{featureCollection};
|
}
|
||||||
static_cast<mbgl::style::CustomGeometrySource *>(self.rawSource)->setTileData(tileID, geojson);
|
const auto geojson = mbgl::GeoJSON{featureCollection};
|
||||||
|
static_cast<mbgl::style::CustomGeometrySource *>(self.rawSource)->setTileData(tileID, geojson);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDataSource:(id<MLNComputedShapeSourceDataSource>)dataSource {
|
- (void)setDataSource:(id<MLNComputedShapeSourceDataSource>)dataSource {
|
||||||
[self.requestQueue cancelAllOperations];
|
[self.requestQueue cancelAllOperations];
|
||||||
// Check which method the datasource implements, to avoid having to check for each tile
|
// Check which method the datasource implements, to avoid having to check for each tile
|
||||||
self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)];
|
self.dataSourceImplementsFeaturesForTile =
|
||||||
self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)];
|
[dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)];
|
||||||
|
self.dataSourceImplementsFeaturesForBounds =
|
||||||
|
[dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)];
|
||||||
|
|
||||||
if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) {
|
if (!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) {
|
||||||
[NSException raise:MLNInvalidDatasourceException
|
[NSException
|
||||||
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
|
raise:MLNInvalidDatasourceException
|
||||||
} else if (self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) {
|
format:@"Datasource does not implement any MLNComputedShapeSourceDataSource methods"];
|
||||||
[NSException raise:MLNInvalidDatasourceException
|
} else if (self.dataSourceImplementsFeaturesForBounds &&
|
||||||
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
|
self.dataSourceImplementsFeaturesForTile) {
|
||||||
}
|
[NSException raise:MLNInvalidDatasourceException
|
||||||
|
format:@"Datasource implements multiple MLNComputedShapeSourceDataSource methods"];
|
||||||
|
}
|
||||||
|
|
||||||
_dataSource = dataSource;
|
_dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) invalidateBounds:(MLNCoordinateBounds)bounds {
|
- (void)invalidateBounds:(MLNCoordinateBounds)bounds {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds));
|
((mbgl::style::CustomGeometrySource *)self.rawSource)
|
||||||
|
->invalidateRegion(MLNLatLngBoundsFromCoordinateBounds(bounds));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z {
|
- (void)invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y));
|
((mbgl::style::CustomGeometrySource *)self.rawSource)
|
||||||
|
->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y));
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ namespace conversion {
|
|||||||
|
|
||||||
// A wrapper class for `id`, so as not to confuse ARC.
|
// A wrapper class for `id`, so as not to confuse ARC.
|
||||||
class Holder {
|
class Holder {
|
||||||
public:
|
public:
|
||||||
Holder(const id v) : value(v) {}
|
Holder(const id v) : value(v) {}
|
||||||
const id value;
|
const id value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
class ConversionTraits<Holder> {
|
class ConversionTraits<Holder> {
|
||||||
public:
|
public:
|
||||||
static bool isUndefined(const Holder& holder) {
|
static bool isUndefined(const Holder& holder) {
|
||||||
const id value = holder.value;
|
const id value = holder.value;
|
||||||
return !value || value == [NSNull null];
|
return !value || value == [NSNull null];
|
||||||
@@ -146,7 +146,7 @@ class ConversionTraits<Holder> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool _isBool(const id value) {
|
static bool _isBool(const id value) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) return false;
|
if (![value isKindOfClass:[NSNumber class]]) return false;
|
||||||
// char: 32-bit boolean
|
// char: 32-bit boolean
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
#import "MLNStyleLayer.h"
|
#import "MLNStyleLayer.h"
|
||||||
|
|
||||||
#import "MLNCustomDrawableStyleLayer_Private.h"
|
#import "MLNCustomDrawableStyleLayer_Private.h"
|
||||||
#import "MLNStyle_Private.h"
|
|
||||||
#import "MLNStyleLayer_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
|
#import "MLNStyleLayer_Private.h"
|
||||||
|
#import "MLNStyle_Private.h"
|
||||||
|
|
||||||
#include <mbgl/layermanager/custom_drawable_layer_factory.hpp>
|
#include <mbgl/layermanager/custom_drawable_layer_factory.hpp>
|
||||||
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
#include <mbgl/style/layers/custom_drawable_layer.hpp>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <mbgl/style/layer.hpp>
|
#include <mbgl/style/layer.hpp>
|
||||||
|
|
||||||
@@ -20,12 +20,12 @@
|
|||||||
|
|
||||||
@implementation MLNCustomDrawableStyleLayer
|
@implementation MLNCustomDrawableStyleLayer
|
||||||
|
|
||||||
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer {
|
- (instancetype)initWithRawLayer:(mbgl::style::Layer*)rawLayer {
|
||||||
return [super initWithRawLayer:rawLayer];
|
return [super initWithRawLayer:rawLayer];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
|
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
|
||||||
return [super initWithPendingLayer:std::move(pendingLayer)];
|
return [super initWithPendingLayer:std::move(pendingLayer)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
MLNStyleLayer* CustomDrawableStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
|
MLNStyleLayer* CustomDrawableStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
|
||||||
return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer];
|
return [[MLNCustomDrawableStyleLayer alloc] initWithRawLayer:rawLayer];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
#import "MLNCustomStyleLayer.h"
|
#import "MLNCustomStyleLayer.h"
|
||||||
#import "MLNCustomStyleLayer_Private.h"
|
#import "MLNCustomStyleLayer_Private.h"
|
||||||
|
|
||||||
#import "MLNMapView_Private.h"
|
|
||||||
#import "MLNStyle_Private.h"
|
|
||||||
#import "MLNStyleLayer_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
|
#import "MLNMapView_Private.h"
|
||||||
|
#import "MLNStyleLayer_Private.h"
|
||||||
|
#import "MLNStyle_Private.h"
|
||||||
|
|
||||||
#if MLN_RENDER_BACKEND_METAL
|
#if MLN_RENDER_BACKEND_METAL
|
||||||
#import <MetalKit/MetalKit.h>
|
#import <MetalKit/MetalKit.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mbgl/style/layers/custom_layer.hpp>
|
|
||||||
#include <mbgl/math/wrap.hpp>
|
#include <mbgl/math/wrap.hpp>
|
||||||
|
#include <mbgl/style/layers/custom_layer.hpp>
|
||||||
|
|
||||||
#if MLN_RENDER_BACKEND_METAL
|
#if MLN_RENDER_BACKEND_METAL
|
||||||
#include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp>
|
#include <mbgl/style/layers/mtl/custom_layer_render_parameters.hpp>
|
||||||
@@ -28,44 +28,42 @@ class MLNCustomLayerHost;
|
|||||||
@implementation MLNCustomStyleLayer
|
@implementation MLNCustomStyleLayer
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||||
auto layer = std::make_unique<mbgl::style::CustomLayer>(
|
auto layer = std::make_unique<mbgl::style::CustomLayer>(
|
||||||
identifier.UTF8String,
|
identifier.UTF8String, std::make_unique<MLNCustomLayerHost>(self));
|
||||||
std::make_unique<MLNCustomLayerHost>(self)
|
return self = [super initWithPendingLayer:std::move(layer)];
|
||||||
);
|
|
||||||
return self = [super initWithPendingLayer:std::move(layer)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::CustomLayer *)rawLayer {
|
- (mbgl::style::CustomLayer *)rawLayer {
|
||||||
return (mbgl::style::CustomLayer *)super.rawLayer;
|
return (mbgl::style::CustomLayer *)super.rawLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNMapView *)mapView {
|
- (MLNMapView *)mapView {
|
||||||
if ([self.style.stylable isKindOfClass:[MLNMapView class]]) {
|
if ([self.style.stylable isKindOfClass:[MLNMapView class]]) {
|
||||||
return (MLNMapView *)self.style.stylable;
|
return (MLNMapView *)self.style.stylable;
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
- (EAGLContext *)context {
|
- (EAGLContext *)context {
|
||||||
return self.mapView.context;
|
return self.mapView.context;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
- (CGLContextObj)context {
|
- (CGLContextObj)context {
|
||||||
return self.mapView.context;
|
return self.mapView.context;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
|
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
|
||||||
self.style = style;
|
self.style = style;
|
||||||
self.style.customLayers[self.identifier] = self;
|
self.style.customLayers[self.identifier] = self;
|
||||||
[super addToStyle:style belowLayer:otherLayer];
|
[super addToStyle:style belowLayer:otherLayer];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeFromStyle:(MLNStyle *)style {
|
- (void)removeFromStyle:(MLNStyle *)style {
|
||||||
[super removeFromStyle:style];
|
[super removeFromStyle:style];
|
||||||
self.style.customLayers[self.identifier] = nil;
|
self.style.customLayers[self.identifier] = nil;
|
||||||
self.style = nil;
|
self.style = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didMoveToMapView:(MLNMapView *)mapView {
|
- (void)didMoveToMapView:(MLNMapView *)mapView {
|
||||||
@@ -78,74 +76,75 @@ class MLNCustomLayerHost;
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)setNeedsDisplay {
|
- (void)setNeedsDisplay {
|
||||||
[self.mapView setNeedsRerender];
|
[self.mapView setNeedsRerender];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
class MLNCustomLayerHost : public mbgl::style::CustomLayerHost {
|
class MLNCustomLayerHost : public mbgl::style::CustomLayerHost {
|
||||||
public:
|
public:
|
||||||
MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) {
|
MLNCustomLayerHost(MLNCustomStyleLayer *styleLayer) {
|
||||||
layerRef = styleLayer;
|
layerRef = styleLayer;
|
||||||
layer = nil;
|
layer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
if (layerRef == nil)
|
||||||
|
return;
|
||||||
|
else if (layer == nil)
|
||||||
|
layer = layerRef;
|
||||||
|
|
||||||
|
if (layer.mapView) {
|
||||||
|
[layer didMoveToMapView:layer.mapView];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void initialize() {
|
void render(const mbgl::style::CustomLayerRenderParameters ¶meters) {
|
||||||
if (layerRef == nil) return;
|
if (!layer) return;
|
||||||
else if (layer == nil) layer = layerRef;
|
|
||||||
|
|
||||||
if (layer.mapView) {
|
|
||||||
[layer didMoveToMapView:layer.mapView];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void render(const mbgl::style::CustomLayerRenderParameters& parameters) {
|
|
||||||
if (!layer) return;
|
|
||||||
|
|
||||||
#if MLN_RENDER_BACKEND_METAL
|
#if MLN_RENDER_BACKEND_METAL
|
||||||
MTL::RenderCommandEncoder* ptr =
|
MTL::RenderCommandEncoder *ptr =
|
||||||
static_cast<const mbgl::style::mtl::CustomLayerRenderParameters&>(parameters).encoder.get();
|
static_cast<const mbgl::style::mtl::CustomLayerRenderParameters &>(parameters)
|
||||||
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
|
.encoder.get();
|
||||||
layer.renderEncoder = encoder;
|
id<MTLRenderCommandEncoder> encoder = (__bridge id<MTLRenderCommandEncoder>)ptr;
|
||||||
|
layer.renderEncoder = encoder;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MLNStyleLayerDrawingContext drawingContext = {
|
MLNStyleLayerDrawingContext drawingContext = {
|
||||||
.size = CGSizeMake(parameters.width, parameters.height),
|
.size = CGSizeMake(parameters.width, parameters.height),
|
||||||
.centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude),
|
.centerCoordinate = CLLocationCoordinate2DMake(parameters.latitude, parameters.longitude),
|
||||||
.zoomLevel = parameters.zoom,
|
.zoomLevel = parameters.zoom,
|
||||||
.direction = mbgl::util::wrap(parameters.bearing, 0., 360.),
|
.direction = mbgl::util::wrap(parameters.bearing, 0., 360.),
|
||||||
.pitch = static_cast<CGFloat>(parameters.pitch),
|
.pitch = static_cast<CGFloat>(parameters.pitch),
|
||||||
.fieldOfView = static_cast<CGFloat>(parameters.fieldOfView),
|
.fieldOfView = static_cast<CGFloat>(parameters.fieldOfView),
|
||||||
.projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)
|
.projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix)};
|
||||||
};
|
|
||||||
|
|
||||||
if (layer.mapView) {
|
if (layer.mapView) {
|
||||||
[layer drawInMapView:layer.mapView withContext:drawingContext];
|
[layer drawInMapView:layer.mapView withContext:drawingContext];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void contextLost() {
|
void contextLost() {}
|
||||||
}
|
|
||||||
|
void deinitialize() {
|
||||||
void deinitialize() {
|
if (layer == nil) return;
|
||||||
if (layer == nil) return;
|
|
||||||
|
if (layer.mapView) {
|
||||||
if (layer.mapView) {
|
[layer willMoveFromMapView:layer.mapView];
|
||||||
[layer willMoveFromMapView:layer.mapView];
|
|
||||||
}
|
|
||||||
layerRef = layer;
|
|
||||||
layer = nil;
|
|
||||||
}
|
}
|
||||||
|
layerRef = layer;
|
||||||
|
layer = nil;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
__weak MLNCustomStyleLayer * layerRef;
|
__weak MLNCustomStyleLayer *layerRef;
|
||||||
MLNCustomStyleLayer * layer = nil;
|
MLNCustomStyleLayer *layer = nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
MLNStyleLayer* CustomStyleLayerPeerFactory::createPeer(style::Layer* rawLayer) {
|
MLNStyleLayer *CustomStyleLayerPeerFactory::createPeer(style::Layer *rawLayer) {
|
||||||
return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer];
|
return [[MLNCustomStyleLayer alloc] initWithRawLayer:rawLayer];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -3,25 +3,25 @@
|
|||||||
CGFloat MLNEffectiveScaleFactorForView(id viewOrNil) {
|
CGFloat MLNEffectiveScaleFactorForView(id viewOrNil) {
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_TV
|
#if TARGET_OS_IPHONE || TARGET_OS_TV
|
||||||
|
|
||||||
UIView *view = (UIView *)viewOrNil;
|
UIView *view = (UIView *)viewOrNil;
|
||||||
UIScreen *screen = view.window.screen ?: [UIScreen mainScreen];
|
UIScreen *screen = view.window.screen ?: [UIScreen mainScreen];
|
||||||
|
|
||||||
CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width;
|
CGFloat logicalToPixelRatio = screen.nativeBounds.size.width / screen.bounds.size.width;
|
||||||
if (logicalToPixelRatio > 0.0) {
|
if (logicalToPixelRatio > 0.0) {
|
||||||
return logicalToPixelRatio;
|
return logicalToPixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale;
|
return [screen respondsToSelector:@selector(nativeScale)] ? screen.nativeScale : screen.scale;
|
||||||
|
|
||||||
#elif TARGET_OS_OSX
|
#elif TARGET_OS_OSX
|
||||||
|
|
||||||
NSView *view = (NSView *)viewOrNil;
|
NSView *view = (NSView *)viewOrNil;
|
||||||
NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen];
|
NSScreen *screen = view ? (view.window.screen ?: [NSScreen mainScreen]) : [NSScreen mainScreen];
|
||||||
return screen ? screen.backingScaleFactor : 1.0;
|
return screen ? screen.backingScaleFactor : 1.0;
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
return 1.0;
|
return 1.0;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
#import "MLNFoundation_Private.h"
|
|
||||||
#import "MLNFeature_Private.h"
|
|
||||||
#import "MLNCluster.h"
|
#import "MLNCluster.h"
|
||||||
|
#import "MLNFeature_Private.h"
|
||||||
|
#import "MLNFoundation_Private.h"
|
||||||
|
|
||||||
#import "MLNPointAnnotation.h"
|
#import "MLNPointAnnotation.h"
|
||||||
#import "MLNPolyline.h"
|
|
||||||
#import "MLNPolygon.h"
|
#import "MLNPolygon.h"
|
||||||
|
#import "MLNPolyline.h"
|
||||||
#import "MLNValueEvaluator.h"
|
#import "MLNValueEvaluator.h"
|
||||||
|
|
||||||
#import "MLNShape_Private.h"
|
|
||||||
#import "MLNPointCollection_Private.h"
|
#import "MLNPointCollection_Private.h"
|
||||||
#import "MLNPolyline_Private.h"
|
|
||||||
#import "MLNPolygon_Private.h"
|
#import "MLNPolygon_Private.h"
|
||||||
|
#import "MLNPolyline_Private.h"
|
||||||
|
#import "MLNShape_Private.h"
|
||||||
|
|
||||||
#import "NSDictionary+MLNAdditions.h"
|
|
||||||
#import "NSArray+MLNAdditions.h"
|
|
||||||
#import "NSExpression+MLNPrivateAdditions.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "NSArray+MLNAdditions.h"
|
||||||
|
#import "NSDictionary+MLNAdditions.h"
|
||||||
|
#import "NSExpression+MLNPrivateAdditions.h"
|
||||||
|
|
||||||
#import <mbgl/util/geometry.hpp>
|
|
||||||
#import <mbgl/style/conversion/geojson.hpp>
|
|
||||||
#import <mapbox/feature.hpp>
|
#import <mapbox/feature.hpp>
|
||||||
|
#import <mbgl/style/conversion/geojson.hpp>
|
||||||
|
#import <mbgl/util/geometry.hpp>
|
||||||
|
|
||||||
// Cluster constants
|
// Cluster constants
|
||||||
static NSString * const MLNClusterIdentifierKey = @"cluster_id";
|
static NSString *const MLNClusterIdentifierKey = @"cluster_id";
|
||||||
static NSString * const MLNClusterCountKey = @"point_count";
|
static NSString *const MLNClusterCountKey = @"point_count";
|
||||||
const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax;
|
const NSUInteger MLNClusterIdentifierInvalid = NSUIntegerMax;
|
||||||
|
|
||||||
@interface MLNEmptyFeature ()
|
@interface MLNEmptyFeature ()
|
||||||
@@ -40,28 +40,29 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry(@{@"type": @"Empty"}, self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry(@{@"type" : @"Empty"}, self.attributes, self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
return mbgl::EmptyGeometry();
|
return mbgl::EmptyGeometry();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>",
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, attributes = %@>",
|
NSStringFromClass([self class]), (void *)self,
|
||||||
NSStringFromClass([self class]), (void *)self,
|
self.identifier
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
: self.identifier,
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -80,25 +81,27 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
self.coordinate.latitude, self.coordinate.longitude,
|
: self.identifier,
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
self.coordinate.latitude, self.coordinate.longitude,
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -106,28 +109,28 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
|||||||
@implementation MLNPointFeatureCluster
|
@implementation MLNPointFeatureCluster
|
||||||
|
|
||||||
- (NSUInteger)clusterIdentifier {
|
- (NSUInteger)clusterIdentifier {
|
||||||
NSNumber *clusterNumber = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
|
NSNumber *clusterNumber =
|
||||||
MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
|
MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterIdentifierKey], NSNumber);
|
||||||
|
MLNAssert(clusterNumber, @"Clusters should have a cluster_id");
|
||||||
|
|
||||||
if (!clusterNumber) {
|
if (!clusterNumber) {
|
||||||
return MLNClusterIdentifierInvalid;
|
return MLNClusterIdentifierInvalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue];
|
NSUInteger clusterIdentifier = [clusterNumber unsignedIntegerValue];
|
||||||
MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit");
|
MLNAssert(clusterIdentifier <= UINT32_MAX, @"Cluster identifiers are 32bit");
|
||||||
|
|
||||||
return clusterIdentifier;
|
return clusterIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)clusterPointCount {
|
- (NSUInteger)clusterPointCount {
|
||||||
NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber);
|
NSNumber *count = MLN_OBJC_DYNAMIC_CAST([self attributeForKey:MLNClusterCountKey], NSNumber);
|
||||||
MLNAssert(count, @"Clusters should have a point_count");
|
MLNAssert(count, @"Clusters should have a point_count");
|
||||||
|
|
||||||
return [count unsignedIntegerValue];
|
return [count unsignedIntegerValue];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@interface MLNPolylineFeature ()
|
@interface MLNPolylineFeature ()
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -142,26 +145,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
(unsigned long)[self pointCount],
|
: self.identifier,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds),
|
(unsigned long)[self pointCount],
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
MLNStringFromCoordinateBounds(self.overlayBounds),
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -180,26 +185,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
(unsigned long)[self pointCount],
|
: self.identifier,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds),
|
(unsigned long)[self pointCount],
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
MLNStringFromCoordinateBounds(self.overlayBounds),
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -218,16 +225,17 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -246,26 +254,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
(unsigned long)self.polylines.count,
|
: self.identifier,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds),
|
(unsigned long)self.polylines.count,
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
MLNStringFromCoordinateBounds(self.overlayBounds),
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -284,26 +294,28 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
MLNLogDebug(@"Retrieving attributeForKey: %@", key);
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
return mbglFeature({[self geometryObject]}, identifier, self.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier,
|
self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier]
|
||||||
(unsigned long)self.polygons.count,
|
: self.identifier,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds),
|
(unsigned long)self.polygons.count,
|
||||||
self.attributes.count ? self.attributes : @"none"];
|
MLNStringFromCoordinateBounds(self.overlayBounds),
|
||||||
|
self.attributes.count ? self.attributes : @"none"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -319,7 +331,7 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
|||||||
@dynamic shapes;
|
@dynamic shapes;
|
||||||
|
|
||||||
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape<MLNFeature> *> *)shapes {
|
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape<MLNFeature> *> *)shapes {
|
||||||
return [super shapeCollectionWithShapes:shapes];
|
return [super shapeCollectionWithShapes:shapes];
|
||||||
}
|
}
|
||||||
|
|
||||||
MLN_DEFINE_FEATURE_INIT_WITH_CODER();
|
MLN_DEFINE_FEATURE_INIT_WITH_CODER();
|
||||||
@@ -328,22 +340,24 @@ MLN_DEFINE_FEATURE_IS_EQUAL();
|
|||||||
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
||||||
|
|
||||||
- (id)attributeForKey:(NSString *)key {
|
- (id)attributeForKey:(NSString *)key {
|
||||||
return self.attributes[key];
|
return self.attributes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes, self.identifier);
|
return NSDictionaryFeatureForGeometry([super geoJSONDictionary], self.attributes,
|
||||||
|
self.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
mbgl::FeatureCollection featureCollection;
|
mbgl::FeatureCollection featureCollection;
|
||||||
featureCollection.reserve(self.shapes.count);
|
featureCollection.reserve(self.shapes.count);
|
||||||
for (MLNShape <MLNFeature> *feature in self.shapes) {
|
for (MLNShape<MLNFeature> *feature in self.shapes) {
|
||||||
auto geoJSONObject = feature.geoJSONObject;
|
auto geoJSONObject = feature.geoJSONObject;
|
||||||
MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(), @"Feature collection must only contain features.");
|
MLNAssert(geoJSONObject.is<mbgl::GeoJSONFeature>(),
|
||||||
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
|
@"Feature collection must only contain features.");
|
||||||
}
|
featureCollection.push_back(geoJSONObject.get<mbgl::GeoJSONFeature>());
|
||||||
return featureCollection;
|
}
|
||||||
|
return featureCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -355,199 +369,211 @@ MLN_DEFINE_FEATURE_ATTRIBUTES_GETTER();
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
class GeometryEvaluator {
|
class GeometryEvaluator {
|
||||||
private:
|
private:
|
||||||
const mbgl::PropertyMap *shared_properties;
|
const mbgl::PropertyMap *shared_properties;
|
||||||
const bool is_in_feature;
|
const bool is_in_feature;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false):
|
GeometryEvaluator(const mbgl::PropertyMap *properties = nullptr, const bool isInFeature = false)
|
||||||
shared_properties(properties),
|
: shared_properties(properties), is_in_feature(isInFeature) {}
|
||||||
is_in_feature(isInFeature)
|
|
||||||
{}
|
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::EmptyGeometry &) const {
|
MLNShape *operator()(const mbgl::EmptyGeometry &) const {
|
||||||
return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init];
|
return is_in_feature ? [[MLNEmptyFeature alloc] init] : [[MLNShape alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::Point<T> &geometry) const {
|
MLNShape *operator()(const mbgl::Point<T> &geometry) const {
|
||||||
Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class];
|
Class shapeClass = is_in_feature ? [MLNPointFeature class] : [MLNPointAnnotation class];
|
||||||
|
|
||||||
// If we're dealing with a cluster, we should change the class type.
|
// If we're dealing with a cluster, we should change the class type.
|
||||||
// This could be generic and build the subclass at runtime if it turns
|
// This could be generic and build the subclass at runtime if it turns
|
||||||
// out we need to support more than point clusters.
|
// out we need to support more than point clusters.
|
||||||
if (shared_properties) {
|
if (shared_properties) {
|
||||||
auto clusterIt = shared_properties->find("cluster");
|
auto clusterIt = shared_properties->find("cluster");
|
||||||
if (clusterIt != shared_properties->end()) {
|
if (clusterIt != shared_properties->end()) {
|
||||||
auto clusterValue = clusterIt->second;
|
auto clusterValue = clusterIt->second;
|
||||||
if (clusterValue.template is<bool>()) {
|
if (clusterValue.template is<bool>()) {
|
||||||
if (clusterValue.template get<bool>()) {
|
if (clusterValue.template get<bool>()) {
|
||||||
shapeClass = [MLNPointFeatureCluster class];
|
shapeClass = [MLNPointFeatureCluster class];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
MLNPointAnnotation *shape = [[shapeClass alloc] init];
|
|
||||||
shape.coordinate = toLocationCoordinate2D(geometry);
|
|
||||||
return shape;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::LineString<T> &geometry) const {
|
MLNPointAnnotation *shape = [[shapeClass alloc] init];
|
||||||
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
|
shape.coordinate = toLocationCoordinate2D(geometry);
|
||||||
Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class];
|
return shape;
|
||||||
return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
|
}
|
||||||
|
|
||||||
|
MLNShape *operator()(const mbgl::LineString<T> &geometry) const {
|
||||||
|
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
|
||||||
|
Class shapeClass = is_in_feature ? [MLNPolylineFeature class] : [MLNPolyline class];
|
||||||
|
return [shapeClass polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
MLNShape *operator()(const mbgl::Polygon<T> &geometry) const {
|
||||||
|
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
MLNShape *operator()(const mbgl::MultiPoint<T> &geometry) const {
|
||||||
|
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
|
||||||
|
Class shapeClass =
|
||||||
|
is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class];
|
||||||
|
return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
|
||||||
|
}
|
||||||
|
|
||||||
|
MLNShape *operator()(const mbgl::MultiLineString<T> &geometry) const {
|
||||||
|
NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()];
|
||||||
|
for (auto &lineString : geometry) {
|
||||||
|
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString);
|
||||||
|
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0]
|
||||||
|
count:coordinates.size()];
|
||||||
|
[polylines addObject:polyline];
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::Polygon<T> &geometry) const {
|
Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class];
|
||||||
return toShape<MLNPolygon, MLNPolygonFeature>(geometry, is_in_feature);
|
return [shapeClass multiPolylineWithPolylines:polylines];
|
||||||
|
}
|
||||||
|
|
||||||
|
MLNShape *operator()(const mbgl::MultiPolygon<T> &geometry) const {
|
||||||
|
NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
|
||||||
|
for (auto &polygon : geometry) {
|
||||||
|
[polygons addObject:toShape(polygon, false)];
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::MultiPoint<T> &geometry) const {
|
Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class];
|
||||||
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(geometry);
|
return [shapeClass multiPolygonWithPolygons:polygons];
|
||||||
Class shapeClass = is_in_feature ? [MLNPointCollectionFeature class] : [MLNPointCollection class];
|
}
|
||||||
return [[shapeClass alloc] initWithCoordinates:&coordinates[0] count:coordinates.size()];
|
|
||||||
}
|
MLNShape *operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
|
||||||
|
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
|
||||||
MLNShape * operator()(const mbgl::MultiLineString<T> &geometry) const {
|
for (auto &geometry : collection) {
|
||||||
NSMutableArray *polylines = [NSMutableArray arrayWithCapacity:geometry.size()];
|
// This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(),
|
||||||
for (auto &lineString : geometry) {
|
// but these are raw geometries with no associated feature IDs or attributes.
|
||||||
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(lineString);
|
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, *this);
|
||||||
MLNPolyline *polyline = [MLNPolyline polylineWithCoordinates:&coordinates[0] count:coordinates.size()];
|
[shapes addObject:shape];
|
||||||
[polylines addObject:polyline];
|
|
||||||
}
|
|
||||||
|
|
||||||
Class shapeClass = is_in_feature ? [MLNMultiPolylineFeature class] : [MLNMultiPolyline class];
|
|
||||||
return [shapeClass multiPolylineWithPolylines:polylines];
|
|
||||||
}
|
|
||||||
|
|
||||||
MLNShape * operator()(const mbgl::MultiPolygon<T> &geometry) const {
|
|
||||||
NSMutableArray *polygons = [NSMutableArray arrayWithCapacity:geometry.size()];
|
|
||||||
for (auto &polygon : geometry) {
|
|
||||||
[polygons addObject:toShape(polygon, false)];
|
|
||||||
}
|
|
||||||
|
|
||||||
Class shapeClass = is_in_feature ? [MLNMultiPolygonFeature class] : [MLNMultiPolygon class];
|
|
||||||
return [shapeClass multiPolygonWithPolygons:polygons];
|
|
||||||
}
|
|
||||||
|
|
||||||
MLNShape * operator()(const mapbox::geometry::geometry_collection<T> &collection) const {
|
|
||||||
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
|
|
||||||
for (auto &geometry : collection) {
|
|
||||||
// This is very much like the transformation that happens in MLNFeaturesFromMBGLFeatures(), but these are raw geometries with no associated feature IDs or attributes.
|
|
||||||
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, *this);
|
|
||||||
[shapes addObject:shape];
|
|
||||||
}
|
|
||||||
Class shapeClass = is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class];
|
|
||||||
return [shapeClass shapeCollectionWithShapes:shapes];
|
|
||||||
}
|
}
|
||||||
|
Class shapeClass =
|
||||||
|
is_in_feature ? [MLNShapeCollectionFeature class] : [MLNShapeCollection class];
|
||||||
|
return [shapeClass shapeCollectionWithShapes:shapes];
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
|
static CLLocationCoordinate2D toLocationCoordinate2D(const mbgl::Point<T> &point) {
|
||||||
return CLLocationCoordinate2DMake(point.y, point.x);
|
return CLLocationCoordinate2DMake(point.y, point.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(
|
||||||
|
const std::vector<mbgl::Point<T>> &points) {
|
||||||
|
std::vector<CLLocationCoordinate2D> coordinates;
|
||||||
|
coordinates.reserve(points.size());
|
||||||
|
std::transform(points.begin(), points.end(), std::back_inserter(coordinates),
|
||||||
|
toLocationCoordinate2D);
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U = MLNPolygon, typename V = MLNPolygonFeature>
|
||||||
|
static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) {
|
||||||
|
auto &linearRing = geometry.front();
|
||||||
|
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing);
|
||||||
|
NSMutableArray *innerPolygons;
|
||||||
|
if (geometry.size() > 1) {
|
||||||
|
innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1];
|
||||||
|
for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) {
|
||||||
|
auto &innerRing = *iter;
|
||||||
|
std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing);
|
||||||
|
MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0]
|
||||||
|
count:innerCoordinates.size()];
|
||||||
|
[innerPolygons addObject:innerPolygon];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<CLLocationCoordinate2D> toLocationCoordinates2D(const std::vector<mbgl::Point<T>> &points) {
|
Class shapeClass = isInFeature ? [V class] : [U class];
|
||||||
std::vector<CLLocationCoordinate2D> coordinates;
|
return [shapeClass polygonWithCoordinates:&coordinates[0]
|
||||||
coordinates.reserve(points.size());
|
count:coordinates.size()
|
||||||
std::transform(points.begin(), points.end(), std::back_inserter(coordinates), toLocationCoordinate2D);
|
interiorPolygons:innerPolygons];
|
||||||
return coordinates;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
template<typename U = MLNPolygon, typename V = MLNPolygonFeature>
|
|
||||||
static U *toShape(const mbgl::Polygon<T> &geometry, const bool isInFeature) {
|
|
||||||
auto &linearRing = geometry.front();
|
|
||||||
std::vector<CLLocationCoordinate2D> coordinates = toLocationCoordinates2D(linearRing);
|
|
||||||
NSMutableArray *innerPolygons;
|
|
||||||
if (geometry.size() > 1) {
|
|
||||||
innerPolygons = [NSMutableArray arrayWithCapacity:geometry.size() - 1];
|
|
||||||
for (auto iter = geometry.begin() + 1; iter != geometry.end(); iter++) {
|
|
||||||
auto &innerRing = *iter;
|
|
||||||
std::vector<CLLocationCoordinate2D> innerCoordinates = toLocationCoordinates2D(innerRing);
|
|
||||||
MLNPolygon *innerPolygon = [MLNPolygon polygonWithCoordinates:&innerCoordinates[0] count:innerCoordinates.size()];
|
|
||||||
[innerPolygons addObject:innerPolygon];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Class shapeClass = isInFeature ? [V class] : [U class];
|
|
||||||
return [shapeClass polygonWithCoordinates:&coordinates[0] count:coordinates.size() interiorPolygons:innerPolygons];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class GeoJSONEvaluator {
|
class GeoJSONEvaluator {
|
||||||
public:
|
public:
|
||||||
MLNShape * operator()(const mbgl::Geometry<T> &geometry) const {
|
MLNShape *operator()(const mbgl::Geometry<T> &geometry) const {
|
||||||
GeometryEvaluator<T> evaluator;
|
GeometryEvaluator<T> evaluator;
|
||||||
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
|
MLNShape *shape = mapbox::geometry::geometry<T>::visit(geometry, evaluator);
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape <MLNFeature> * operator()(const mbgl::GeoJSONFeature &feature) const {
|
MLNShape<MLNFeature> *operator()(const mbgl::GeoJSONFeature &feature) const {
|
||||||
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)MLNFeatureFromMBGLFeature(feature);
|
MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)MLNFeatureFromMBGLFeature(feature);
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape <MLNFeature> * operator()(const mbgl::FeatureCollection &collection) const {
|
MLNShape<MLNFeature> *operator()(const mbgl::FeatureCollection &collection) const {
|
||||||
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
|
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:collection.size()];
|
||||||
for (const auto &feature : collection) {
|
for (const auto &feature : collection) {
|
||||||
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
|
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
|
||||||
}
|
|
||||||
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
|
|
||||||
}
|
}
|
||||||
|
return [MLNShapeCollectionFeature shapeCollectionWithShapes:shapes];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::Feature> &features) {
|
NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
|
||||||
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
|
const std::vector<mbgl::Feature> &features) {
|
||||||
for (const auto &feature : features) {
|
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
|
||||||
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
|
for (const auto &feature : features) {
|
||||||
}
|
[shapes addObject:MLNFeatureFromMBGLFeature(static_cast<mbgl::GeoJSONFeature>(feature))];
|
||||||
return shapes;
|
}
|
||||||
|
return shapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSArray<MLNShape <MLNFeature> *> *MLNFeaturesFromMBGLFeatures(const std::vector<mbgl::GeoJSONFeature> &features) {
|
NSArray<MLNShape<MLNFeature> *> *MLNFeaturesFromMBGLFeatures(
|
||||||
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
|
const std::vector<mbgl::GeoJSONFeature> &features) {
|
||||||
for (const auto &feature : features) {
|
NSMutableArray *shapes = [NSMutableArray arrayWithCapacity:features.size()];
|
||||||
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
|
for (const auto &feature : features) {
|
||||||
}
|
[shapes addObject:MLNFeatureFromMBGLFeature(feature)];
|
||||||
return shapes;
|
}
|
||||||
|
return shapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
id <MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
|
id<MLNFeature> MLNFeatureFromMBGLFeature(const mbgl::GeoJSONFeature &feature) {
|
||||||
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
|
NSMutableDictionary *attributes =
|
||||||
for (auto &pair : feature.properties) {
|
[NSMutableDictionary dictionaryWithCapacity:feature.properties.size()];
|
||||||
auto &value = pair.second;
|
for (auto &pair : feature.properties) {
|
||||||
ValueEvaluator evaluator;
|
auto &value = pair.second;
|
||||||
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
|
ValueEvaluator evaluator;
|
||||||
}
|
attributes[@(pair.first.c_str())] = mbgl::Value::visit(value, evaluator);
|
||||||
GeometryEvaluator<double> evaluator(&feature.properties, true);
|
}
|
||||||
MLNShape <MLNFeature> *shape = (MLNShape <MLNFeature> *)mapbox::geometry::geometry<double>::visit(feature.geometry, evaluator);
|
GeometryEvaluator<double> evaluator(&feature.properties, true);
|
||||||
if (!feature.id.is<mapbox::feature::null_value_t>()) {
|
MLNShape<MLNFeature> *shape = (MLNShape<MLNFeature> *)mapbox::geometry::geometry<double>::visit(
|
||||||
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
|
feature.geometry, evaluator);
|
||||||
}
|
if (!feature.id.is<mapbox::feature::null_value_t>()) {
|
||||||
shape.attributes = attributes;
|
shape.identifier = mbgl::FeatureIdentifier::visit(feature.id, ValueEvaluator());
|
||||||
|
}
|
||||||
|
shape.attributes = attributes;
|
||||||
|
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShape* MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
|
MLNShape *MLNShapeFromGeoJSON(const mapbox::geojson::geojson &geojson) {
|
||||||
GeoJSONEvaluator<double> evaluator;
|
GeoJSONEvaluator<double> evaluator;
|
||||||
MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator);
|
MLNShape *shape = mapbox::geojson::geojson::visit(geojson, evaluator);
|
||||||
return shape;
|
return shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier, NSDictionary *attributes)
|
mbgl::GeoJSONFeature mbglFeature(mbgl::GeoJSONFeature feature, id identifier,
|
||||||
{
|
NSDictionary *attributes) {
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
|
NSExpression *identifierExpression = [NSExpression expressionForConstantValue:identifier];
|
||||||
feature.id = [identifierExpression mgl_featureIdentifier];
|
feature.id = [identifierExpression mgl_featureIdentifier];
|
||||||
}
|
}
|
||||||
feature.properties = [attributes mgl_propertyMap];
|
feature.properties = [attributes mgl_propertyMap];
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry, NSDictionary *attributes, id identifier) {
|
NSDictionary<NSString *, id> *NSDictionaryFeatureForGeometry(NSDictionary *geometry,
|
||||||
NSMutableDictionary *feature = [@{@"type": @"Feature",
|
NSDictionary *attributes,
|
||||||
@"properties": attributes,
|
id identifier) {
|
||||||
@"geometry": geometry} mutableCopy];
|
NSMutableDictionary *feature =
|
||||||
feature[@"id"] = identifier;
|
[@{@"type" : @"Feature", @"properties" : attributes, @"geometry" : geometry} mutableCopy];
|
||||||
return [feature copy];
|
feature[@"id"] = identifier;
|
||||||
|
return [feature copy];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,22 @@
|
|||||||
@implementation MLNForegroundStyleLayer
|
@implementation MLNForegroundStyleLayer
|
||||||
|
|
||||||
- (NSString *)sourceIdentifier {
|
- (NSString *)sourceIdentifier {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException
|
||||||
format:@"MLNForegroundStyleLayer is an abstract class"];
|
format:@"MLNForegroundStyleLayer is an abstract class"];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
if (self.rawLayer) {
|
if (self.rawLayer) {
|
||||||
return [NSString stringWithFormat:
|
return [NSString
|
||||||
@"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>",
|
stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; visible = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier,
|
NSStringFromClass([self class]), (void *)self, self.identifier,
|
||||||
self.sourceIdentifier, self.visible ? @"YES" : @"NO"];
|
self.sourceIdentifier, self.visible ? @"YES" : @"NO"];
|
||||||
}
|
} else {
|
||||||
else {
|
return [NSString
|
||||||
return [NSString stringWithFormat:
|
stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; visible = NO>",
|
||||||
@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; visible = NO>",
|
NSStringFromClass([self class]), (void *)self, self.identifier];
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier];
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
#import "MLNFoundation_Private.h"
|
#import "MLNFoundation_Private.h"
|
||||||
|
|
||||||
/// Initializes the run loop shim that lives on the main thread.
|
/// Initializes the run loop shim that lives on the main thread.
|
||||||
void MLNInitializeRunLoop() {
|
void MLNInitializeRunLoop() { static mbgl::util::RunLoop runLoop; }
|
||||||
static mbgl::util::RunLoop runLoop;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,113 +19,141 @@ const CLLocationDegrees MLNAngularFieldOfView = 30;
|
|||||||
const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0};
|
const MLNCoordinateSpan MLNCoordinateSpanZero = {0, 0};
|
||||||
|
|
||||||
CGRect MLNExtendRect(CGRect rect, CGPoint point) {
|
CGRect MLNExtendRect(CGRect rect, CGPoint point) {
|
||||||
if (point.x < rect.origin.x) {
|
if (point.x < rect.origin.x) {
|
||||||
rect.size.width += rect.origin.x - point.x;
|
rect.size.width += rect.origin.x - point.x;
|
||||||
rect.origin.x = point.x;
|
rect.origin.x = point.x;
|
||||||
}
|
}
|
||||||
if (point.x > rect.origin.x + rect.size.width) {
|
if (point.x > rect.origin.x + rect.size.width) {
|
||||||
rect.size.width += point.x - (rect.origin.x + rect.size.width);
|
rect.size.width += point.x - (rect.origin.x + rect.size.width);
|
||||||
}
|
}
|
||||||
if (point.y < rect.origin.y) {
|
if (point.y < rect.origin.y) {
|
||||||
rect.size.height += rect.origin.y - point.y;
|
rect.size.height += rect.origin.y - point.y;
|
||||||
rect.origin.y = point.y;
|
rect.origin.y = point.y;
|
||||||
}
|
}
|
||||||
if (point.y > rect.origin.y + rect.size.height) {
|
if (point.y > rect.origin.y + rect.size.height) {
|
||||||
rect.size.height += point.y - (rect.origin.y + rect.size.height);
|
rect.size.height += point.y - (rect.origin.y + rect.size.height);
|
||||||
}
|
}
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
|
mbgl::LatLng MLNLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
|
||||||
try {
|
try {
|
||||||
return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
|
return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
|
||||||
} catch (std::domain_error &error) {
|
} catch (std::domain_error &error) {
|
||||||
[NSException raise:NSInvalidArgumentException format:@"%s", error.what()];
|
[NSException raise:NSInvalidArgumentException format:@"%s", error.what()];
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
|
CLLocationDistance MLNAltitudeForZoomLevel(double zoomLevel, CGFloat pitch,
|
||||||
CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
|
CLLocationDegrees latitude, CGSize size) {
|
||||||
CLLocationDistance metersTall = metersPerPixel * size.height;
|
CLLocationDistance metersPerPixel =
|
||||||
CLLocationDistance altitude = metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
|
mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
|
||||||
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
|
CLLocationDistance metersTall = metersPerPixel * size.height;
|
||||||
|
CLLocationDistance altitude =
|
||||||
|
metersTall / 2 / std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
|
||||||
|
return altitude * std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
|
double MLNZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch,
|
||||||
CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
|
CLLocationDegrees latitude, CGSize size) {
|
||||||
CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
|
CLLocationDistance eyeAltitude =
|
||||||
CLLocationDistance metersPerPixel = metersTall / size.height;
|
altitude / std::sin(M_PI_2 - MLNRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
|
||||||
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel;
|
CLLocationDistance metersTall =
|
||||||
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
|
eyeAltitude * 2 * std::tan(MLNRadiansFromDegrees(MLNAngularFieldOfView) / 2.);
|
||||||
|
CLLocationDistance metersPerPixel = metersTall / size.height;
|
||||||
|
CGFloat mapPixelWidthAtZoom = std::cos(MLNRadiansFromDegrees(latitude)) * mbgl::util::M2PI *
|
||||||
|
mbgl::util::EARTH_RADIUS_M / metersPerPixel;
|
||||||
|
return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize_D);
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) {
|
MLNRadianDistance MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinate2D from,
|
||||||
double a = pow(sin((to.latitude - from.latitude) / 2), 2)
|
MLNRadianCoordinate2D to) {
|
||||||
+ pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude);
|
double a =
|
||||||
|
pow(sin((to.latitude - from.latitude) / 2), 2) +
|
||||||
|
pow(sin((to.longitude - from.longitude) / 2), 2) * cos(from.latitude) * cos(to.latitude);
|
||||||
|
|
||||||
return 2 * atan2(sqrt(a), sqrt(1 - a));
|
return 2 * atan2(sqrt(a), sqrt(1 - a));
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from, MLNRadianCoordinate2D to) {
|
MLNRadianDirection MLNRadianCoordinatesDirection(MLNRadianCoordinate2D from,
|
||||||
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
|
MLNRadianCoordinate2D to) {
|
||||||
double b = cos(from.latitude) * sin(to.latitude)
|
double a = sin(to.longitude - from.longitude) * cos(to.latitude);
|
||||||
- sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
|
double b = cos(from.latitude) * sin(to.latitude) -
|
||||||
return atan2(a, b);
|
sin(from.latitude) * cos(to.latitude) * cos(to.longitude - from.longitude);
|
||||||
|
return atan2(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate,
|
MLNRadianCoordinate2D MLNRadianCoordinateAtDistanceFacingDirection(MLNRadianCoordinate2D coordinate,
|
||||||
MLNRadianDistance distance,
|
MLNRadianDistance distance,
|
||||||
MLNRadianDirection direction) {
|
MLNRadianDirection direction) {
|
||||||
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance)
|
double otherLatitude = asin(sin(coordinate.latitude) * cos(distance) +
|
||||||
+ cos(coordinate.latitude) * sin(distance) * cos(direction));
|
cos(coordinate.latitude) * sin(distance) * cos(direction));
|
||||||
double otherLongitude = coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
|
double otherLongitude =
|
||||||
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
|
coordinate.longitude + atan2(sin(direction) * sin(distance) * cos(coordinate.latitude),
|
||||||
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude);
|
cos(distance) - sin(coordinate.latitude) * sin(otherLatitude));
|
||||||
|
return MLNRadianCoordinate2DMake(otherLatitude, otherLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate, CLLocationCoordinate2D secondCoordinate) {
|
CLLocationDirection MLNDirectionBetweenCoordinates(CLLocationCoordinate2D firstCoordinate,
|
||||||
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) && MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
|
CLLocationCoordinate2D secondCoordinate) {
|
||||||
return 0;
|
if (MLNEqualFloatWithAccuracy(firstCoordinate.latitude, secondCoordinate.latitude, 1e-8) &&
|
||||||
}
|
MLNEqualFloatWithAccuracy(firstCoordinate.longitude, secondCoordinate.longitude, 1e-8)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ported from
|
||||||
|
// https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
|
||||||
|
MLNRadianCoordinate2D firstRadianCoordinate =
|
||||||
|
MLNRadianCoordinateFromLocationCoordinate(firstCoordinate);
|
||||||
|
MLNRadianCoordinate2D secondRadianCoordinate =
|
||||||
|
MLNRadianCoordinateFromLocationCoordinate(secondCoordinate);
|
||||||
|
|
||||||
// Ported from https://github.com/mapbox/turf-swift/blob/857e2e8060678ef4a7a9169d4971b0788fdffc37/Turf/Turf.swift#L23-L31
|
CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) *
|
||||||
MLNRadianCoordinate2D firstRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(firstCoordinate);
|
cos(secondRadianCoordinate.latitude);
|
||||||
MLNRadianCoordinate2D secondRadianCoordinate = MLNRadianCoordinateFromLocationCoordinate(secondCoordinate);
|
CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude) -
|
||||||
|
sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) *
|
||||||
CGFloat a = sin(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude) * cos(secondRadianCoordinate.latitude);
|
cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
|
||||||
CGFloat b = (cos(firstRadianCoordinate.latitude) * sin(secondRadianCoordinate.latitude)
|
MLNRadianDirection radianDirection = atan2(a, b);
|
||||||
- sin(firstRadianCoordinate.latitude) * cos(secondRadianCoordinate.latitude) * cos(secondRadianCoordinate.longitude - firstRadianCoordinate.longitude));
|
return radianDirection * 180 / M_PI;
|
||||||
MLNRadianDirection radianDirection = atan2(a, b);
|
|
||||||
return radianDirection * 180 / M_PI;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CGPoint MLNPointRounded(CGPoint point) {
|
CGPoint MLNPointRounded(CGPoint point) {
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
|
CGFloat scaleFactor = [UIScreen mainScreen].nativeScale;
|
||||||
#elif TARGET_OS_MAC
|
#elif TARGET_OS_MAC
|
||||||
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
CGFloat scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
||||||
#endif
|
#endif
|
||||||
return CGPointMake(round(point.x * scaleFactor) / scaleFactor, round(point.y * scaleFactor) / scaleFactor);
|
return CGPointMake(round(point.x * scaleFactor) / scaleFactor,
|
||||||
|
round(point.y * scaleFactor) / scaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNMapPoint MLNMapPointForCoordinate(CLLocationCoordinate2D coordinate, double zoomLevel) {
|
MLNMapPoint MLNMapPointForCoordinate(CLLocationCoordinate2D coordinate, double zoomLevel) {
|
||||||
mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel));
|
mbgl::Point<double> projectedCoordinate = mbgl::Projection::project(
|
||||||
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel);
|
MLNLatLngFromLocationCoordinate2D(coordinate), std::pow(2.0, zoomLevel));
|
||||||
|
return MLNMapPointMake(projectedCoordinate.x, projectedCoordinate.y, zoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) {
|
MLNMatrix4 MLNMatrix4Make(std::array<double, 16> array) {
|
||||||
MLNMatrix4 mat4 = {
|
MLNMatrix4 mat4 = {.m00 = array[0],
|
||||||
.m00 = array[0], .m01 = array[1], .m02 = array[2], .m03 = array[3],
|
.m01 = array[1],
|
||||||
.m10 = array[4], .m11 = array[5], .m12 = array[6], .m13 = array[7],
|
.m02 = array[2],
|
||||||
.m20 = array[8], .m21 = array[9], .m22 = array[10], .m23 = array[11],
|
.m03 = array[3],
|
||||||
.m30 = array[12], .m31 = array[13], .m32 = array[14], .m33 = array[15]
|
.m10 = array[4],
|
||||||
};
|
.m11 = array[5],
|
||||||
return mat4;
|
.m12 = array[6],
|
||||||
|
.m13 = array[7],
|
||||||
|
.m20 = array[8],
|
||||||
|
.m21 = array[9],
|
||||||
|
.m22 = array[10],
|
||||||
|
.m23 = array[11],
|
||||||
|
.m30 = array[12],
|
||||||
|
.m31 = array[13],
|
||||||
|
.m32 = array[14],
|
||||||
|
.m33 = array[15]};
|
||||||
|
return mat4;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy)
|
BOOL MLNEqualFloatWithAccuracy(CGFloat left, CGFloat right, CGFloat accuracy) {
|
||||||
{
|
return MAX(left, right) - MIN(left, right) <= accuracy;
|
||||||
return MAX(left, right) - MIN(left, right) <= accuracy;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,17 @@
|
|||||||
#import "MLNTileSource_Private.h"
|
#import "MLNTileSource_Private.h"
|
||||||
#import "NSURL+MLNAdditions.h"
|
#import "NSURL+MLNAdditions.h"
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import "UIImage+MLNAdditions.h"
|
#import "UIImage+MLNAdditions.h"
|
||||||
#else
|
#else
|
||||||
#import "NSImage+MLNAdditions.h"
|
#import "NSImage+MLNAdditions.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mbgl/style/sources/image_source.hpp>
|
#include <mbgl/style/sources/image_source.hpp>
|
||||||
#include <mbgl/util/premultiply.hpp>
|
#include <mbgl/util/premultiply.hpp>
|
||||||
|
|
||||||
@interface MLNImageSource ()
|
@interface MLNImageSource ()
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
|
coordinateQuad:(MLNCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
@property (nonatomic, readonly) mbgl::style::ImageSource *rawSource;
|
@property (nonatomic, readonly) mbgl::style::ImageSource *rawSource;
|
||||||
|
|
||||||
@@ -23,90 +24,93 @@
|
|||||||
|
|
||||||
@implementation MLNImageSource
|
@implementation MLNImageSource
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
|
coordinateQuad:(MLNCoordinateQuad)coordinateQuad {
|
||||||
const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad);
|
const auto coordsArray = MLNLatLngArrayFromCoordinateQuad(coordinateQuad);
|
||||||
auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray);
|
auto source = std::make_unique<mbgl::style::ImageSource>(identifier.UTF8String, coordsArray);
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad URL:(NSURL *)url {
|
coordinateQuad:(MLNCoordinateQuad)coordinateQuad
|
||||||
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad];
|
URL:(NSURL *)url {
|
||||||
self.URL = url;
|
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
|
||||||
return self;
|
self.URL = url;
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
|
coordinateQuad:(MLNCoordinateQuad)coordinateQuad
|
||||||
|
image:(MLNImage *)image {
|
||||||
|
self = [self initWithIdentifier:identifier coordinateQuad:coordinateQuad];
|
||||||
|
self.image = image;
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MLNCoordinateQuad)coordinateQuad image:(MLNImage *)image {
|
return self;
|
||||||
self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad];
|
|
||||||
self.image = image;
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL *)URL {
|
- (NSURL *)URL {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
auto url = self.rawSource->getURL();
|
auto url = self.rawSource->getURL();
|
||||||
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setURL:(NSURL *)url {
|
- (void)setURL:(NSURL *)url {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
if (url) {
|
if (url) {
|
||||||
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
||||||
_image = nil;
|
_image = nil;
|
||||||
} else {
|
} else {
|
||||||
self.image = nullptr;
|
self.image = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setImage:(MLNImage *)image {
|
- (void)setImage:(MLNImage *)image {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
if (image != nullptr) {
|
if (image != nullptr) {
|
||||||
self.rawSource->setImage(image.mgl_premultipliedImage);
|
self.rawSource->setImage(image.mgl_premultipliedImage);
|
||||||
} else {
|
} else {
|
||||||
self.rawSource->setImage(mbgl::PremultipliedImage({0,0}));
|
self.rawSource->setImage(mbgl::PremultipliedImage({0, 0}));
|
||||||
}
|
}
|
||||||
_image = image;
|
_image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNCoordinateQuad)coordinates {
|
- (MLNCoordinateQuad)coordinates {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates());
|
return MLNCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setCoordinates: (MLNCoordinateQuad)coordinateQuad {
|
- (void)setCoordinates:(MLNCoordinateQuad)coordinateQuad {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad));
|
self.rawSource->setCoordinates(MLNLatLngArrayFromCoordinateQuad(coordinateQuad));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
if (self.rawSource) {
|
if (self.rawSource) {
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
|
return [NSString
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier,
|
stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>",
|
||||||
MLNStringFromCoordinateQuad(self.coordinates),
|
NSStringFromClass([self class]), (void *)self, self.identifier,
|
||||||
self.URL,
|
MLNStringFromCoordinateQuad(self.coordinates), self.URL, self.image];
|
||||||
self.image];
|
} else {
|
||||||
}
|
return [NSString
|
||||||
else {
|
stringWithFormat:
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
|
@"<%@: %p; identifier = %@; coordinates = <unknown>; URL = <unknown>; image = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier, self.image];
|
NSStringFromClass([self class]), (void *)self, self.identifier, self.image];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::ImageSource *)rawSource {
|
- (mbgl::style::ImageSource *)rawSource {
|
||||||
return (mbgl::style::ImageSource *)super.rawSource;
|
return (mbgl::style::ImageSource *)super.rawSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)attributionHTMLString {
|
- (NSString *)attributionHTMLString {
|
||||||
if (!self.rawSource) {
|
if (!self.rawSource) {
|
||||||
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
|
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
|
||||||
return nil;
|
self.identifier);
|
||||||
}
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
auto attribution = self.rawSource->getAttribution();
|
auto attribution = self.rawSource->getAttribution();
|
||||||
return attribution ? @(attribution->c_str()) : nil;
|
return attribution ? @(attribution->c_str()) : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include <mbgl/util/logging.hpp>
|
|
||||||
#include <mbgl/util/enum.hpp>
|
#include <mbgl/util/enum.hpp>
|
||||||
|
#include <mbgl/util/logging.hpp>
|
||||||
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
|
||||||
@@ -10,154 +10,155 @@
|
|||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
class MLNCoreLoggingObserver : public Log :: Observer {
|
class MLNCoreLoggingObserver : public Log ::Observer {
|
||||||
public:
|
public:
|
||||||
//Return true not print messages at core level, and filter at platform level.
|
// Return true not print messages at core level, and filter at platform level.
|
||||||
bool onRecord(EventSeverity severity, Event event, int64_t code, const std::string& msg) override{
|
bool onRecord(EventSeverity severity, Event event, int64_t code,
|
||||||
|
const std::string &msg) override {
|
||||||
NSString *message = [NSString stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code, [NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
|
NSString *message = [NSString
|
||||||
switch (severity) {
|
stringWithFormat:@"[event]:%s [code]:%lld [message]:%@", Enum<Event>::toString(event), code,
|
||||||
case EventSeverity::Debug:
|
[NSString stringWithCString:msg.c_str() encoding:NSUTF8StringEncoding]];
|
||||||
MLNLogDebug(message);
|
switch (severity) {
|
||||||
break;
|
case EventSeverity::Debug:
|
||||||
case EventSeverity::Info:
|
MLNLogDebug(message);
|
||||||
MLNLogInfo(message);
|
break;
|
||||||
break;
|
case EventSeverity::Info:
|
||||||
case EventSeverity::Warning:
|
MLNLogInfo(message);
|
||||||
MLNLogWarning(message);
|
break;
|
||||||
break;
|
case EventSeverity::Warning:
|
||||||
case EventSeverity::Error:
|
MLNLogWarning(message);
|
||||||
MLNLogError(message);
|
break;
|
||||||
break;
|
case EventSeverity::Error:
|
||||||
default:
|
MLNLogError(message);
|
||||||
assert(false);
|
break;
|
||||||
}
|
default:
|
||||||
return true;
|
assert(false);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
} // namespace mbgl
|
||||||
|
|
||||||
@implementation MLNLoggingConfiguration
|
@implementation MLNLoggingConfiguration {
|
||||||
{
|
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
|
||||||
std::unique_ptr<mbgl::MLNCoreLoggingObserver> _coreLoggingObserver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)sharedConfiguration {
|
+ (instancetype)sharedConfiguration {
|
||||||
static dispatch_once_t once;
|
static dispatch_once_t once;
|
||||||
static id sharedConfiguration;
|
static id sharedConfiguration;
|
||||||
dispatch_once(&once, ^{
|
dispatch_once(&once, ^{
|
||||||
sharedConfiguration = [[self alloc] init];
|
sharedConfiguration = [[self alloc] init];
|
||||||
((MLNLoggingConfiguration *)sharedConfiguration).handler = nil;
|
((MLNLoggingConfiguration *)sharedConfiguration).handler = nil;
|
||||||
});
|
});
|
||||||
return sharedConfiguration;
|
return sharedConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)init{
|
- (id)init {
|
||||||
if(self = [super init]){
|
if (self = [super init]) {
|
||||||
mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>());
|
mbgl::Log::setObserver(std::make_unique<mbgl::MLNCoreLoggingObserver>());
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler {
|
- (void)setHandler:(void (^)(MLNLoggingLevel, NSString *, NSUInteger, NSString *))handler {
|
||||||
|
if (!handler) {
|
||||||
if (!handler) {
|
_handler = [self defaultBlockHandler];
|
||||||
_handler = [self defaultBlockHandler];
|
} else {
|
||||||
} else {
|
_handler = handler;
|
||||||
_handler = handler;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)logCallingFunction:(const char *)callingFunction functionLine:(NSUInteger)functionLine messageType:(MLNLoggingLevel)type format:(id)messageFormat, ... {
|
- (void)logCallingFunction:(const char *)callingFunction
|
||||||
va_list formatList;
|
functionLine:(NSUInteger)functionLine
|
||||||
va_start(formatList, messageFormat);
|
messageType:(MLNLoggingLevel)type
|
||||||
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
|
format:(id)messageFormat, ... {
|
||||||
va_end(formatList);
|
va_list formatList;
|
||||||
|
va_start(formatList, messageFormat);
|
||||||
_handler(type, @(callingFunction), functionLine, formattedMessage);
|
NSString *formattedMessage = [[NSString alloc] initWithFormat:messageFormat arguments:formatList];
|
||||||
|
va_end(formatList);
|
||||||
|
|
||||||
|
_handler(type, @(callingFunction), functionLine, formattedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNLoggingBlockHandler)defaultBlockHandler {
|
- (MLNLoggingBlockHandler)defaultBlockHandler {
|
||||||
MLNLoggingBlockHandler maplibreHandler = ^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) {
|
MLNLoggingBlockHandler maplibreHandler =
|
||||||
|
^(MLNLoggingLevel level, NSString *fileName, NSUInteger line, NSString *message) {
|
||||||
if (@available(iOS 10.0, macOS 10.12.0, *)) {
|
if (@available(iOS 10.0, macOS 10.12.0, *)) {
|
||||||
static dispatch_once_t once;
|
static dispatch_once_t once;
|
||||||
static os_log_t info_log;
|
static os_log_t info_log;
|
||||||
#if MLN_LOGGING_ENABLE_DEBUG
|
#if MLN_LOGGING_ENABLE_DEBUG
|
||||||
static os_log_t debug_log;
|
static os_log_t debug_log;
|
||||||
#endif
|
#endif
|
||||||
static os_log_t error_log;
|
static os_log_t error_log;
|
||||||
static os_log_t fault_log;
|
static os_log_t fault_log;
|
||||||
static os_log_type_t log_types[] = { OS_LOG_TYPE_DEFAULT,
|
static os_log_type_t log_types[] = {OS_LOG_TYPE_DEFAULT, OS_LOG_TYPE_INFO,
|
||||||
OS_LOG_TYPE_INFO,
|
|
||||||
#if MLN_LOGGING_ENABLE_DEBUG
|
#if MLN_LOGGING_ENABLE_DEBUG
|
||||||
OS_LOG_TYPE_DEBUG,
|
OS_LOG_TYPE_DEBUG,
|
||||||
#endif
|
#endif
|
||||||
OS_LOG_TYPE_ERROR,
|
OS_LOG_TYPE_ERROR, OS_LOG_TYPE_FAULT};
|
||||||
OS_LOG_TYPE_FAULT };
|
constexpr const char *const subsystem = "org.maplibre.Native";
|
||||||
constexpr const char* const subsystem = "org.maplibre.Native";
|
dispatch_once(&once, ^{
|
||||||
dispatch_once(&once, ^ {
|
info_log = os_log_create(subsystem, "INFO");
|
||||||
info_log = os_log_create(subsystem, "INFO");
|
|
||||||
#if MLN_LOGGING_ENABLE_DEBUG
|
#if MLN_LOGGING_ENABLE_DEBUG
|
||||||
debug_log = os_log_create(subsystem, "DEBUG");
|
debug_log = os_log_create(subsystem, "DEBUG");
|
||||||
#endif
|
#endif
|
||||||
error_log = os_log_create(subsystem, "ERROR");
|
error_log = os_log_create(subsystem, "ERROR");
|
||||||
fault_log = os_log_create(subsystem, "FAULT");
|
fault_log = os_log_create(subsystem, "FAULT");
|
||||||
});
|
});
|
||||||
|
|
||||||
os_log_t maplibre_log;
|
os_log_t maplibre_log;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case MLNLoggingLevelInfo:
|
case MLNLoggingLevelInfo:
|
||||||
case MLNLoggingLevelWarning:
|
case MLNLoggingLevelWarning:
|
||||||
maplibre_log = info_log;
|
maplibre_log = info_log;
|
||||||
break;
|
break;
|
||||||
#if MLN_LOGGING_ENABLE_DEBUG
|
#if MLN_LOGGING_ENABLE_DEBUG
|
||||||
case MLNLoggingLevelDebug:
|
case MLNLoggingLevelDebug:
|
||||||
maplibre_log = debug_log;
|
maplibre_log = debug_log;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case MLNLoggingLevelError:
|
case MLNLoggingLevelError:
|
||||||
maplibre_log = error_log;
|
maplibre_log = error_log;
|
||||||
break;
|
break;
|
||||||
case MLNLoggingLevelFault:
|
case MLNLoggingLevelFault:
|
||||||
maplibre_log = fault_log;
|
maplibre_log = fault_log;
|
||||||
break;
|
break;
|
||||||
case MLNLoggingLevelNone:
|
case MLNLoggingLevelNone:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
os_log_type_t logType = log_types[level];
|
os_log_type_t logType = log_types[level];
|
||||||
os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line, message);
|
os_log_with_type(maplibre_log, logType, "%@ - %lu: %@", fileName, (unsigned long)line,
|
||||||
|
message);
|
||||||
} else {
|
} else {
|
||||||
NSString *category;
|
NSString *category;
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case MLNLoggingLevelInfo:
|
case MLNLoggingLevelInfo:
|
||||||
case MLNLoggingLevelWarning:
|
case MLNLoggingLevelWarning:
|
||||||
category = @"INFO";
|
category = @"INFO";
|
||||||
break;
|
break;
|
||||||
#if MLN_LOGGING_ENABLE_DEBUG
|
#if MLN_LOGGING_ENABLE_DEBUG
|
||||||
case MLNLoggingLevelDebug:
|
case MLNLoggingLevelDebug:
|
||||||
category = @"DEBUG";
|
category = @"DEBUG";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case MLNLoggingLevelError:
|
case MLNLoggingLevelError:
|
||||||
category = @"ERROR";
|
category = @"ERROR";
|
||||||
break;
|
break;
|
||||||
case MLNLoggingLevelFault:
|
case MLNLoggingLevelFault:
|
||||||
category = @"FAULT";
|
category = @"FAULT";
|
||||||
break;
|
break;
|
||||||
case MLNLoggingLevelNone:
|
case MLNLoggingLevelNone:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message);
|
NSLog(@"[%@] %@ - %lu: %@", category, fileName, (unsigned long)line, message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return maplibreHandler;
|
return maplibreHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,191 +8,179 @@
|
|||||||
|
|
||||||
@implementation MLNMapCamera
|
@implementation MLNMapCamera
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding
|
+ (BOOL)supportsSecureCoding {
|
||||||
{
|
return YES;
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)camera
|
+ (instancetype)camera {
|
||||||
{
|
return [[self alloc] init];
|
||||||
return [[self alloc] init];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||||
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
|
fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
|
||||||
eyeAltitude:(CLLocationDistance)eyeAltitude
|
eyeAltitude:(CLLocationDistance)eyeAltitude {
|
||||||
{
|
CLLocationDirection heading = -1;
|
||||||
CLLocationDirection heading = -1;
|
CGFloat pitch = -1;
|
||||||
CGFloat pitch = -1;
|
if (CLLocationCoordinate2DIsValid(centerCoordinate) &&
|
||||||
if (CLLocationCoordinate2DIsValid(centerCoordinate) && CLLocationCoordinate2DIsValid(eyeCoordinate)) {
|
CLLocationCoordinate2DIsValid(eyeCoordinate)) {
|
||||||
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
|
heading = MLNDirectionBetweenCoordinates(eyeCoordinate, centerCoordinate);
|
||||||
|
|
||||||
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
|
CLLocation *centerLocation = [[CLLocation alloc] initWithLatitude:centerCoordinate.latitude
|
||||||
longitude:centerCoordinate.longitude];
|
longitude:centerCoordinate.longitude];
|
||||||
CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude
|
CLLocation *eyeLocation = [[CLLocation alloc] initWithLatitude:eyeCoordinate.latitude
|
||||||
longitude:eyeCoordinate.longitude];
|
longitude:eyeCoordinate.longitude];
|
||||||
CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation];
|
CLLocationDistance groundDistance = [eyeLocation distanceFromLocation:centerLocation];
|
||||||
CGFloat radianPitch = atan2(eyeAltitude, groundDistance);
|
CGFloat radianPitch = atan2(eyeAltitude, groundDistance);
|
||||||
pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0);
|
pitch = mbgl::util::wrap(90 - MLNDegreesFromRadians(radianPitch), 0.0, 360.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
||||||
altitude:eyeAltitude
|
altitude:eyeAltitude
|
||||||
pitch:pitch
|
pitch:pitch
|
||||||
heading:heading];
|
heading:heading];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||||
acrossDistance:(CLLocationDistance)distance
|
acrossDistance:(CLLocationDistance)distance
|
||||||
pitch:(CGFloat)pitch
|
pitch:(CGFloat)pitch
|
||||||
heading:(CLLocationDirection)heading
|
heading:(CLLocationDirection)heading {
|
||||||
{
|
// Angle at the viewpoint formed by the straight lines running perpendicular
|
||||||
// Angle at the viewpoint formed by the straight lines running perpendicular
|
// to the ground and toward the center coordinate.
|
||||||
// to the ground and toward the center coordinate.
|
CLLocationDirection eyeAngle = 90 - pitch;
|
||||||
CLLocationDirection eyeAngle = 90 - pitch;
|
CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
|
||||||
CLLocationDistance altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
|
|
||||||
|
|
||||||
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
||||||
altitude:altitude
|
altitude:altitude
|
||||||
pitch:pitch
|
pitch:pitch
|
||||||
heading:heading];
|
heading:heading];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||||
altitude:(CLLocationDistance)altitude
|
altitude:(CLLocationDistance)altitude
|
||||||
pitch:(CGFloat)pitch
|
pitch:(CGFloat)pitch
|
||||||
heading:(CLLocationDirection)heading
|
heading:(CLLocationDirection)heading {
|
||||||
{
|
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
||||||
return [[self alloc] initWithCenterCoordinate:centerCoordinate
|
altitude:altitude
|
||||||
altitude:altitude
|
pitch:pitch
|
||||||
pitch:pitch
|
heading:heading];
|
||||||
heading:heading];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
+ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||||
fromDistance:(CLLocationDistance)distance
|
fromDistance:(CLLocationDistance)distance
|
||||||
pitch:(CGFloat)pitch
|
pitch:(CGFloat)pitch
|
||||||
heading:(CLLocationDirection)heading
|
heading:(CLLocationDirection)heading {
|
||||||
{
|
// TODO: Remove this compatibility shim in favor of
|
||||||
// TODO: Remove this compatibility shim in favor of `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:.
|
// `-cameraLookingAtCenterCoordinate:acrossDistance:pitch:heading:.
|
||||||
// https://github.com/mapbox/mapbox-gl-native/issues/12943
|
// https://github.com/mapbox/mapbox-gl-native/issues/12943
|
||||||
return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
|
return [MLNMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
|
||||||
altitude:distance
|
altitude:distance
|
||||||
pitch:pitch
|
pitch:pitch
|
||||||
heading:heading];
|
heading:heading];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
|
||||||
altitude:(CLLocationDistance)altitude
|
altitude:(CLLocationDistance)altitude
|
||||||
pitch:(CGFloat)pitch
|
pitch:(CGFloat)pitch
|
||||||
heading:(CLLocationDirection)heading
|
heading:(CLLocationDirection)heading {
|
||||||
{
|
MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°",
|
||||||
MLNLogDebug(@"Initializing withCenterCoordinate: %@ altitude: %.0fm pitch: %f° heading: %f°", MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading);
|
MLNStringFromCLLocationCoordinate2D(centerCoordinate), altitude, pitch, heading);
|
||||||
if (self = [super init])
|
if (self = [super init]) {
|
||||||
{
|
_centerCoordinate = centerCoordinate;
|
||||||
_centerCoordinate = centerCoordinate;
|
_altitude = altitude;
|
||||||
_altitude = altitude;
|
_pitch = pitch;
|
||||||
_pitch = pitch;
|
_heading = heading;
|
||||||
_heading = heading;
|
_roll = 0;
|
||||||
_roll = 0;
|
}
|
||||||
}
|
return self;
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
{
|
MLNLogInfo(@"Initialiazing with coder.");
|
||||||
MLNLogInfo(@"Initialiazing with coder.");
|
if (self = [super init]) {
|
||||||
if (self = [super init])
|
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
|
||||||
{
|
[coder decodeDoubleForKey:@"centerLongitude"]);
|
||||||
_centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
|
_altitude = [coder decodeDoubleForKey:@"altitude"];
|
||||||
[coder decodeDoubleForKey:@"centerLongitude"]);
|
_pitch = [coder decodeDoubleForKey:@"pitch"];
|
||||||
_altitude = [coder decodeDoubleForKey:@"altitude"];
|
_heading = [coder decodeDoubleForKey:@"heading"];
|
||||||
_pitch = [coder decodeDoubleForKey:@"pitch"];
|
_roll = [coder decodeDoubleForKey:@"roll"];
|
||||||
_heading = [coder decodeDoubleForKey:@"heading"];
|
}
|
||||||
_roll = [coder decodeDoubleForKey:@"roll"];
|
return self;
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
{
|
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
|
||||||
[coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
|
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
|
||||||
[coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
|
[coder encodeDouble:_altitude forKey:@"altitude"];
|
||||||
[coder encodeDouble:_altitude forKey:@"altitude"];
|
[coder encodeDouble:_pitch forKey:@"pitch"];
|
||||||
[coder encodeDouble:_pitch forKey:@"pitch"];
|
[coder encodeDouble:_heading forKey:@"heading"];
|
||||||
[coder encodeDouble:_heading forKey:@"heading"];
|
[coder encodeDouble:_roll forKey:@"roll"];
|
||||||
[coder encodeDouble:_roll forKey:@"roll"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)copyWithZone:(nullable NSZone *)zone
|
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||||
{
|
MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
|
||||||
MLNMapCamera *copy = [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
|
altitude:_altitude
|
||||||
altitude:_altitude
|
pitch:_pitch
|
||||||
pitch:_pitch
|
heading:_heading];
|
||||||
heading:_heading];
|
copy.roll = _roll;
|
||||||
copy.roll = _roll;
|
return copy;
|
||||||
return copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSSet<NSString *> *)keyPathsForValuesAffectingViewingDistance {
|
+ (NSSet<NSString *> *)keyPathsForValuesAffectingViewingDistance {
|
||||||
return [NSSet setWithObjects:@"altitude", @"pitch", nil];
|
return [NSSet setWithObjects:@"altitude", @"pitch", nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationDistance)viewingDistance {
|
- (CLLocationDistance)viewingDistance {
|
||||||
CLLocationDirection eyeAngle = 90 - self.pitch;
|
CLLocationDirection eyeAngle = 90 - self.pitch;
|
||||||
return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle));
|
return self.altitude / sin(MLNRadiansFromDegrees(eyeAngle));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setViewingDistance:(CLLocationDistance)distance {
|
- (void)setViewingDistance:(CLLocationDistance)distance {
|
||||||
MLNLogDebug(@"Setting viewingDistance: %f", distance);
|
MLNLogDebug(@"Setting viewingDistance: %f", distance);
|
||||||
CLLocationDirection eyeAngle = 90 - self.pitch;
|
CLLocationDirection eyeAngle = 90 - self.pitch;
|
||||||
self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
|
self.altitude = distance * sin(MLNRadiansFromDegrees(eyeAngle));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; "
|
||||||
return [NSString stringWithFormat:@"<%@: %p; centerCoordinate = %f, %f; altitude = %.0fm; heading = %.0f°; pitch = %.0f°; roll = %.0f°>",
|
@"heading = %.0f°; pitch = %.0f°; roll = %.0f°>",
|
||||||
NSStringFromClass([self class]), (void *)self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch, _roll];
|
NSStringFromClass([self class]), (void *)self,
|
||||||
|
_centerCoordinate.latitude, _centerCoordinate.longitude,
|
||||||
|
_altitude, _heading, _pitch, _roll];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other
|
- (BOOL)isEqual:(id)other {
|
||||||
{
|
if (![other isKindOfClass:[self class]]) {
|
||||||
if ( ! [other isKindOfClass:[self class]])
|
return NO;
|
||||||
{
|
}
|
||||||
return NO;
|
if (other == self) {
|
||||||
}
|
return YES;
|
||||||
if (other == self)
|
}
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
MLNMapCamera *otherCamera = other;
|
MLNMapCamera *otherCamera = other;
|
||||||
return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude
|
return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude &&
|
||||||
&& _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude
|
_centerCoordinate.longitude == otherCamera.centerCoordinate.longitude &&
|
||||||
&& _altitude == otherCamera.altitude
|
_altitude == otherCamera.altitude && _pitch == otherCamera.pitch &&
|
||||||
&& _pitch == otherCamera.pitch && _heading == otherCamera.heading
|
_heading == otherCamera.heading && _roll == otherCamera.roll);
|
||||||
&& _roll == otherCamera.roll);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera
|
- (BOOL)isEqualToMapCamera:(MLNMapCamera *)otherCamera {
|
||||||
{
|
if (otherCamera == self) {
|
||||||
if (otherCamera == self)
|
return YES;
|
||||||
{
|
}
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude, otherCamera.centerCoordinate.latitude, 1e-8)
|
return (MLNEqualFloatWithAccuracy(_centerCoordinate.latitude,
|
||||||
&& MLNEqualFloatWithAccuracy(_centerCoordinate.longitude, otherCamera.centerCoordinate.longitude, 1e-8)
|
otherCamera.centerCoordinate.latitude, 1e-8) &&
|
||||||
&& MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6)
|
MLNEqualFloatWithAccuracy(_centerCoordinate.longitude,
|
||||||
&& MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2)
|
otherCamera.centerCoordinate.longitude, 1e-8) &&
|
||||||
&& MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2)
|
MLNEqualFloatWithAccuracy(_altitude, otherCamera.altitude, 1e-6) &&
|
||||||
&& MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2));
|
MLNEqualFloatWithAccuracy(_pitch, otherCamera.pitch, 1e-2) &&
|
||||||
|
MLNEqualFloatWithAccuracy(_heading, otherCamera.heading, 1e-2) &&
|
||||||
|
MLNEqualFloatWithAccuracy(_roll, otherCamera.roll, 1e-2));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash
|
- (NSUInteger)hash {
|
||||||
{
|
return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash +
|
||||||
return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash
|
@(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
|
||||||
+ @(_altitude).hash + @(_pitch).hash + @(_heading).hash + @(_roll).hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -6,19 +6,17 @@
|
|||||||
|
|
||||||
@implementation MLNMapOptions
|
@implementation MLNMapOptions
|
||||||
|
|
||||||
-(instancetype _Nonnull)init
|
- (instancetype _Nonnull)init {
|
||||||
{
|
self = [super init];
|
||||||
self = [super init];
|
if (self) {
|
||||||
if (self)
|
_styleURL = nil;
|
||||||
{
|
_styleJSON = nil;
|
||||||
_styleURL = nil;
|
_pluginLayers = nil;
|
||||||
_styleJSON = nil;
|
|
||||||
_pluginLayers = nil;
|
|
||||||
|
|
||||||
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
|
_actionJournalOptions = [[MLNActionJournalOptions alloc] init];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,208 +1,201 @@
|
|||||||
#import "MLNMultiPoint_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
#import "MLNShape_Private.h"
|
|
||||||
#import "NSCoder+MLNAdditions.h"
|
|
||||||
#import "MLNTypes.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNMultiPoint_Private.h"
|
||||||
|
#import "MLNShape_Private.h"
|
||||||
|
#import "MLNTypes.h"
|
||||||
|
#import "NSCoder+MLNAdditions.h"
|
||||||
|
|
||||||
@implementation MLNMultiPoint
|
@implementation MLNMultiPoint {
|
||||||
{
|
std::optional<mbgl::LatLngBounds> _bounds;
|
||||||
std::optional<mbgl::LatLngBounds> _bounds;
|
std::vector<CLLocationCoordinate2D> _coordinates;
|
||||||
std::vector<CLLocationCoordinate2D> _coordinates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count
|
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
||||||
{
|
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count);
|
||||||
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long) count);
|
self = [super init];
|
||||||
self = [super init];
|
|
||||||
|
|
||||||
if (self)
|
if (self) {
|
||||||
{
|
if (!count) {
|
||||||
if (!count) {
|
[NSException raise:NSInvalidArgumentException
|
||||||
[NSException raise:NSInvalidArgumentException
|
format:@"A multipoint must have at least one vertex."];
|
||||||
format:@"A multipoint must have at least one vertex."];
|
|
||||||
}
|
|
||||||
_coordinates = { coords, coords + count };
|
|
||||||
}
|
}
|
||||||
|
_coordinates = {coords, coords + count};
|
||||||
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)decoder
|
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
{
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
if (self = [super initWithCoder:decoder]) {
|
||||||
if (self = [super initWithCoder:decoder]) {
|
_coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"];
|
||||||
_coordinates = [decoder mgl_decodeLocationCoordinates2DForKey:@"coordinates"];
|
}
|
||||||
}
|
return self;
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
{
|
[super encodeWithCoder:coder];
|
||||||
[super encodeWithCoder:coder];
|
[coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"];
|
||||||
[coder mgl_encodeLocationCoordinates2D:_coordinates forKey:@"coordinates"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other
|
- (BOOL)isEqual:(id)other {
|
||||||
{
|
if (self == other) return YES;
|
||||||
if (self == other) return YES;
|
if (![other isKindOfClass:[MLNMultiPoint class]]) return NO;
|
||||||
if (![other isKindOfClass:[MLNMultiPoint class]]) return NO;
|
|
||||||
|
|
||||||
MLNMultiPoint *otherMultipoint = other;
|
MLNMultiPoint *otherMultipoint = other;
|
||||||
return ([super isEqual:otherMultipoint]
|
return ([super isEqual:otherMultipoint] && _coordinates == otherMultipoint->_coordinates);
|
||||||
&& _coordinates == otherMultipoint->_coordinates);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash
|
- (NSUInteger)hash {
|
||||||
{
|
NSUInteger hash = [super hash];
|
||||||
NSUInteger hash = [super hash];
|
for (auto coord : _coordinates) {
|
||||||
for (auto coord : _coordinates) {
|
hash += @(coord.latitude + coord.longitude).hash;
|
||||||
hash += @(coord.latitude+coord.longitude).hash;
|
}
|
||||||
}
|
return hash;
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
{
|
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
|
||||||
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
|
return _coordinates.at(0);
|
||||||
return _coordinates.at(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)pointCount
|
- (NSUInteger)pointCount {
|
||||||
{
|
return _coordinates.size();
|
||||||
return _coordinates.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSSet<NSString *> *)keyPathsForValuesAffectingPointCount
|
+ (NSSet<NSString *> *)keyPathsForValuesAffectingPointCount {
|
||||||
{
|
return [NSSet setWithObjects:@"coordinates", nil];
|
||||||
return [NSSet setWithObjects:@"coordinates", nil];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D *)coordinates
|
- (CLLocationCoordinate2D *)coordinates {
|
||||||
{
|
return _coordinates.data();
|
||||||
return _coordinates.data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range
|
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range {
|
||||||
{
|
if (range.location + range.length > [self pointCount]) {
|
||||||
if (range.location + range.length > [self pointCount])
|
[NSException
|
||||||
{
|
raise:NSRangeException
|
||||||
[NSException raise:NSRangeException
|
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
|
||||||
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
|
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
||||||
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), coords);
|
std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range),
|
||||||
|
coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
- (void)setCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
||||||
MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count);
|
MLNLogDebug(@"Setting: %lu coordinates.", (unsigned long)count);
|
||||||
if (!count) {
|
if (!count) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"A multipoint must have at least one vertex."];
|
format:@"A multipoint must have at least one vertex."];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self willChangeValueForKey:@"coordinates"];
|
[self willChangeValueForKey:@"coordinates"];
|
||||||
_coordinates = { coords, coords + count };
|
_coordinates = {coords, coords + count};
|
||||||
_bounds = {};
|
_bounds = {};
|
||||||
[self didChangeValueForKey:@"coordinates"];
|
[self didChangeValueForKey:@"coordinates"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)insertCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count atIndex:(NSUInteger)index {
|
- (void)insertCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count, (unsigned long)index);
|
count:(NSUInteger)count
|
||||||
if (!count) {
|
atIndex:(NSUInteger)index {
|
||||||
return;
|
MLNLogDebug(@"Inserting %lu coordinates at index %lu.", (unsigned long)count,
|
||||||
}
|
(unsigned long)index);
|
||||||
|
if (!count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (index > _coordinates.size()) {
|
if (index > _coordinates.size()) {
|
||||||
[NSException raise:NSRangeException
|
[NSException raise:NSRangeException
|
||||||
format:@"Invalid index %lu for existing coordinate count %ld",
|
format:@"Invalid index %lu for existing coordinate count %ld", (unsigned long)index,
|
||||||
(unsigned long)index, (unsigned long)[self pointCount]];
|
(unsigned long)[self pointCount]];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self willChangeValueForKey:@"coordinates"];
|
[self willChangeValueForKey:@"coordinates"];
|
||||||
_coordinates.insert(_coordinates.begin() + index, count, *coords);
|
_coordinates.insert(_coordinates.begin() + index, count, *coords);
|
||||||
_bounds = {};
|
_bounds = {};
|
||||||
[self didChangeValueForKey:@"coordinates"];
|
[self didChangeValueForKey:@"coordinates"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count
|
- (void)appendCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
||||||
{
|
MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count);
|
||||||
MLNLogDebug(@"Appending %lu coordinates.", (unsigned long)count);
|
[self insertCoordinates:coords count:count atIndex:_coordinates.size()];
|
||||||
[self insertCoordinates:coords count:count atIndex:_coordinates.size()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords
|
- (void)replaceCoordinatesInRange:(NSRange)range
|
||||||
{
|
withCoordinates:(const CLLocationCoordinate2D *)coords {
|
||||||
MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range));
|
MLNLogDebug(@"Replacing coordinates in range: %@", NSStringFromRange(range));
|
||||||
[self replaceCoordinatesInRange:range withCoordinates:coords count:range.length];
|
[self replaceCoordinatesInRange:range withCoordinates:coords count:range.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)replaceCoordinatesInRange:(NSRange)range withCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
- (void)replaceCoordinatesInRange:(NSRange)range
|
||||||
MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count, NSStringFromRange(range));
|
withCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
if (!count && !range.length) {
|
count:(NSUInteger)count {
|
||||||
return;
|
MLNLogDebug(@"Replacing %lu coordinates in range %@.", (unsigned long)count,
|
||||||
}
|
NSStringFromRange(range));
|
||||||
|
if (!count && !range.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSMaxRange(range) > _coordinates.size()) {
|
if (NSMaxRange(range) > _coordinates.size()) {
|
||||||
[NSException raise:NSRangeException
|
[NSException raise:NSRangeException
|
||||||
format:@"Invalid range %@ for existing coordinate count %ld",
|
format:@"Invalid range %@ for existing coordinate count %ld",
|
||||||
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
||||||
}
|
}
|
||||||
|
|
||||||
[self willChangeValueForKey:@"coordinates"];
|
[self willChangeValueForKey:@"coordinates"];
|
||||||
std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location);
|
std::copy(coords, coords + MIN(count, range.length), _coordinates.begin() + range.location);
|
||||||
if (count >= range.length) {
|
if (count >= range.length) {
|
||||||
_coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords, coords + count - range.length);
|
_coordinates.insert(_coordinates.begin() + NSMaxRange(range), coords,
|
||||||
} else {
|
coords + count - range.length);
|
||||||
_coordinates.erase(_coordinates.begin() + range.location + count, _coordinates.begin() + NSMaxRange(range));
|
} else {
|
||||||
}
|
_coordinates.erase(_coordinates.begin() + range.location + count,
|
||||||
_bounds = {};
|
_coordinates.begin() + NSMaxRange(range));
|
||||||
[self didChangeValueForKey:@"coordinates"];
|
}
|
||||||
|
_bounds = {};
|
||||||
|
[self didChangeValueForKey:@"coordinates"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeCoordinatesInRange:(NSRange)range {
|
- (void)removeCoordinatesInRange:(NSRange)range {
|
||||||
MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range));
|
MLNLogDebug(@"Removing coordinatesInRange: %@", NSStringFromRange(range));
|
||||||
CLLocationCoordinate2D coords;
|
CLLocationCoordinate2D coords;
|
||||||
[self replaceCoordinatesInRange:range withCoordinates:&coords count:0];
|
[self replaceCoordinatesInRange:range withCoordinates:&coords count:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNCoordinateBounds)overlayBounds
|
- (MLNCoordinateBounds)overlayBounds {
|
||||||
{
|
if (!_bounds) {
|
||||||
if (!_bounds) {
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
for (auto coordinate : _coordinates) {
|
||||||
for (auto coordinate : _coordinates) {
|
if (!MLNLocationCoordinate2DIsValid(coordinate)) {
|
||||||
if (!MLNLocationCoordinate2DIsValid(coordinate)) {
|
bounds = mbgl::LatLngBounds::empty();
|
||||||
bounds = mbgl::LatLngBounds::empty();
|
break;
|
||||||
break;
|
}
|
||||||
}
|
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
|
||||||
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
|
|
||||||
}
|
|
||||||
_bounds = bounds;
|
|
||||||
}
|
}
|
||||||
return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
|
_bounds = bounds;
|
||||||
|
}
|
||||||
|
return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds
|
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
||||||
{
|
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
|
||||||
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id <MLNMultiPointDelegate>)delegate
|
- (mbgl::Annotation)annotationObjectWithDelegate:(__unused id<MLNMultiPointDelegate>)delegate {
|
||||||
{
|
MLNAssert(NO, @"Cannot add an annotation from an instance of %@",
|
||||||
MLNAssert(NO, @"Cannot add an annotation from an instance of %@", NSStringFromClass([self class]));
|
NSStringFromClass([self class]));
|
||||||
return mbgl::SymbolAnnotation(mbgl::Point<double>());
|
return mbgl::SymbolAnnotation(mbgl::Point<double>());
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
|
||||||
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
|
NSStringFromClass([self class]), (void *)self,
|
||||||
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount],
|
(unsigned long)[self pointCount],
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,244 +1,238 @@
|
|||||||
#import "MLNNetworkConfiguration_Private.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNNetworkConfiguration_Private.h"
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
#import "MLNSettings_Private.h"
|
#import "MLNSettings_Private.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#import "MLNReachability.h"
|
#import "MLNReachability.h"
|
||||||
|
|
||||||
static NSString * const MLNStartTime = @"start_time";
|
static NSString *const MLNStartTime = @"start_time";
|
||||||
static NSString * const MLNResourceType = @"resource_type";
|
static NSString *const MLNResourceType = @"resource_type";
|
||||||
NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
|
NSString *const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
|
||||||
|
|
||||||
@interface MLNNetworkConfiguration () <MLNNativeNetworkDelegate>
|
@interface MLNNetworkConfiguration () <MLNNativeNetworkDelegate>
|
||||||
|
|
||||||
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary*> *events;
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSDictionary *> *events;
|
||||||
@property (nonatomic, weak) id<MLNNetworkConfigurationMetricsDelegate> metricsDelegate;
|
@property (nonatomic, weak) id<MLNNetworkConfigurationMetricsDelegate> metricsDelegate;
|
||||||
@property (nonatomic) dispatch_queue_t eventsQueue;
|
@property (nonatomic) dispatch_queue_t eventsQueue;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNNetworkConfiguration
|
@implementation MLNNetworkConfiguration {
|
||||||
{
|
NSURLSessionConfiguration *_sessionConfig;
|
||||||
NSURLSessionConfiguration *_sessionConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
self.sessionConfiguration = nil;
|
self.sessionConfiguration = nil;
|
||||||
_events = [NSMutableDictionary dictionary];
|
_events = [NSMutableDictionary dictionary];
|
||||||
_eventsQueue = dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT);
|
_eventsQueue =
|
||||||
}
|
dispatch_queue_create("org.maplibre.network-configuration", DISPATCH_QUEUE_CONCURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)sharedManager {
|
+ (instancetype)sharedManager {
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
static MLNNetworkConfiguration *_sharedManager;
|
static MLNNetworkConfiguration *_sharedManager;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
_sharedManager = [[self alloc] init];
|
_sharedManager = [[self alloc] init];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notice, this is reset for each call. This is primarily for testing purposes.
|
// Notice, this is reset for each call. This is primarily for testing purposes.
|
||||||
// TODO: Consider only calling this for testing?
|
// TODO: Consider only calling this for testing?
|
||||||
[_sharedManager resetNativeNetworkManagerDelegate];
|
[_sharedManager resetNativeNetworkManagerDelegate];
|
||||||
|
|
||||||
return _sharedManager;
|
return _sharedManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resetNativeNetworkManagerDelegate {
|
- (void)resetNativeNetworkManagerDelegate {
|
||||||
// Tell core about our network integration. `delegate` here is not (yet)
|
// Tell core about our network integration. `delegate` here is not (yet)
|
||||||
// intended to be set to nil, except for testing.
|
// intended to be set to nil, except for testing.
|
||||||
[MLNNativeNetworkManager sharedManager].delegate = self;
|
[MLNNativeNetworkManager sharedManager].delegate = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSURLSessionConfiguration *)defaultSessionConfiguration {
|
+ (NSURLSessionConfiguration *)defaultSessionConfiguration {
|
||||||
NSURLSessionConfiguration* sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfiguration =
|
||||||
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
|
|
||||||
sessionConfiguration.timeoutIntervalForResource = 30;
|
sessionConfiguration.timeoutIntervalForResource = 30;
|
||||||
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
|
sessionConfiguration.HTTPMaximumConnectionsPerHost = 8;
|
||||||
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
sessionConfiguration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||||
sessionConfiguration.URLCache = nil;
|
sessionConfiguration.URLCache = nil;
|
||||||
|
|
||||||
return sessionConfiguration;
|
return sessionConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MLNNativeNetworkDelegate
|
// MARK: - MLNNativeNetworkDelegate
|
||||||
|
|
||||||
- (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager {
|
- (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager {
|
||||||
// Note: this method is NOT called on the main thread.
|
// Note: this method is NOT called on the main thread.
|
||||||
NSURLSession *session;
|
NSURLSession *session;
|
||||||
if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) {
|
if ([self.delegate respondsToSelector:@selector(sessionForNetworkConfiguration:)]) {
|
||||||
session = [self.delegate sessionForNetworkConfiguration:self];
|
session = [self.delegate sessionForNetworkConfiguration:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a background session; string checking is fragile, but this is not
|
// Check for a background session; string checking is fragile, but this is not
|
||||||
// a deal breaker as we're only doing this to provide more clarity to the
|
// a deal breaker as we're only doing this to provide more clarity to the
|
||||||
// developer
|
// developer
|
||||||
NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")],
|
NSAssert(![session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")],
|
||||||
@"Background NSURLSessions are not yet supported");
|
@"Background NSURLSessions are not yet supported");
|
||||||
|
|
||||||
if (session.delegate) {
|
if (session.delegate) {
|
||||||
NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)],
|
NSAssert(![session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)],
|
||||||
@"Session delegates conforming to NSURLSessionDataDelegate are not yet supported");
|
@"Session delegates conforming to NSURLSessionDataDelegate are not yet supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request {
|
- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request {
|
||||||
|
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
|
||||||
|
return [self.delegate willSendRequest:request];
|
||||||
|
}
|
||||||
|
|
||||||
if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) {
|
return request;
|
||||||
return [self.delegate willSendRequest:request];
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response {
|
- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response {
|
||||||
|
if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
|
||||||
if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) {
|
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data
|
||||||
|
urlResponse:response.response
|
||||||
MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data
|
error:response.error];
|
||||||
urlResponse:response.response
|
if (tempResponse) {
|
||||||
error:response.error];
|
MLNInternalNetworkResponse *internalResponse =
|
||||||
if (tempResponse) {
|
[MLNInternalNetworkResponse responseWithData:tempResponse.data
|
||||||
MLNInternalNetworkResponse *internalResponse = [MLNInternalNetworkResponse responseWithData:tempResponse.data
|
urlResponse:tempResponse.response
|
||||||
urlResponse:tempResponse.response
|
error:tempResponse.error];
|
||||||
error:tempResponse.error];
|
return internalResponse;
|
||||||
return internalResponse;
|
} else {
|
||||||
} else {
|
return nil;
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (NSURLSessionConfiguration *)sessionConfiguration {
|
- (NSURLSessionConfiguration *)sessionConfiguration {
|
||||||
NSURLSessionConfiguration *sessionConfig = nil;
|
NSURLSessionConfiguration *sessionConfig = nil;
|
||||||
@synchronized (self) {
|
@synchronized(self) {
|
||||||
sessionConfig = _sessionConfig;
|
sessionConfig = _sessionConfig;
|
||||||
}
|
}
|
||||||
return sessionConfig;
|
return sessionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
|
- (void)setSessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
|
||||||
@synchronized (self) {
|
@synchronized(self) {
|
||||||
if (sessionConfiguration == nil) {
|
if (sessionConfiguration == nil) {
|
||||||
_sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration];
|
_sessionConfig = [MLNNetworkConfiguration defaultSessionConfiguration];
|
||||||
} else {
|
} else {
|
||||||
_sessionConfig = sessionConfiguration;
|
_sessionConfig = sessionConfiguration;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType {
|
- (void)startDownloadEvent:(NSString *)urlString type:(NSString *)resourceType {
|
||||||
if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) {
|
if (urlString && resourceType && ![self eventDictionaryForKey:urlString]) {
|
||||||
NSDate *startDate = [NSDate date];
|
NSDate *startDate = [NSDate date];
|
||||||
[self setEventDictionary:@{ MLNStartTime: startDate, MLNResourceType: resourceType }
|
[self setEventDictionary:@{MLNStartTime : startDate, MLNResourceType : resourceType}
|
||||||
forKey:urlString];
|
forKey:urlString];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)stopDownloadEventForResponse:(NSURLResponse *)response {
|
- (void)stopDownloadEventForResponse:(NSURLResponse *)response {
|
||||||
[self sendEventForURLResponse:response withAction:nil];
|
[self sendEventForURLResponse:response withAction:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)cancelDownloadEventForResponse:(NSURLResponse *)response {
|
- (void)cancelDownloadEventForResponse:(NSURLResponse *)response {
|
||||||
[self sendEventForURLResponse:response withAction:@"cancel"];
|
[self sendEventForURLResponse:response withAction:@"cancel"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)debugLog:(NSString *)message {
|
- (void)debugLog:(NSString *)message {
|
||||||
MLNLogDebug(message);
|
MLNLogDebug(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)errorLog:(NSString *)message {
|
- (void)errorLog:(NSString *)message {
|
||||||
MLNLogError(message);
|
MLNLogError(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Event management
|
// MARK: - Event management
|
||||||
|
|
||||||
- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action
|
- (void)sendEventForURLResponse:(NSURLResponse *)response withAction:(NSString *)action {
|
||||||
{
|
if ([response isKindOfClass:[NSURLResponse class]]) {
|
||||||
if ([response isKindOfClass:[NSURLResponse class]]) {
|
NSString *urlString = response.URL.relativePath;
|
||||||
NSString *urlString = response.URL.relativePath;
|
if (urlString && [self eventDictionaryForKey:urlString]) {
|
||||||
if (urlString && [self eventDictionaryForKey:urlString]) {
|
NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action];
|
||||||
NSDictionary *eventAttributes = [self eventAttributesForURL:response withAction:action];
|
[self removeEventDictionaryForKey:urlString];
|
||||||
[self removeEventDictionaryForKey:urlString];
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes];
|
[self.metricsDelegate networkConfiguration:self didGenerateMetricEvent:eventAttributes];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action
|
- (NSDictionary *)eventAttributesForURL:(NSURLResponse *)response withAction:(NSString *)action {
|
||||||
{
|
NSString *urlString = response.URL.relativePath;
|
||||||
NSString *urlString = response.URL.relativePath;
|
NSDictionary *parameters = [self eventDictionaryForKey:urlString];
|
||||||
NSDictionary *parameters = [self eventDictionaryForKey:urlString];
|
NSDate *startDate = [parameters objectForKey:MLNStartTime];
|
||||||
NSDate *startDate = [parameters objectForKey:MLNStartTime];
|
NSDate *endDate = [NSDate date];
|
||||||
NSDate *endDate = [NSDate date];
|
NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate];
|
||||||
NSTimeInterval elapsedTime = [endDate timeIntervalSinceDate:startDate];
|
NSDateFormatter *iso8601Formatter = [[NSDateFormatter alloc] init];
|
||||||
NSDateFormatter* iso8601Formatter = [[NSDateFormatter alloc] init];
|
iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
|
||||||
iso8601Formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
|
NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]];
|
||||||
NSString *createdDate = [iso8601Formatter stringFromDate:[NSDate date]];
|
|
||||||
|
|
||||||
NSMutableArray *attributes = [NSMutableArray array];
|
NSMutableArray *attributes = [NSMutableArray array];
|
||||||
[attributes addObject:@{ @"name" : @"requestUrl" , @"value" : urlString }];
|
[attributes addObject:@{@"name" : @"requestUrl", @"value" : urlString}];
|
||||||
[attributes addObject:@{ @"name" : MLNResourceType , @"value" : [parameters objectForKey:MLNResourceType] }];
|
[attributes
|
||||||
|
addObject:@{@"name" : MLNResourceType, @"value" : [parameters objectForKey:MLNResourceType]}];
|
||||||
|
|
||||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||||
NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
|
NSInteger responseCode = [(NSHTTPURLResponse *)response statusCode];
|
||||||
[attributes addObject:@{ @"name" : @"responseCode", @"value" : @(responseCode)}];
|
[attributes addObject:@{@"name" : @"responseCode", @"value" : @(responseCode)}];
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi];
|
BOOL isWIFIOn = [[MLNReachability reachabilityWithHostName:response.URL.host] isReachableViaWiFi];
|
||||||
[attributes addObject:@{ @"name" : @"wifiOn", @"value" : @(isWIFIOn)}];
|
[attributes addObject:@{@"name" : @"wifiOn", @"value" : @(isWIFIOn)}];
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
[attributes addObject:@{ @"name" : @"action" , @"value" : action }];
|
[attributes addObject:@{@"name" : @"action", @"value" : action}];
|
||||||
}
|
}
|
||||||
|
|
||||||
double elapsedTimeInMS = elapsedTime * 1000.0;
|
double elapsedTimeInMS = elapsedTime * 1000.0;
|
||||||
|
|
||||||
return @{
|
return @{
|
||||||
@"event" : kMLNDownloadPerformanceEvent,
|
@"event" : kMLNDownloadPerformanceEvent,
|
||||||
@"created" : createdDate,
|
@"created" : createdDate,
|
||||||
@"sessionId" : [NSUUID UUID].UUIDString,
|
@"sessionId" : [NSUUID UUID].UUIDString,
|
||||||
@"counters" : @[ @{ @"name" : @"elapsedMS" , @"value" : @(elapsedTimeInMS) } ],
|
@"counters" : @[ @{@"name" : @"elapsedMS", @"value" : @(elapsedTimeInMS)} ],
|
||||||
@"attributes" : attributes
|
@"attributes" : attributes
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Events dictionary access
|
// MARK: - Events dictionary access
|
||||||
|
|
||||||
- (nullable NSDictionary*)eventDictionaryForKey:(nonnull NSString*)key {
|
- (nullable NSDictionary *)eventDictionaryForKey:(nonnull NSString *)key {
|
||||||
__block NSDictionary *dictionary;
|
__block NSDictionary *dictionary;
|
||||||
|
|
||||||
dispatch_sync(self.eventsQueue, ^{
|
dispatch_sync(self.eventsQueue, ^{
|
||||||
dictionary = [self.events objectForKey:key];
|
dictionary = [self.events objectForKey:key];
|
||||||
});
|
});
|
||||||
|
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setEventDictionary:(nonnull NSDictionary*)dictionary forKey:(nonnull NSString*)key {
|
- (void)setEventDictionary:(nonnull NSDictionary *)dictionary forKey:(nonnull NSString *)key {
|
||||||
dispatch_barrier_async(self.eventsQueue, ^{
|
dispatch_barrier_async(self.eventsQueue, ^{
|
||||||
[self.events setObject:dictionary forKey:key];
|
[self.events setObject:dictionary forKey:key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeEventDictionaryForKey:(nonnull NSString*)key {
|
- (void)removeEventDictionaryForKey:(nonnull NSString *)key {
|
||||||
dispatch_barrier_async(self.eventsQueue, ^{
|
dispatch_barrier_async(self.eventsQueue, ^{
|
||||||
[self.events removeObjectForKey:key];
|
[self.events removeObjectForKey:key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -246,11 +240,11 @@ NSString * const kMLNDownloadPerformanceEvent = @"mobile.performance_trace";
|
|||||||
@implementation MLNNetworkConfiguration (ForTesting)
|
@implementation MLNNetworkConfiguration (ForTesting)
|
||||||
|
|
||||||
+ (void)testing_clearNativeNetworkManagerDelegate {
|
+ (void)testing_clearNativeNetworkManagerDelegate {
|
||||||
[MLNNativeNetworkManager sharedManager].delegate = nil;
|
[MLNNativeNetworkManager sharedManager].delegate = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (id)testing_nativeNetworkManagerDelegate {
|
+ (id)testing_nativeNetworkManagerDelegate {
|
||||||
return [MLNNativeNetworkManager sharedManager].delegate;
|
return [MLNNativeNetworkManager sharedManager].delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import "MLNNetworkResponse.h"
|
#import "MLNNetworkResponse.h"
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@implementation MLNNetworkResponse
|
@implementation MLNNetworkResponse
|
||||||
|
|
||||||
+(MLNNetworkResponse *)responseWithData:(NSData *)data
|
+ (MLNNetworkResponse *)responseWithData:(NSData *)data
|
||||||
urlResponse:(NSURLResponse *)response
|
urlResponse:(NSURLResponse *)response
|
||||||
error:(NSError *)error {
|
error:(NSError *)error {
|
||||||
|
MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init];
|
||||||
MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init];
|
tempResult.data = data;
|
||||||
tempResult.data = data;
|
tempResult.response = response;
|
||||||
tempResult.response = response;
|
tempResult.error = error;
|
||||||
tempResult.error = error;
|
return tempResult;
|
||||||
return tempResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#import "MLNOfflinePack_Private.h"
|
#import "MLNOfflinePack_Private.h"
|
||||||
|
|
||||||
#import "MLNOfflineStorage_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNOfflineRegion_Private.h"
|
#import "MLNOfflineRegion_Private.h"
|
||||||
#import "MLNTilePyramidOfflineRegion.h"
|
#import "MLNOfflineStorage_Private.h"
|
||||||
#import "MLNTilePyramidOfflineRegion_Private.h"
|
|
||||||
#import "MLNShapeOfflineRegion.h"
|
#import "MLNShapeOfflineRegion.h"
|
||||||
#import "MLNShapeOfflineRegion_Private.h"
|
#import "MLNShapeOfflineRegion_Private.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNTilePyramidOfflineRegion.h"
|
||||||
|
#import "MLNTilePyramidOfflineRegion_Private.h"
|
||||||
|
|
||||||
#import "NSValue+MLNAdditions.h"
|
#import "NSValue+MLNAdditions.h"
|
||||||
|
|
||||||
@@ -22,18 +22,19 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
|
|||||||
This macro should be used at the beginning of any public-facing instance method
|
This macro should be used at the beginning of any public-facing instance method
|
||||||
of ``MLNOfflinePack``. For private methods, an assertion is more appropriate.
|
of ``MLNOfflinePack``. For private methods, an assertion is more appropriate.
|
||||||
*/
|
*/
|
||||||
#define MLNAssertOfflinePackIsValid() \
|
#define MLNAssertOfflinePackIsValid() \
|
||||||
do { \
|
do { \
|
||||||
if (_state == MLNOfflinePackStateInvalid) { \
|
if (_state == MLNOfflinePackStateInvalid) { \
|
||||||
[NSException raise:MLNInvalidOfflinePackException \
|
[NSException \
|
||||||
format: \
|
raise:MLNInvalidOfflinePackException \
|
||||||
@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \
|
format:@"-[MLNOfflineStorage removePack:withCompletionHandler:] has been called " \
|
||||||
@"on this instance of MLNOfflinePack, rendering it invalid. It is an " \
|
@"on this instance of MLNOfflinePack, rendering it invalid. It is an " \
|
||||||
@"error to send any message to this pack."]; \
|
@"error to send any message to this pack."]; \
|
||||||
} \
|
} \
|
||||||
} while (NO);
|
} while (NO);
|
||||||
|
|
||||||
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private, MLNTilePyramidOfflineRegion_Private>
|
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private,
|
||||||
|
MLNTilePyramidOfflineRegion_Private>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
|
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
|
||||||
@@ -41,14 +42,14 @@ const MLNExceptionName MLNInvalidOfflinePackException = @"MLNInvalidOfflinePackE
|
|||||||
|
|
||||||
class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver {
|
class MBGLOfflineRegionObserver : public mbgl::OfflineRegionObserver {
|
||||||
public:
|
public:
|
||||||
MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {}
|
MBGLOfflineRegionObserver(MLNOfflinePack *pack_) : pack(pack_) {}
|
||||||
|
|
||||||
void statusChanged(mbgl::OfflineRegionStatus status) override;
|
void statusChanged(mbgl::OfflineRegionStatus status) override;
|
||||||
void responseError(mbgl::Response::Error error) override;
|
void responseError(mbgl::Response::Error error) override;
|
||||||
void mapboxTileCountLimitExceeded(uint64_t limit) override;
|
void mapboxTileCountLimitExceeded(uint64_t limit) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
__weak MLNOfflinePack *pack = nullptr;
|
__weak MLNOfflinePack *pack = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@interface MLNOfflinePack ()
|
@interface MLNOfflinePack ()
|
||||||
@@ -59,268 +60,296 @@ private:
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNOfflinePack {
|
@implementation MLNOfflinePack {
|
||||||
BOOL _isSuspending;
|
BOOL _isSuspending;
|
||||||
std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource;
|
std::shared_ptr<mbgl::DatabaseFileSource> _mbglDatabaseFileSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
MLNLogInfo(@"Calling this initializer is not allowed.");
|
MLNLogInfo(@"Calling this initializer is not allowed.");
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_state = MLNOfflinePackStateInvalid;
|
_state = MLNOfflinePackStateInvalid;
|
||||||
NSLog(@"%s called; did you mean to call +[MLNOfflineStorage addPackForRegion:withContext:completionHandler:] instead?", __PRETTY_FUNCTION__);
|
NSLog(@"%s called; did you mean to call +[MLNOfflineStorage "
|
||||||
}
|
@"addPackForRegion:withContext:completionHandler:] instead?",
|
||||||
return self;
|
__PRETTY_FUNCTION__);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region {
|
- (instancetype)initWithMBGLRegion:(mbgl::OfflineRegion *)region {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_mbglOfflineRegion = region;
|
_mbglOfflineRegion = region;
|
||||||
_state = MLNOfflinePackStateUnknown;
|
_state = MLNOfflinePackStateUnknown;
|
||||||
|
|
||||||
_mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource];
|
_mbglDatabaseFileSource = [[MLNOfflineStorage sharedOfflineStorage] mbglDatabaseFileSource];
|
||||||
_mbglDatabaseFileSource->setOfflineRegionObserver(*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self));
|
_mbglDatabaseFileSource->setOfflineRegionObserver(
|
||||||
}
|
*_mbglOfflineRegion, std::make_unique<MBGLOfflineRegionObserver>(self));
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
MLNAssert(_state == MLNOfflinePackStateInvalid, @"MLNOfflinePack was not invalided prior to deallocation.");
|
MLNAssert(_state == MLNOfflinePackStateInvalid,
|
||||||
|
@"MLNOfflinePack was not invalided prior to deallocation.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id <MLNOfflineRegion>)region {
|
- (id<MLNOfflineRegion>)region {
|
||||||
MLNAssertOfflinePackIsValid();
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
const mbgl::OfflineRegionDefinition ®ionDefinition = _mbglOfflineRegion->getDefinition();
|
const mbgl::OfflineRegionDefinition ®ionDefinition = _mbglOfflineRegion->getDefinition();
|
||||||
MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private.");
|
MLNAssert([MLNTilePyramidOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)],
|
||||||
MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)], @"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private.");
|
@"MLNTilePyramidOfflineRegion should conform to MLNOfflineRegion_Private.");
|
||||||
|
MLNAssert([MLNShapeOfflineRegion conformsToProtocol:@protocol(MLNOfflineRegion_Private)],
|
||||||
|
@"MLNShapeOfflineRegion should conform to MLNOfflineRegion_Private.");
|
||||||
|
|
||||||
return std::visit(mbgl::overloaded{
|
return std::visit(
|
||||||
[&] (const mbgl::OfflineTilePyramidRegionDefinition def){
|
mbgl::overloaded{[&](const mbgl::OfflineTilePyramidRegionDefinition def) {
|
||||||
return (id <MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc] initWithOfflineRegionDefinition:def];
|
return (id<MLNOfflineRegion>)[[MLNTilePyramidOfflineRegion alloc]
|
||||||
},
|
initWithOfflineRegionDefinition:def];
|
||||||
[&] (const mbgl::OfflineGeometryRegionDefinition& def){
|
},
|
||||||
return (id <MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc] initWithOfflineRegionDefinition:def];
|
[&](const mbgl::OfflineGeometryRegionDefinition &def) {
|
||||||
}
|
return (id<MLNOfflineRegion>)[[MLNShapeOfflineRegion alloc]
|
||||||
}, regionDefinition);
|
initWithOfflineRegionDefinition:def];
|
||||||
|
}},
|
||||||
|
regionDefinition);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)context {
|
- (NSData *)context {
|
||||||
MLNAssertOfflinePackIsValid();
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata();
|
const mbgl::OfflineRegionMetadata &metadata = _mbglOfflineRegion->getMetadata();
|
||||||
return [NSData dataWithBytes:&metadata[0] length:metadata.size()];
|
return [NSData dataWithBytes:&metadata[0] length:metadata.size()];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setContext:(NSData *)context completionHandler:(void (^_Nullable)(NSError * _Nullable error))completion {
|
- (void)setContext:(NSData *)context
|
||||||
MLNAssertOfflinePackIsValid();
|
completionHandler:(void (^_Nullable)(NSError *_Nullable error))completion {
|
||||||
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
mbgl::OfflineRegionMetadata metadata(context.length);
|
mbgl::OfflineRegionMetadata metadata(context.length);
|
||||||
[context getBytes:&metadata[0] length:metadata.size()];
|
[context getBytes:&metadata[0] length:metadata.size()];
|
||||||
|
|
||||||
[self willChangeValueForKey:@"context"];
|
[self willChangeValueForKey:@"context"];
|
||||||
__weak MLNOfflinePack *weakSelf = self;
|
__weak MLNOfflinePack *weakSelf = self;
|
||||||
_mbglDatabaseFileSource->updateOfflineMetadata(_mbglOfflineRegion->getID(), metadata, [&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr> mbglOfflineRegionMetadata) {
|
_mbglDatabaseFileSource->updateOfflineMetadata(
|
||||||
|
_mbglOfflineRegion->getID(), metadata,
|
||||||
|
[&, completion, weakSelf](mbgl::expected<mbgl::OfflineRegionMetadata, std::exception_ptr>
|
||||||
|
mbglOfflineRegionMetadata) {
|
||||||
NSError *error;
|
NSError *error;
|
||||||
if (!mbglOfflineRegionMetadata) {
|
if (!mbglOfflineRegionMetadata) {
|
||||||
NSString *errorDescription = @(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str());
|
NSString *errorDescription =
|
||||||
error = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeModifyingOfflineStorageFailed userInfo:errorDescription ? @{
|
@(mbgl::util::toString(mbglOfflineRegionMetadata.error()).c_str());
|
||||||
NSLocalizedDescriptionKey: errorDescription,
|
error = [NSError errorWithDomain:MLNErrorDomain
|
||||||
} : nil];
|
code:MLNErrorCodeModifyingOfflineStorageFailed
|
||||||
|
userInfo:errorDescription ? @{
|
||||||
|
NSLocalizedDescriptionKey : errorDescription,
|
||||||
|
}
|
||||||
|
: nil];
|
||||||
}
|
}
|
||||||
dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) {
|
dispatch_async(dispatch_get_main_queue(), [&, completion, weakSelf, error](void) {
|
||||||
[weakSelf reloadWithCompletionHandler:^(NSError * _Nullable reloadingError) {
|
[weakSelf reloadWithCompletionHandler:^(NSError *_Nullable reloadingError) {
|
||||||
MLNOfflinePack *strongSelf = weakSelf;
|
MLNOfflinePack *strongSelf = weakSelf;
|
||||||
[strongSelf didChangeValueForKey:@"context"];
|
[strongSelf didChangeValueForKey:@"context"];
|
||||||
if (completion) {
|
if (completion) {
|
||||||
completion(error ?: reloadingError);
|
completion(error ?: reloadingError);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reloadWithCompletionHandler:(void (^)(NSError * _Nullable error))completion {
|
- (void)reloadWithCompletionHandler:(void (^)(NSError *_Nullable error))completion {
|
||||||
auto regionID = _mbglOfflineRegion->getID();
|
auto regionID = _mbglOfflineRegion->getID();
|
||||||
MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage];
|
MLNOfflineStorage *sharedOfflineStorage = [MLNOfflineStorage sharedOfflineStorage];
|
||||||
__weak MLNOfflinePack *weakSelf = self;
|
__weak MLNOfflinePack *weakSelf = self;
|
||||||
[sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs, __unused NSError * _Nullable error) {
|
[sharedOfflineStorage getPacksWithCompletionHandler:^(NSArray<MLNOfflinePack *> *packs,
|
||||||
MLNOfflinePack *strongSelf = weakSelf;
|
__unused NSError *_Nullable error) {
|
||||||
for (MLNOfflinePack *pack in packs) {
|
MLNOfflinePack *strongSelf = weakSelf;
|
||||||
if (pack.mbglOfflineRegion->getID() == regionID) {
|
for (MLNOfflinePack *pack in packs) {
|
||||||
strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion;
|
if (pack.mbglOfflineRegion->getID() == regionID) {
|
||||||
}
|
strongSelf.mbglOfflineRegion = pack.mbglOfflineRegion;
|
||||||
[pack invalidate];
|
}
|
||||||
}
|
[pack invalidate];
|
||||||
completion(error);
|
}
|
||||||
}];
|
completion(error);
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)resume {
|
- (void)resume {
|
||||||
MLNLogInfo(@"Resuming pack download.");
|
MLNLogInfo(@"Resuming pack download.");
|
||||||
MLNAssertOfflinePackIsValid();
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
self.state = MLNOfflinePackStateActive;
|
self.state = MLNOfflinePackStateActive;
|
||||||
|
|
||||||
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Active);
|
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion,
|
||||||
|
mbgl::OfflineRegionDownloadState::Active);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)suspend {
|
- (void)suspend {
|
||||||
MLNLogInfo(@"Suspending pack download.");
|
MLNLogInfo(@"Suspending pack download.");
|
||||||
MLNAssertOfflinePackIsValid();
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
if (self.state == MLNOfflinePackStateActive) {
|
if (self.state == MLNOfflinePackStateActive) {
|
||||||
self.state = MLNOfflinePackStateInactive;
|
self.state = MLNOfflinePackStateInactive;
|
||||||
_isSuspending = YES;
|
_isSuspending = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
_mbglDatabaseFileSource->setOfflineRegionDownloadState(*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
|
_mbglDatabaseFileSource->setOfflineRegionDownloadState(
|
||||||
|
*_mbglOfflineRegion, mbgl::OfflineRegionDownloadState::Inactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)invalidate {
|
- (void)invalidate {
|
||||||
MLNLogInfo(@"Invalidating pack.");
|
MLNLogInfo(@"Invalidating pack.");
|
||||||
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot invalidate an already invalid offline pack.");
|
MLNAssert(_state != MLNOfflinePackStateInvalid,
|
||||||
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region");
|
@"Cannot invalidate an already invalid offline pack.");
|
||||||
|
MLNAssert(self.mbglOfflineRegion, @"Should have a valid region");
|
||||||
|
|
||||||
@synchronized (self) {
|
@synchronized(self) {
|
||||||
self.state = MLNOfflinePackStateInvalid;
|
self.state = MLNOfflinePackStateInvalid;
|
||||||
if (self.mbglOfflineRegion) {
|
if (self.mbglOfflineRegion) {
|
||||||
_mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
|
_mbglDatabaseFileSource->setOfflineRegionObserver(*self.mbglOfflineRegion, nullptr);
|
||||||
}
|
|
||||||
self.mbglOfflineRegion = nil;
|
|
||||||
}
|
}
|
||||||
|
self.mbglOfflineRegion = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setState:(MLNOfflinePackState)state {
|
- (void)setState:(MLNOfflinePackState)state {
|
||||||
MLNLogDebug(@"Setting state: %ld", (long)state);
|
MLNLogDebug(@"Setting state: %ld", (long)state);
|
||||||
if (!self.mbglOfflineRegion) {
|
if (!self.mbglOfflineRegion) {
|
||||||
// A progress update has arrived after the call to
|
// A progress update has arrived after the call to
|
||||||
// -[MLNOfflineStorage removePack:withCompletionHandler:] but before the
|
// -[MLNOfflineStorage removePack:withCompletionHandler:] but before the
|
||||||
// removal is complete and the completion handler is called.
|
// removal is complete and the completion handler is called.
|
||||||
MLNAssert(_state == MLNOfflinePackStateInvalid, @"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
|
MLNAssert(_state == MLNOfflinePackStateInvalid,
|
||||||
return;
|
@"A valid MLNOfflinePack has no mbgl::OfflineRegion.");
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change the state of an invalid offline pack.");
|
MLNAssert(_state != MLNOfflinePackStateInvalid,
|
||||||
|
@"Cannot change the state of an invalid offline pack.");
|
||||||
|
|
||||||
if (!_isSuspending || state != MLNOfflinePackStateActive) {
|
if (!_isSuspending || state != MLNOfflinePackStateActive) {
|
||||||
_isSuspending = NO;
|
_isSuspending = NO;
|
||||||
_state = state;
|
_state = state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)requestProgress {
|
- (void)requestProgress {
|
||||||
MLNLogInfo(@"Requesting pack progress.");
|
MLNLogInfo(@"Requesting pack progress.");
|
||||||
MLNAssertOfflinePackIsValid();
|
MLNAssertOfflinePackIsValid();
|
||||||
|
|
||||||
__weak MLNOfflinePack *weakSelf = self;
|
__weak MLNOfflinePack *weakSelf = self;
|
||||||
_mbglDatabaseFileSource->getOfflineRegionStatus(*_mbglOfflineRegion, [&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) {
|
_mbglDatabaseFileSource->getOfflineRegionStatus(
|
||||||
|
*_mbglOfflineRegion,
|
||||||
|
[&, weakSelf](mbgl::expected<mbgl::OfflineRegionStatus, std::exception_ptr> status) {
|
||||||
if (status) {
|
if (status) {
|
||||||
mbgl::OfflineRegionStatus checkedStatus = *status;
|
mbgl::OfflineRegionStatus checkedStatus = *status;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
MLNOfflinePack *strongSelf = weakSelf;
|
MLNOfflinePack *strongSelf = weakSelf;
|
||||||
[strongSelf offlineRegionStatusDidChange:checkedStatus];
|
[strongSelf offlineRegionStatusDidChange:checkedStatus];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status {
|
- (void)offlineRegionStatusDidChange:(mbgl::OfflineRegionStatus)status {
|
||||||
MLNAssert(_state != MLNOfflinePackStateInvalid, @"Cannot change update progress of an invalid offline pack.");
|
MLNAssert(_state != MLNOfflinePackStateInvalid,
|
||||||
|
@"Cannot change update progress of an invalid offline pack.");
|
||||||
|
|
||||||
switch (status.downloadState) {
|
switch (status.downloadState) {
|
||||||
case mbgl::OfflineRegionDownloadState::Inactive:
|
case mbgl::OfflineRegionDownloadState::Inactive:
|
||||||
self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive;
|
self.state = status.complete() ? MLNOfflinePackStateComplete : MLNOfflinePackStateInactive;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case mbgl::OfflineRegionDownloadState::Active:
|
case mbgl::OfflineRegionDownloadState::Active:
|
||||||
self.state = MLNOfflinePackStateActive;
|
self.state = MLNOfflinePackStateActive;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isSuspending) {
|
if (_isSuspending) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNOfflinePackProgress progress;
|
MLNOfflinePackProgress progress;
|
||||||
progress.countOfResourcesCompleted = status.completedResourceCount;
|
progress.countOfResourcesCompleted = status.completedResourceCount;
|
||||||
progress.countOfBytesCompleted = status.completedResourceSize;
|
progress.countOfBytesCompleted = status.completedResourceSize;
|
||||||
progress.countOfTilesCompleted = status.completedTileCount;
|
progress.countOfTilesCompleted = status.completedTileCount;
|
||||||
progress.countOfTileBytesCompleted = status.completedTileSize;
|
progress.countOfTileBytesCompleted = status.completedTileSize;
|
||||||
progress.countOfResourcesExpected = status.requiredResourceCount;
|
progress.countOfResourcesExpected = status.requiredResourceCount;
|
||||||
progress.maximumResourcesExpected = status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
|
progress.maximumResourcesExpected =
|
||||||
self.progress = progress;
|
status.requiredResourceCountIsPrecise ? status.requiredResourceCount : UINT64_MAX;
|
||||||
|
self.progress = progress;
|
||||||
|
|
||||||
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyState: @(self.state),
|
NSDictionary *userInfo = @{
|
||||||
MLNOfflinePackUserInfoKeyProgress: [NSValue valueWithMLNOfflinePackProgress:progress]};
|
MLNOfflinePackUserInfoKeyState : @(self.state),
|
||||||
|
MLNOfflinePackUserInfoKeyProgress : [NSValue valueWithMLNOfflinePackProgress:progress]
|
||||||
|
};
|
||||||
|
|
||||||
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
||||||
[noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification
|
[noteCenter postNotificationName:MLNOfflinePackProgressChangedNotification
|
||||||
object:self
|
object:self
|
||||||
userInfo:userInfo];
|
userInfo:userInfo];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveError:(NSError *)error {
|
- (void)didReceiveError:(NSError *)error {
|
||||||
MLNLogError(@"Error: %@", error.localizedDescription);
|
MLNLogError(@"Error: %@", error.localizedDescription);
|
||||||
MLNLogInfo(@"Notifying about pack error.");
|
MLNLogInfo(@"Notifying about pack error.");
|
||||||
|
|
||||||
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyError: error };
|
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyError : error};
|
||||||
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
||||||
[noteCenter postNotificationName:MLNOfflinePackErrorNotification
|
[noteCenter postNotificationName:MLNOfflinePackErrorNotification object:self userInfo:userInfo];
|
||||||
object:self
|
|
||||||
userInfo:userInfo];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit {
|
- (void)didReceiveMaximumAllowedMapboxTiles:(uint64_t)limit {
|
||||||
MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit);
|
MLNLogInfo(@"Notifying reached maximum allowed Mapbox tiles: %lu", (unsigned long)limit);
|
||||||
NSDictionary *userInfo = @{ MLNOfflinePackUserInfoKeyMaximumCount: @(limit) };
|
NSDictionary *userInfo = @{MLNOfflinePackUserInfoKeyMaximumCount : @(limit)};
|
||||||
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
|
||||||
[noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification
|
[noteCenter postNotificationName:MLNOfflinePackMaximumMapboxTilesReachedNotification
|
||||||
object:self
|
object:self
|
||||||
userInfo:userInfo];
|
userInfo:userInfo];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSError *MLNErrorFromResponseError(mbgl::Response::Error error) {
|
NSError *MLNErrorFromResponseError(mbgl::Response::Error error) {
|
||||||
NSInteger errorCode = MLNErrorCodeUnknown;
|
NSInteger errorCode = MLNErrorCodeUnknown;
|
||||||
switch (error.reason) {
|
switch (error.reason) {
|
||||||
case mbgl::Response::Error::Reason::NotFound:
|
case mbgl::Response::Error::Reason::NotFound:
|
||||||
errorCode = MLNErrorCodeNotFound;
|
errorCode = MLNErrorCodeNotFound;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case mbgl::Response::Error::Reason::Server:
|
case mbgl::Response::Error::Reason::Server:
|
||||||
errorCode = MLNErrorCodeBadServerResponse;
|
errorCode = MLNErrorCodeBadServerResponse;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case mbgl::Response::Error::Reason::Connection:
|
case mbgl::Response::Error::Reason::Connection:
|
||||||
errorCode = MLNErrorCodeConnectionFailed;
|
errorCode = MLNErrorCodeConnectionFailed;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return [NSError errorWithDomain:MLNErrorDomain code:errorCode userInfo:@{
|
return [NSError errorWithDomain:MLNErrorDomain
|
||||||
NSLocalizedFailureReasonErrorKey: @(error.message.c_str())
|
code:errorCode
|
||||||
}];
|
userInfo:@{
|
||||||
|
NSLocalizedFailureReasonErrorKey : @(error.message.c_str())
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) {
|
void MBGLOfflineRegionObserver::statusChanged(mbgl::OfflineRegionStatus status) {
|
||||||
__weak MLNOfflinePack *weakPack = pack;
|
__weak MLNOfflinePack *weakPack = pack;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[weakPack offlineRegionStatusDidChange:status];
|
[weakPack offlineRegionStatusDidChange:status];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) {
|
void MBGLOfflineRegionObserver::responseError(mbgl::Response::Error error) {
|
||||||
__weak MLNOfflinePack *weakPack = pack;
|
__weak MLNOfflinePack *weakPack = pack;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[weakPack didReceiveError:MLNErrorFromResponseError(error)];
|
[weakPack didReceiveError:MLNErrorFromResponseError(error)];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) {
|
void MBGLOfflineRegionObserver::mapboxTileCountLimitExceeded(uint64_t limit) {
|
||||||
__weak MLNOfflinePack *weakPack = pack;
|
__weak MLNOfflinePack *weakPack = pack;
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[weakPack didReceiveMaximumAllowedMapboxTiles:limit];
|
[weakPack didReceiveMaximumAllowedMapboxTiles:limit];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,42 +2,41 @@
|
|||||||
|
|
||||||
@implementation MLNPluginLayerProperty
|
@implementation MLNPluginLayerProperty
|
||||||
|
|
||||||
+(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
|
+ (MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName
|
||||||
propertyType:(MLNPluginLayerPropertyType)propertyType
|
propertyType:(MLNPluginLayerPropertyType)propertyType
|
||||||
defaultValue:(id)defaultValue {
|
defaultValue:(id)defaultValue {
|
||||||
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
|
MLNPluginLayerProperty *tempResult = [[MLNPluginLayerProperty alloc] init];
|
||||||
tempResult.propertyName = propertyName;
|
tempResult.propertyName = propertyName;
|
||||||
tempResult.propertyType = propertyType;
|
tempResult.propertyType = propertyType;
|
||||||
|
|
||||||
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
|
if (propertyType == MLNPluginLayerPropertyTypeSingleFloat) {
|
||||||
if ([defaultValue isKindOfClass:[NSNumber class]]) {
|
if ([defaultValue isKindOfClass:[NSNumber class]]) {
|
||||||
tempResult.singleFloatDefaultValue = [defaultValue floatValue];
|
tempResult.singleFloatDefaultValue = [defaultValue floatValue];
|
||||||
}
|
|
||||||
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
|
|
||||||
#if TARGET_OS_IPHONE
|
|
||||||
if ([defaultValue isKindOfClass:[UIColor class]]) {
|
|
||||||
#else
|
|
||||||
if ([defaultValue isKindOfClass:[NSColor class]]) {
|
|
||||||
#endif
|
|
||||||
tempResult.colorDefaultValue = defaultValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (propertyType == MLNPluginLayerPropertyTypeColor) {
|
||||||
|
#if TARGET_OS_IPHONE
|
||||||
|
if ([defaultValue isKindOfClass:[UIColor class]]) {
|
||||||
|
#else
|
||||||
|
if ([defaultValue isKindOfClass:[NSColor class]]) {
|
||||||
|
#endif
|
||||||
|
tempResult.colorDefaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tempResult;
|
return tempResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
// Base class implemenation
|
||||||
|
if (self = [super init]) {
|
||||||
|
// Default setup
|
||||||
|
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
|
||||||
|
self.propertyName = @"unknown";
|
||||||
|
|
||||||
-(id)init {
|
// Default values for the various types
|
||||||
// Base class implemenation
|
self.singleFloatDefaultValue = 1.0;
|
||||||
if (self = [super init]) {
|
}
|
||||||
// Default setup
|
return self;
|
||||||
self.propertyType = MLNPluginLayerPropertyTypeUnknown;
|
|
||||||
self.propertyName = @"unknown";
|
|
||||||
|
|
||||||
// Default values for the various types
|
|
||||||
self.singleFloatDefaultValue = 1.0;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -45,36 +44,31 @@
|
|||||||
@implementation MLNPluginLayer
|
@implementation MLNPluginLayer
|
||||||
|
|
||||||
// This is the layer type in the style that is used
|
// This is the layer type in the style that is used
|
||||||
+(MLNPluginLayerCapabilities *)layerCapabilities {
|
+ (MLNPluginLayerCapabilities *)layerCapabilities {
|
||||||
|
// Base class returns the class name just to return something
|
||||||
// Base class returns the class name just to return something
|
// TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
|
||||||
// TODO: Add an assert/etc or something to notify the developer that this needs to be overridden
|
return nil;
|
||||||
return nil;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onRenderLayer:(MLNMapView *)mapView
|
- (void)onRenderLayer:(MLNMapView *)mapView
|
||||||
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
|
renderEncoder:(id<MTLRenderCommandEncoder>)renderEncoder {
|
||||||
// Base class does nothing
|
// Base class does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
|
- (void)onUpdateLayer:(MLNPluginLayerDrawingContext)drawingContext {
|
||||||
// Base class does nothing
|
// Base class does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
- (void)onUpdateLayerProperties:(NSDictionary *)layerProperties {
|
||||||
// Base class does nothing
|
// Base class does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Added to a map view
|
/// Added to a map view
|
||||||
- (void)didMoveToMapView:(MLNMapView *)mapView {
|
- (void)didMoveToMapView:(MLNMapView *)mapView {
|
||||||
// Base class does nothing
|
// Base class does nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@implementation MLNPluginLayerCapabilities
|
@implementation MLNPluginLayerCapabilities
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
#import "MLNPluginStyleLayer.h"
|
#import "MLNPluginStyleLayer.h"
|
||||||
#import "MLNPluginStyleLayer_Private.h"
|
|
||||||
#import <mbgl/plugin/plugin_layer.hpp>
|
#import <mbgl/plugin/plugin_layer.hpp>
|
||||||
#import <mbgl/plugin/plugin_layer_impl.hpp>
|
#import <mbgl/plugin/plugin_layer_impl.hpp>
|
||||||
#import "MLNPluginLayer.h"
|
#import "MLNPluginLayer.h"
|
||||||
|
#import "MLNPluginStyleLayer_Private.h"
|
||||||
|
|
||||||
@implementation MLNPluginStyleLayer
|
@implementation MLNPluginStyleLayer
|
||||||
|
|
||||||
-(void)getStats {
|
- (void)getStats {
|
||||||
|
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
|
||||||
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
|
auto pl = l->impl();
|
||||||
auto pl = l->impl();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-(MLNPluginLayer *)pluginLayer {
|
- (MLNPluginLayer *)pluginLayer {
|
||||||
|
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
|
||||||
mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer;
|
if (l->_platformReference) {
|
||||||
if (l->_platformReference) {
|
MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference;
|
||||||
MLNPluginLayer *pl = (__bridge MLNPluginLayer *)l->_platformReference;
|
return pl;
|
||||||
return pl;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
|
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
MLNStyleLayer *mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) {
|
||||||
MLNStyleLayer* mbgl::PluginLayerPeerFactory::createPeer(style::Layer *rawLayer) {
|
return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer];
|
||||||
return [[MLNPluginStyleLayer alloc] initWithRawLayer:rawLayer];
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,71 +1,66 @@
|
|||||||
#import "MLNPointAnnotation.h"
|
#import "MLNPointAnnotation.h"
|
||||||
|
|
||||||
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNShape_Private.h"
|
#import "MLNShape_Private.h"
|
||||||
#import "NSCoder+MLNAdditions.h"
|
#import "NSCoder+MLNAdditions.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
|
||||||
|
|
||||||
#import <mbgl/util/geometry.hpp>
|
#import <mbgl/util/geometry.hpp>
|
||||||
|
|
||||||
|
|
||||||
@implementation MLNPointAnnotation
|
@implementation MLNPointAnnotation
|
||||||
|
|
||||||
@synthesize coordinate;
|
@synthesize coordinate;
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding
|
+ (BOOL)supportsSecureCoding {
|
||||||
{
|
return YES;
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
{
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
if (self = [super initWithCoder:coder]) {
|
||||||
if (self = [super initWithCoder:coder]) {
|
self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"];
|
||||||
self.coordinate = [coder decodeMLNCoordinateForKey:@"coordinate"];
|
}
|
||||||
}
|
return self;
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
{
|
[super encodeWithCoder:coder];
|
||||||
[super encodeWithCoder:coder];
|
[coder encodeMLNCoordinate:coordinate forKey:@"coordinate"];
|
||||||
[coder encodeMLNCoordinate:coordinate forKey:@"coordinate"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other
|
- (BOOL)isEqual:(id)other {
|
||||||
{
|
if (other == self) return YES;
|
||||||
if (other == self) return YES;
|
if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO;
|
||||||
if (![other isKindOfClass:[MLNPointAnnotation class]]) return NO;
|
|
||||||
|
|
||||||
MLNPointAnnotation *otherAnnotation = other;
|
MLNPointAnnotation *otherAnnotation = other;
|
||||||
return ([super isEqual:other]
|
return ([super isEqual:other] &&
|
||||||
&& self.coordinate.latitude == otherAnnotation.coordinate.latitude
|
self.coordinate.latitude == otherAnnotation.coordinate.latitude &&
|
||||||
&& self.coordinate.longitude == otherAnnotation.coordinate.longitude);
|
self.coordinate.longitude == otherAnnotation.coordinate.longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash
|
- (NSUInteger)hash {
|
||||||
{
|
return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash;
|
||||||
return [super hash] + @(self.coordinate.latitude).hash + @(self.coordinate.longitude).hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>",
|
stringWithFormat:@"<%@: %p; title = %@; subtitle = %@; coordinate = %f, %f>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
||||||
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle,
|
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
|
||||||
coordinate.latitude, coordinate.longitude];
|
: self.subtitle,
|
||||||
|
coordinate.latitude, coordinate.longitude];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
{
|
return @{
|
||||||
return @{@"type": @"Point",
|
@"type" : @"Point",
|
||||||
@"coordinates": @[@(self.coordinate.longitude), @(self.coordinate.latitude)]};
|
@"coordinates" : @[ @(self.coordinate.longitude), @(self.coordinate.latitude) ]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
{
|
mbgl::Point<double> point = {self.coordinate.longitude, self.coordinate.latitude};
|
||||||
mbgl::Point<double> point = { self.coordinate.longitude, self.coordinate.latitude };
|
return point;
|
||||||
return point;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,139 +1,131 @@
|
|||||||
#import "MLNPointCollection_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
#import "NSArray+MLNAdditions.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNPointCollection_Private.h"
|
||||||
|
#import "NSArray+MLNAdditions.h"
|
||||||
|
|
||||||
#import <mbgl/util/geojson.hpp>
|
#import <mbgl/util/geojson.hpp>
|
||||||
#import <mbgl/util/geometry.hpp>
|
#import <mbgl/util/geometry.hpp>
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@implementation MLNPointCollection
|
@implementation MLNPointCollection {
|
||||||
{
|
std::optional<mbgl::LatLngBounds> _bounds;
|
||||||
std::optional<mbgl::LatLngBounds> _bounds;
|
std::vector<CLLocationCoordinate2D> _coordinates;
|
||||||
std::vector<CLLocationCoordinate2D> _coordinates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count
|
+ (instancetype)pointCollectionWithCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
{
|
count:(NSUInteger)count {
|
||||||
return [[self alloc] initWithCoordinates:coords count:count];
|
return [[self alloc] initWithCoordinates:coords count:count];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count
|
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
||||||
{
|
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count);
|
||||||
MLNLogDebug(@"Initializing with %lu coordinates.", (unsigned long)count);
|
self = [super init];
|
||||||
self = [super init];
|
if (self) {
|
||||||
if (self)
|
_coordinates = {coords, coords + count};
|
||||||
{
|
}
|
||||||
_coordinates = { coords, coords + count };
|
return self;
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable instancetype)initWithCoder:(NSCoder *)decoder {
|
- (nullable instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
MLNLogInfo(@"Initializing with coder.O");
|
MLNLogInfo(@"Initializing with coder.O");
|
||||||
if (self = [super initWithCoder:decoder]) {
|
if (self = [super initWithCoder:decoder]) {
|
||||||
NSSet<Class> *coordinatesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class]]];
|
NSSet<Class> *coordinatesClasses =
|
||||||
NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"];
|
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class] ]];
|
||||||
_coordinates = [coordinates mgl_coordinates];
|
NSArray *coordinates = [decoder decodeObjectOfClasses:coordinatesClasses forKey:@"coordinates"];
|
||||||
}
|
_coordinates = [coordinates mgl_coordinates];
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
[super encodeWithCoder:coder];
|
[super encodeWithCoder:coder];
|
||||||
[coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"];
|
[coder encodeObject:[NSArray mgl_coordinatesFromCoordinates:_coordinates] forKey:@"coordinates"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (self == other) return YES;
|
if (self == other) return YES;
|
||||||
if (![other isKindOfClass:[MLNPointCollection class]]) return NO;
|
if (![other isKindOfClass:[MLNPointCollection class]]) return NO;
|
||||||
|
|
||||||
MLNPointCollection *otherCollection = (MLNPointCollection *)other;
|
MLNPointCollection *otherCollection = (MLNPointCollection *)other;
|
||||||
return ([super isEqual:other]
|
return ([super isEqual:other] &&
|
||||||
&& ((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) || [[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]]));
|
((![self geoJSONDictionary] && ![otherCollection geoJSONDictionary]) ||
|
||||||
|
[[self geoJSONDictionary] isEqualToDictionary:[otherCollection geoJSONDictionary]]));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (MLNCoordinateBounds)overlayBounds {
|
- (MLNCoordinateBounds)overlayBounds {
|
||||||
if (!_bounds) {
|
if (!_bounds) {
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
for (auto coordinate : _coordinates) {
|
for (auto coordinate : _coordinates) {
|
||||||
if (!MLNLocationCoordinate2DIsValid(coordinate)) {
|
if (!MLNLocationCoordinate2DIsValid(coordinate)) {
|
||||||
bounds = mbgl::LatLngBounds::empty();
|
bounds = mbgl::LatLngBounds::empty();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
|
bounds.extend(MLNLatLngFromLocationCoordinate2D(coordinate));
|
||||||
}
|
|
||||||
_bounds = bounds;
|
|
||||||
}
|
}
|
||||||
return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
|
_bounds = bounds;
|
||||||
|
}
|
||||||
|
return MLNCoordinateBoundsFromLatLngBounds(*_bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)pointCount
|
- (NSUInteger)pointCount {
|
||||||
{
|
return _coordinates.size();
|
||||||
return _coordinates.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D *)coordinates
|
- (CLLocationCoordinate2D *)coordinates {
|
||||||
{
|
return _coordinates.data();
|
||||||
return _coordinates.data();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
{
|
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
|
||||||
MLNAssert([self pointCount] > 0, @"A multipoint must have coordinates");
|
return _coordinates.at(0);
|
||||||
return _coordinates.at(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range
|
- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range {
|
||||||
{
|
if (range.location + range.length > [self pointCount]) {
|
||||||
if (range.location + range.length > [self pointCount])
|
[NSException
|
||||||
{
|
raise:NSRangeException
|
||||||
[NSException raise:NSRangeException
|
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
|
||||||
format:@"Invalid coordinate range %@ extends beyond current coordinate count of %ld",
|
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
||||||
NSStringFromRange(range), (unsigned long)[self pointCount]];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range), coords);
|
std::copy(_coordinates.begin() + range.location, _coordinates.begin() + NSMaxRange(range),
|
||||||
|
coords);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds
|
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
||||||
{
|
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
|
||||||
return MLNCoordinateBoundsIntersectsCoordinateBounds(self.overlayBounds, overlayBounds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
{
|
mbgl::MultiPoint<double> multiPoint;
|
||||||
mbgl::MultiPoint<double> multiPoint;
|
multiPoint.reserve(self.pointCount);
|
||||||
multiPoint.reserve(self.pointCount);
|
for (NSUInteger i = 0; i < self.pointCount; i++) {
|
||||||
for (NSUInteger i = 0; i < self.pointCount; i++)
|
multiPoint.push_back(
|
||||||
{
|
mbgl::Point<double>(self.coordinates[i].longitude, self.coordinates[i].latitude));
|
||||||
multiPoint.push_back(mbgl::Point<double>(self.coordinates[i].longitude, self.coordinates[i].latitude));
|
}
|
||||||
}
|
return multiPoint;
|
||||||
return multiPoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
{
|
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
|
||||||
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
|
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
||||||
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
||||||
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
[coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
|
||||||
[coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return @{@"type": @"MultiPoint",
|
return @{@"type" : @"MultiPoint", @"coordinates" : coordinates};
|
||||||
@"coordinates": coordinates};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
|
||||||
return [NSString stringWithFormat:@"<%@: %p; count = %lu; bounds = %@>",
|
NSStringFromClass([self class]), (void *)self,
|
||||||
NSStringFromClass([self class]), (void *)self, (unsigned long)[self pointCount],
|
(unsigned long)[self pointCount],
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,135 +1,140 @@
|
|||||||
#import "MLNPolygon_Private.h"
|
#import "MLNPolygon_Private.h"
|
||||||
|
|
||||||
#import "MLNMultiPoint_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNMultiPoint_Private.h"
|
||||||
|
|
||||||
#import "MLNFeature.h"
|
#import "MLNFeature.h"
|
||||||
|
|
||||||
#import <mbgl/util/geojson.hpp>
|
|
||||||
#import <mapbox/polylabel.hpp>
|
#import <mapbox/polylabel.hpp>
|
||||||
|
#import <mbgl/util/geojson.hpp>
|
||||||
|
|
||||||
@implementation MLNPolygon
|
@implementation MLNPolygon
|
||||||
|
|
||||||
@dynamic overlayBounds;
|
@dynamic overlayBounds;
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count {
|
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
return [self polygonWithCoordinates:coords count:count interiorPolygons:nil];
|
count:(NSUInteger)count {
|
||||||
|
return [self polygonWithCoordinates:coords count:count interiorPolygons:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
|
+ (instancetype)polygonWithCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons];
|
count:(NSUInteger)count
|
||||||
|
interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
|
||||||
|
return [[self alloc] initWithCoordinates:coords count:count interiorPolygons:interiorPolygons];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords count:(NSUInteger)count interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
|
- (instancetype)initWithCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count, (unsigned long)interiorPolygons);
|
count:(NSUInteger)count
|
||||||
if (self = [super initWithCoordinates:coords count:count]) {
|
interiorPolygons:(NSArray<MLNPolygon *> *)interiorPolygons {
|
||||||
if (interiorPolygons.count) {
|
MLNLogDebug(@"Initializing with %lu coordinates and %lu interiorPolygons.", (unsigned long)count,
|
||||||
_interiorPolygons = interiorPolygons;
|
(unsigned long)interiorPolygons);
|
||||||
}
|
if (self = [super initWithCoordinates:coords count:count]) {
|
||||||
|
if (interiorPolygons.count) {
|
||||||
|
_interiorPolygons = interiorPolygons;
|
||||||
}
|
}
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
MLNLogInfo(@"Initializng with coder.");
|
MLNLogInfo(@"Initializng with coder.");
|
||||||
self = [super initWithCoder:decoder];
|
self = [super initWithCoder:decoder];
|
||||||
if (self) {
|
if (self) {
|
||||||
NSSet<Class> *polygonClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]];
|
NSSet<Class> *polygonClasses =
|
||||||
_interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"];
|
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
|
||||||
}
|
_interiorPolygons = [decoder decodeObjectOfClasses:polygonClasses forKey:@"interiorPolygons"];
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
[super encodeWithCoder:coder];
|
[super encodeWithCoder:coder];
|
||||||
[coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"];
|
[coder encodeObject:self.interiorPolygons forKey:@"interiorPolygons"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (self == other) return YES;
|
if (self == other) return YES;
|
||||||
if (![other isKindOfClass:[MLNPolygon class]]) return NO;
|
if (![other isKindOfClass:[MLNPolygon class]]) return NO;
|
||||||
|
|
||||||
MLNPolygon *otherPolygon = (MLNPolygon *)other;
|
MLNPolygon *otherPolygon = (MLNPolygon *)other;
|
||||||
return ([super isEqual:otherPolygon] &&
|
return ([super isEqual:otherPolygon] &&
|
||||||
[[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]);
|
[[self geoJSONDictionary] isEqualToDictionary:[otherPolygon geoJSONDictionary]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
return [super hash] + [[self geoJSONDictionary] hash];
|
return [super hash] + [[self geoJSONDictionary] hash];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate {
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
// pole of inaccessibility
|
// pole of inaccessibility
|
||||||
auto poi = mapbox::polylabel([self polygon]);
|
auto poi = mapbox::polylabel([self polygon]);
|
||||||
|
|
||||||
return MLNLocationCoordinate2DFromPoint(poi);
|
return MLNLocationCoordinate2DFromPoint(poi);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::LinearRing<double>)ring {
|
- (mbgl::LinearRing<double>)ring {
|
||||||
NSUInteger count = self.pointCount;
|
NSUInteger count = self.pointCount;
|
||||||
CLLocationCoordinate2D *coordinates = self.coordinates;
|
CLLocationCoordinate2D *coordinates = self.coordinates;
|
||||||
|
|
||||||
mbgl::LinearRing<double> result;
|
mbgl::LinearRing<double> result;
|
||||||
result.reserve(self.pointCount);
|
result.reserve(self.pointCount);
|
||||||
for (NSUInteger i = 0; i < count; i++) {
|
for (NSUInteger i = 0; i < count; i++) {
|
||||||
result.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
|
result.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Polygon<double>)polygon {
|
- (mbgl::Polygon<double>)polygon {
|
||||||
mbgl::Polygon<double> geometry;
|
mbgl::Polygon<double> geometry;
|
||||||
geometry.push_back(self.ring);
|
geometry.push_back(self.ring);
|
||||||
for (MLNPolygon *polygon in self.interiorPolygons) {
|
for (MLNPolygon *polygon in self.interiorPolygons) {
|
||||||
geometry.push_back(polygon.ring);
|
geometry.push_back(polygon.ring);
|
||||||
}
|
}
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
return [self polygon];
|
return [self polygon];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate {
|
- (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
|
||||||
|
mbgl::FillAnnotation annotation{[self polygon]};
|
||||||
|
annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
|
||||||
|
annotation.outlineColor = {[delegate strokeColorForShapeAnnotation:self]};
|
||||||
|
annotation.color = {[delegate fillColorForPolygonAnnotation:self]};
|
||||||
|
|
||||||
mbgl::FillAnnotation annotation { [self polygon] };
|
return annotation;
|
||||||
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
|
|
||||||
annotation.outlineColor = { [delegate strokeColorForShapeAnnotation:self] };
|
|
||||||
annotation.color = { [delegate fillColorForPolygonAnnotation:self] };
|
|
||||||
|
|
||||||
return annotation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return @{@"type": @"Polygon",
|
return @{@"type" : @"Polygon", @"coordinates" : self.mgl_coordinates};
|
||||||
@"coordinates": self.mgl_coordinates};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id> *)mgl_coordinates {
|
- (NSArray<id> *)mgl_coordinates {
|
||||||
NSMutableArray *coordinates = [NSMutableArray array];
|
NSMutableArray *coordinates = [NSMutableArray array];
|
||||||
|
|
||||||
NSMutableArray *exteriorRing = [NSMutableArray array];
|
NSMutableArray *exteriorRing = [NSMutableArray array];
|
||||||
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
||||||
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
||||||
[exteriorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
|
[exteriorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
|
||||||
|
}
|
||||||
|
[coordinates addObject:exteriorRing];
|
||||||
|
|
||||||
|
for (MLNPolygon *interiorPolygon in self.interiorPolygons) {
|
||||||
|
NSMutableArray *interiorRing = [NSMutableArray array];
|
||||||
|
for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) {
|
||||||
|
CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index];
|
||||||
|
[interiorRing addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
|
||||||
}
|
}
|
||||||
[coordinates addObject:exteriorRing];
|
[coordinates addObject:interiorRing];
|
||||||
|
}
|
||||||
|
|
||||||
for (MLNPolygon *interiorPolygon in self.interiorPolygons) {
|
return [coordinates copy];
|
||||||
NSMutableArray *interiorRing = [NSMutableArray array];
|
|
||||||
for (NSUInteger index = 0; index < interiorPolygon.pointCount; index++) {
|
|
||||||
CLLocationCoordinate2D coordinate = interiorPolygon.coordinates[index];
|
|
||||||
[interiorRing addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
|
|
||||||
}
|
|
||||||
[coordinates addObject:interiorRing];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [coordinates copy];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -141,117 +146,117 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNMultiPolygon {
|
@implementation MLNMultiPolygon {
|
||||||
MLNCoordinateBounds _overlayBounds;
|
MLNCoordinateBounds _overlayBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize overlayBounds = _overlayBounds;
|
@synthesize overlayBounds = _overlayBounds;
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)multiPolygonWithPolygons:(NSArray<MLNPolygon *> *)polygons {
|
+ (instancetype)multiPolygonWithPolygons:(NSArray<MLNPolygon *> *)polygons {
|
||||||
return [[self alloc] initWithPolygons:polygons];
|
return [[self alloc] initWithPolygons:polygons];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPolygons:(NSArray<MLNPolygon *> *)polygons {
|
- (instancetype)initWithPolygons:(NSArray<MLNPolygon *> *)polygons {
|
||||||
MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count);
|
MLNLogDebug(@"Initializing with %lu polygons.", (unsigned long)polygons.count);
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_polygons = polygons;
|
_polygons = polygons;
|
||||||
|
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
|
|
||||||
for (MLNPolygon *polygon in _polygons) {
|
for (MLNPolygon *polygon in _polygons) {
|
||||||
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
|
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
|
||||||
}
|
|
||||||
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
|
||||||
}
|
}
|
||||||
return self;
|
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
if (self = [super initWithCoder:decoder]) {
|
if (self = [super initWithCoder:decoder]) {
|
||||||
NSSet<Class> *polygonsClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolygon class]]];
|
NSSet<Class> *polygonsClasses =
|
||||||
_polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"];
|
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolygon class] ]];
|
||||||
|
_polygons = [decoder decodeObjectOfClasses:polygonsClasses forKey:@"polygons"];
|
||||||
|
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
|
|
||||||
for (MLNPolygon *polygon in _polygons) {
|
for (MLNPolygon *polygon in _polygons) {
|
||||||
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
|
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polygon.overlayBounds));
|
||||||
}
|
|
||||||
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
|
||||||
}
|
}
|
||||||
return self;
|
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
[super encodeWithCoder:coder];
|
[super encodeWithCoder:coder];
|
||||||
[coder encodeObject:_polygons forKey:@"polygons"];
|
[coder encodeObject:_polygons forKey:@"polygons"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (self == other) return YES;
|
if (self == other) return YES;
|
||||||
if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO;
|
if (![other isKindOfClass:[MLNMultiPolygon class]]) return NO;
|
||||||
|
|
||||||
MLNMultiPolygon *otherMultiPolygon = other;
|
MLNMultiPolygon *otherMultiPolygon = other;
|
||||||
return [super isEqual:other]
|
return [super isEqual:other] && [self.polygons isEqualToArray:otherMultiPolygon.polygons];
|
||||||
&& [self.polygons isEqualToArray:otherMultiPolygon.polygons];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
NSUInteger hash = [super hash];
|
NSUInteger hash = [super hash];
|
||||||
for (MLNPolygon *polygon in self.polygons) {
|
for (MLNPolygon *polygon in self.polygons) {
|
||||||
hash += [polygon hash];
|
hash += [polygon hash];
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate {
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
MLNPolygon *firstPolygon = self.polygons.firstObject;
|
MLNPolygon *firstPolygon = self.polygons.firstObject;
|
||||||
|
|
||||||
return firstPolygon.coordinate;
|
return firstPolygon.coordinate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
||||||
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
|
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::MultiPolygon<double>)multiPolygon {
|
- (mbgl::MultiPolygon<double>)multiPolygon {
|
||||||
mbgl::MultiPolygon<double> multiPolygon;
|
mbgl::MultiPolygon<double> multiPolygon;
|
||||||
multiPolygon.reserve(self.polygons.count);
|
multiPolygon.reserve(self.polygons.count);
|
||||||
for (MLNPolygon *polygon in self.polygons) {
|
for (MLNPolygon *polygon in self.polygons) {
|
||||||
mbgl::Polygon<double> geometry;
|
mbgl::Polygon<double> geometry;
|
||||||
geometry.push_back(polygon.ring);
|
geometry.push_back(polygon.ring);
|
||||||
for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) {
|
for (MLNPolygon *interiorPolygon in polygon.interiorPolygons) {
|
||||||
geometry.push_back(interiorPolygon.ring);
|
geometry.push_back(interiorPolygon.ring);
|
||||||
}
|
|
||||||
multiPolygon.push_back(geometry);
|
|
||||||
}
|
}
|
||||||
return multiPolygon;
|
multiPolygon.push_back(geometry);
|
||||||
|
}
|
||||||
|
return multiPolygon;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
return [self multiPolygon];
|
return [self multiPolygon];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count];
|
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.polygons.count];
|
||||||
for (MLNPolygonFeature *feature in self.polygons) {
|
for (MLNPolygonFeature *feature in self.polygons) {
|
||||||
[coordinates addObject: feature.mgl_coordinates];
|
[coordinates addObject:feature.mgl_coordinates];
|
||||||
}
|
}
|
||||||
return @{@"type": @"MultiPolygon",
|
return @{@"type" : @"MultiPolygon", @"coordinates" : coordinates};
|
||||||
@"coordinates": coordinates};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
|
stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
||||||
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle,
|
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
|
||||||
(unsigned long)self.polygons.count,
|
: self.subtitle,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
(unsigned long)self.polygons.count,
|
||||||
|
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,128 +1,125 @@
|
|||||||
#import "MLNPolyline_Private.h"
|
#import "MLNPolyline_Private.h"
|
||||||
|
|
||||||
#import "MLNMultiPoint_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
|
#import "MLNMultiPoint_Private.h"
|
||||||
|
|
||||||
#import "MLNFeature.h"
|
#import "MLNFeature.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
|
||||||
#import <mbgl/util/geojson.hpp>
|
|
||||||
#import <mapbox/polylabel.hpp>
|
#import <mapbox/polylabel.hpp>
|
||||||
|
#import <mbgl/util/geojson.hpp>
|
||||||
|
|
||||||
@implementation MLNPolyline
|
@implementation MLNPolyline
|
||||||
|
|
||||||
@dynamic overlayBounds;
|
@dynamic overlayBounds;
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding
|
+ (BOOL)supportsSecureCoding {
|
||||||
{
|
return YES;
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords
|
+ (instancetype)polylineWithCoordinates:(const CLLocationCoordinate2D *)coords
|
||||||
count:(NSUInteger)count
|
count:(NSUInteger)count {
|
||||||
{
|
return [[self alloc] initWithCoordinates:coords count:count];
|
||||||
return [[self alloc] initWithCoordinates:coords count:count];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::LineString<double>)lineString {
|
- (mbgl::LineString<double>)lineString {
|
||||||
NSUInteger count = self.pointCount;
|
NSUInteger count = self.pointCount;
|
||||||
CLLocationCoordinate2D *coordinates = self.coordinates;
|
CLLocationCoordinate2D *coordinates = self.coordinates;
|
||||||
|
|
||||||
mbgl::LineString<double> geometry;
|
mbgl::LineString<double> geometry;
|
||||||
geometry.reserve(self.pointCount);
|
geometry.reserve(self.pointCount);
|
||||||
for (NSUInteger i = 0; i < count; i++) {
|
for (NSUInteger i = 0; i < count; i++) {
|
||||||
geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
|
geometry.push_back(mbgl::Point<double>(coordinates[i].longitude, coordinates[i].latitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
return geometry;
|
return geometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Annotation)annotationObjectWithDelegate:(id <MLNMultiPointDelegate>)delegate {
|
- (mbgl::Annotation)annotationObjectWithDelegate:(id<MLNMultiPointDelegate>)delegate {
|
||||||
mbgl::LineAnnotation annotation { [self lineString] };
|
mbgl::LineAnnotation annotation{[self lineString]};
|
||||||
annotation.opacity = { static_cast<float>([delegate alphaForShapeAnnotation:self]) };
|
annotation.opacity = {static_cast<float>([delegate alphaForShapeAnnotation:self])};
|
||||||
annotation.color = { [delegate strokeColorForShapeAnnotation:self] };
|
annotation.color = {[delegate strokeColorForShapeAnnotation:self]};
|
||||||
annotation.width = { static_cast<float>([delegate lineWidthForPolylineAnnotation:self]) };
|
annotation.width = {static_cast<float>([delegate lineWidthForPolylineAnnotation:self])};
|
||||||
|
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
return [self lineString];
|
return [self lineString];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return @{@"type": @"LineString",
|
return @{@"type" : @"LineString", @"coordinates" : self.mgl_coordinates};
|
||||||
@"coordinates": self.mgl_coordinates};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id> *)mgl_coordinates {
|
- (NSArray<id> *)mgl_coordinates {
|
||||||
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
|
NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:self.pointCount];
|
||||||
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
for (NSUInteger index = 0; index < self.pointCount; index++) {
|
||||||
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
CLLocationCoordinate2D coordinate = self.coordinates[index];
|
||||||
[coordinates addObject:@[@(coordinate.longitude), @(coordinate.latitude)]];
|
[coordinates addObject:@[ @(coordinate.longitude), @(coordinate.latitude) ]];
|
||||||
}
|
}
|
||||||
return [coordinates copy];
|
return [coordinates copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]);
|
return self == other || ([other isKindOfClass:[MLNPolyline class]] && [super isEqual:other]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate {
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
NSUInteger count = self.pointCount;
|
NSUInteger count = self.pointCount;
|
||||||
MLNAssert(count > 0, @"Polyline must have coordinates");
|
MLNAssert(count > 0, @"Polyline must have coordinates");
|
||||||
|
|
||||||
CLLocationCoordinate2D *coordinates = self.coordinates;
|
CLLocationCoordinate2D *coordinates = self.coordinates;
|
||||||
CLLocationDistance middle = [self length] / 2.0;
|
CLLocationDistance middle = [self length] / 2.0;
|
||||||
CLLocationDistance traveled = 0.0;
|
CLLocationDistance traveled = 0.0;
|
||||||
|
|
||||||
if (count > 1 || middle > traveled) {
|
if (count > 1 || middle > traveled) {
|
||||||
for (NSUInteger i = 0; i < count; i++) {
|
for (NSUInteger i = 0; i < count; i++) {
|
||||||
|
// Avoid a heap buffer overflow when there are only two coordinates.
|
||||||
|
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
|
||||||
|
|
||||||
// Avoid a heap buffer overflow when there are only two coordinates.
|
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
|
||||||
NSUInteger nextIndex = (i + 1 == count) ? 0 : 1;
|
MLNRadianCoordinate2D to =
|
||||||
|
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
|
||||||
|
|
||||||
MLNRadianCoordinate2D from = MLNRadianCoordinateFromLocationCoordinate(coordinates[i]);
|
if (traveled >= middle) {
|
||||||
MLNRadianCoordinate2D to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i + nextIndex]);
|
double overshoot = middle - traveled;
|
||||||
|
if (overshoot == 0) {
|
||||||
if (traveled >= middle) {
|
return coordinates[i];
|
||||||
double overshoot = middle - traveled;
|
|
||||||
if (overshoot == 0) {
|
|
||||||
return coordinates[i];
|
|
||||||
}
|
|
||||||
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
|
|
||||||
CLLocationDirection direction = [self direction:from to:to] - 180;
|
|
||||||
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(from,
|
|
||||||
overshoot/mbgl::util::EARTH_RADIUS_M,
|
|
||||||
MLNRadiansFromDegrees(direction));
|
|
||||||
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
|
|
||||||
MLNDegreesFromRadians(otherCoordinate.longitude));
|
|
||||||
}
|
|
||||||
|
|
||||||
traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
|
|
||||||
}
|
}
|
||||||
}
|
to = MLNRadianCoordinateFromLocationCoordinate(coordinates[i - 1]);
|
||||||
|
CLLocationDirection direction = [self direction:from to:to] - 180;
|
||||||
|
MLNRadianCoordinate2D otherCoordinate = MLNRadianCoordinateAtDistanceFacingDirection(
|
||||||
|
from, overshoot / mbgl::util::EARTH_RADIUS_M, MLNRadiansFromDegrees(direction));
|
||||||
|
return CLLocationCoordinate2DMake(MLNDegreesFromRadians(otherCoordinate.latitude),
|
||||||
|
MLNDegreesFromRadians(otherCoordinate.longitude));
|
||||||
|
}
|
||||||
|
|
||||||
return coordinates[count - 1];
|
traveled += (MLNDistanceBetweenRadianCoordinates(from, to) * mbgl::util::EARTH_RADIUS_M);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordinates[count - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationDistance)length
|
- (CLLocationDistance)length {
|
||||||
{
|
CLLocationDistance length = 0.0;
|
||||||
CLLocationDistance length = 0.0;
|
|
||||||
|
|
||||||
NSUInteger count = self.pointCount;
|
NSUInteger count = self.pointCount;
|
||||||
CLLocationCoordinate2D *coordinates = self.coordinates;
|
CLLocationCoordinate2D *coordinates = self.coordinates;
|
||||||
|
|
||||||
for (NSUInteger i = 0; i < count - 1; i++) {
|
for (NSUInteger i = 0; i < count - 1; i++) {
|
||||||
length += (MLNDistanceBetweenRadianCoordinates(MLNRadianCoordinateFromLocationCoordinate(coordinates[i]), MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) * mbgl::util::EARTH_RADIUS_M);
|
length += (MLNDistanceBetweenRadianCoordinates(
|
||||||
}
|
MLNRadianCoordinateFromLocationCoordinate(coordinates[i]),
|
||||||
|
MLNRadianCoordinateFromLocationCoordinate(coordinates[i + 1])) *
|
||||||
|
mbgl::util::EARTH_RADIUS_M);
|
||||||
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to
|
- (CLLocationDirection)direction:(MLNRadianCoordinate2D)from to:(MLNRadianCoordinate2D)to {
|
||||||
{
|
return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to));
|
||||||
return MLNDegreesFromRadians(MLNRadianCoordinatesDirection(from, to));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -134,112 +131,112 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNMultiPolyline {
|
@implementation MLNMultiPolyline {
|
||||||
MLNCoordinateBounds _overlayBounds;
|
MLNCoordinateBounds _overlayBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize overlayBounds = _overlayBounds;
|
@synthesize overlayBounds = _overlayBounds;
|
||||||
|
|
||||||
+ (instancetype)multiPolylineWithPolylines:(NSArray<MLNPolyline *> *)polylines {
|
+ (instancetype)multiPolylineWithPolylines:(NSArray<MLNPolyline *> *)polylines {
|
||||||
return [[self alloc] initWithPolylines:polylines];
|
return [[self alloc] initWithPolylines:polylines];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPolylines:(NSArray<MLNPolyline *> *)polylines {
|
- (instancetype)initWithPolylines:(NSArray<MLNPolyline *> *)polylines {
|
||||||
MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count);
|
MLNLogDebug(@"Initializing with %lu polylines.", (unsigned long)polylines.count);
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_polylines = polylines;
|
_polylines = polylines;
|
||||||
|
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
|
|
||||||
for (MLNPolyline *polyline in _polylines) {
|
for (MLNPolyline *polyline in _polylines) {
|
||||||
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
|
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
|
||||||
}
|
|
||||||
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
|
||||||
}
|
}
|
||||||
return self;
|
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
if (self = [super initWithCoder:decoder]) {
|
if (self = [super initWithCoder:decoder]) {
|
||||||
NSSet<Class> *polylinesClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNPolyline class]]];
|
NSSet<Class> *polylinesClasses =
|
||||||
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
|
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNPolyline class] ]];
|
||||||
|
_polylines = [decoder decodeObjectOfClasses:polylinesClasses forKey:@"polylines"];
|
||||||
|
|
||||||
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
|
||||||
|
|
||||||
for (MLNPolyline *polyline in _polylines) {
|
for (MLNPolyline *polyline in _polylines) {
|
||||||
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
|
bounds.extend(MLNLatLngBoundsFromCoordinateBounds(polyline.overlayBounds));
|
||||||
}
|
|
||||||
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
|
||||||
}
|
}
|
||||||
return self;
|
_overlayBounds = MLNCoordinateBoundsFromLatLngBounds(bounds);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
[super encodeWithCoder:coder];
|
[super encodeWithCoder:coder];
|
||||||
[coder encodeObject:_polylines forKey:@"polylines"];
|
[coder encodeObject:_polylines forKey:@"polylines"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other
|
- (BOOL)isEqual:(id)other {
|
||||||
{
|
if (self == other) return YES;
|
||||||
if (self == other) return YES;
|
if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO;
|
||||||
if (![other isKindOfClass:[MLNMultiPolyline class]]) return NO;
|
|
||||||
|
|
||||||
MLNMultiPolyline *otherMultipoline = other;
|
MLNMultiPolyline *otherMultipoline = other;
|
||||||
return ([super isEqual:otherMultipoline]
|
return ([super isEqual:otherMultipoline] &&
|
||||||
&& [self.polylines isEqualToArray:otherMultipoline.polylines]);
|
[self.polylines isEqualToArray:otherMultipoline.polylines]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
NSUInteger hash = [super hash];
|
NSUInteger hash = [super hash];
|
||||||
for (MLNPolyline *polyline in self.polylines) {
|
for (MLNPolyline *polyline in self.polylines) {
|
||||||
hash += [polyline hash];
|
hash += [polyline hash];
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate {
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
MLNPolyline *polyline = self.polylines.firstObject;
|
MLNPolyline *polyline = self.polylines.firstObject;
|
||||||
CLLocationCoordinate2D *coordinates = polyline.coordinates;
|
CLLocationCoordinate2D *coordinates = polyline.coordinates;
|
||||||
MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
|
MLNAssert([polyline pointCount] > 0, @"Polyline must have coordinates");
|
||||||
CLLocationCoordinate2D firstCoordinate = coordinates[0];
|
CLLocationCoordinate2D firstCoordinate = coordinates[0];
|
||||||
|
|
||||||
return firstCoordinate;
|
return firstCoordinate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
- (BOOL)intersectsOverlayBounds:(MLNCoordinateBounds)overlayBounds {
|
||||||
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
|
return MLNCoordinateBoundsIntersectsCoordinateBounds(_overlayBounds, overlayBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
mbgl::MultiLineString<double> multiLineString;
|
mbgl::MultiLineString<double> multiLineString;
|
||||||
multiLineString.reserve(self.polylines.count);
|
multiLineString.reserve(self.polylines.count);
|
||||||
for (MLNPolyline *polyline in self.polylines) {
|
for (MLNPolyline *polyline in self.polylines) {
|
||||||
multiLineString.push_back([polyline lineString]);
|
multiLineString.push_back([polyline lineString]);
|
||||||
}
|
}
|
||||||
return multiLineString;
|
return multiLineString;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
NSMutableArray *coordinates = [NSMutableArray array];
|
NSMutableArray *coordinates = [NSMutableArray array];
|
||||||
for (MLNPolylineFeature *feature in self.polylines) {
|
for (MLNPolylineFeature *feature in self.polylines) {
|
||||||
[coordinates addObject: feature.mgl_coordinates];
|
[coordinates addObject:feature.mgl_coordinates];
|
||||||
}
|
}
|
||||||
return @{@"type": @"MultiLineString",
|
return @{@"type" : @"MultiLineString", @"coordinates" : coordinates};
|
||||||
@"coordinates": coordinates};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
return [NSString
|
||||||
return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
|
stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self,
|
NSStringFromClass([self class]), (void *)self,
|
||||||
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title,
|
||||||
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle,
|
self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle]
|
||||||
(unsigned long)self.polylines.count,
|
: self.subtitle,
|
||||||
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
(unsigned long)self.polylines.count,
|
||||||
|
MLNStringFromCoordinateBounds(self.overlayBounds)];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ const MLNTileSourceOption MLNTileSourceOptionDEMEncoding = @"MLNTileSourceOption
|
|||||||
|
|
||||||
@implementation MLNRasterDEMSource
|
@implementation MLNRasterDEMSource
|
||||||
|
|
||||||
- (std::unique_ptr<mbgl::style::RasterSource>)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset tileSize:(uint16_t)tileSize {
|
- (std::unique_ptr<mbgl::style::RasterSource>)
|
||||||
auto source = std::make_unique<mbgl::style::RasterDEMSource>(identifier.UTF8String,
|
pendingSourceWithIdentifier:(NSString *)identifier
|
||||||
urlOrTileset,
|
urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset
|
||||||
tileSize);
|
tileSize:(uint16_t)tileSize {
|
||||||
return source;
|
auto source =
|
||||||
|
std::make_unique<mbgl::style::RasterDEMSource>(identifier.UTF8String, urlOrTileset, tileSize);
|
||||||
|
return source;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -22,64 +22,78 @@ static const CGFloat MLNRasterTileSourceRetinaTileSize = 512;
|
|||||||
|
|
||||||
@implementation MLNRasterTileSource
|
@implementation MLNRasterTileSource
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
// The style specification default is 512, but 256 is the expected value for
|
configurationURL:(NSURL *)configurationURL {
|
||||||
// any tile set that would be accessed through a mapbox: URL and therefore
|
// The style specification default is 512, but 256 is the expected value for
|
||||||
// any tile URL that this option currently affects.
|
// any tile set that would be accessed through a mapbox: URL and therefore
|
||||||
BOOL isMapboxURL = ([configurationURL.scheme isEqualToString:@"mapbox"]
|
// any tile URL that this option currently affects.
|
||||||
&& [configurationURL.host containsString:@"."]
|
BOOL isMapboxURL =
|
||||||
&& (!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"]));
|
([configurationURL.scheme isEqualToString:@"mapbox"] &&
|
||||||
CGFloat tileSize = isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize;
|
[configurationURL.host containsString:@"."] &&
|
||||||
return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize];
|
(!configurationURL.path.length || [configurationURL.path isEqualToString:@"/"]));
|
||||||
|
CGFloat tileSize =
|
||||||
|
isMapboxURL ? MLNRasterTileSourceClassicTileSize : MLNRasterTileSourceRetinaTileSize;
|
||||||
|
return [self initWithIdentifier:identifier configurationURL:configurationURL tileSize:tileSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL tileSize:(CGFloat)tileSize {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString;
|
configurationURL:(NSURL *)configurationURL
|
||||||
auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:configurationURLString.UTF8String tileSize:uint16_t(round(tileSize))];
|
tileSize:(CGFloat)tileSize {
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
NSString *configurationURLString = configurationURL.mgl_URLByStandardizingScheme.absoluteString;
|
||||||
|
auto source = [self pendingSourceWithIdentifier:identifier
|
||||||
|
urlOrTileset:configurationURLString.UTF8String
|
||||||
|
tileSize:uint16_t(round(tileSize))];
|
||||||
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::unique_ptr<mbgl::style::RasterSource>)pendingSourceWithIdentifier:(NSString *)identifier urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset tileSize:(uint16_t)tileSize {
|
- (std::unique_ptr<mbgl::style::RasterSource>)
|
||||||
auto source = std::make_unique<mbgl::style::RasterSource>(identifier.UTF8String,
|
pendingSourceWithIdentifier:(NSString *)identifier
|
||||||
urlOrTileset,
|
urlOrTileset:(mbgl::variant<std::string, mbgl::Tileset>)urlOrTileset
|
||||||
tileSize);
|
tileSize:(uint16_t)tileSize {
|
||||||
return source;
|
auto source =
|
||||||
|
std::make_unique<mbgl::style::RasterSource>(identifier.UTF8String, urlOrTileset, tileSize);
|
||||||
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
|
tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
|
||||||
|
options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
|
||||||
|
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
|
||||||
|
|
||||||
uint16_t tileSize = MLNRasterTileSourceRetinaTileSize;
|
uint16_t tileSize = MLNRasterTileSourceRetinaTileSize;
|
||||||
if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) {
|
if (NSNumber *tileSizeNumber = options[MLNTileSourceOptionTileSize]) {
|
||||||
if (![tileSizeNumber isKindOfClass:[NSNumber class]]) {
|
if (![tileSizeNumber isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."];
|
format:@"MLNTileSourceOptionTileSize must be set to an NSNumber."];
|
||||||
}
|
|
||||||
tileSize = static_cast<uint16_t>(round(tileSizeNumber.doubleValue));
|
|
||||||
}
|
}
|
||||||
|
tileSize = static_cast<uint16_t>(round(tileSizeNumber.doubleValue));
|
||||||
|
}
|
||||||
|
|
||||||
auto source = [self pendingSourceWithIdentifier:identifier urlOrTileset:tileSet tileSize:tileSize];
|
auto source = [self pendingSourceWithIdentifier:identifier
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
urlOrTileset:tileSet
|
||||||
|
tileSize:tileSize];
|
||||||
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::RasterSource *)rawSource {
|
- (mbgl::style::RasterSource *)rawSource {
|
||||||
return (mbgl::style::RasterSource *)super.rawSource;
|
return (mbgl::style::RasterSource *)super.rawSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL *)configurationURL {
|
- (NSURL *)configurationURL {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
auto url = self.rawSource->getURL();
|
auto url = self.rawSource->getURL();
|
||||||
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)attributionHTMLString {
|
- (NSString *)attributionHTMLString {
|
||||||
if (!self.rawSource) {
|
if (!self.rawSource) {
|
||||||
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
|
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
|
||||||
return nil;
|
self.identifier);
|
||||||
}
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
auto attribution = self.rawSource->getAttribution();
|
auto attribution = self.rawSource->getAttribution();
|
||||||
return attribution ? @(attribution->c_str()) : nil;
|
return attribution ? @(attribution->c_str()) : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
dependent way.
|
dependent way.
|
||||||
*/
|
*/
|
||||||
class MLNRenderFrontend : public mbgl::RendererFrontend {
|
class MLNRenderFrontend : public mbgl::RendererFrontend {
|
||||||
public:
|
public:
|
||||||
MLNRenderFrontend(std::unique_ptr<mbgl::Renderer> renderer_, MLNMapView* nativeView_,
|
MLNRenderFrontend(std::unique_ptr<mbgl::Renderer> renderer_, MLNMapView* nativeView_,
|
||||||
mbgl::gfx::RendererBackend& mbglBackend_, bool async = false)
|
mbgl::gfx::RendererBackend& mbglBackend_, bool async = false)
|
||||||
: renderer(std::move(renderer_)), nativeView(nativeView_), mbglBackend(mbglBackend_) {
|
: renderer(std::move(renderer_)), nativeView(nativeView_), mbglBackend(mbglBackend_) {
|
||||||
@@ -73,7 +73,7 @@ class MLNRenderFrontend : public mbgl::RendererFrontend {
|
|||||||
renderer->reduceMemoryUse();
|
renderer->reduceMemoryUse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<mbgl::Renderer> renderer;
|
std::unique_ptr<mbgl::Renderer> renderer;
|
||||||
__weak MLNMapView* nativeView = nullptr;
|
__weak MLNMapView* nativeView = nullptr;
|
||||||
mbgl::gfx::RendererBackend& mbglBackend;
|
mbgl::gfx::RendererBackend& mbglBackend;
|
||||||
|
|||||||
@@ -3,36 +3,35 @@
|
|||||||
@implementation MLNRenderingStats
|
@implementation MLNRenderingStats
|
||||||
|
|
||||||
- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats {
|
- (void)setCoreData:(const mbgl::gfx::RenderingStats&)stats {
|
||||||
_encodingTime = stats.encodingTime;
|
_encodingTime = stats.encodingTime;
|
||||||
_renderingTime = stats.renderingTime;
|
_renderingTime = stats.renderingTime;
|
||||||
_numFrames = stats.numFrames;
|
_numFrames = stats.numFrames;
|
||||||
_numDrawCalls = stats.numDrawCalls;
|
_numDrawCalls = stats.numDrawCalls;
|
||||||
_totalDrawCalls = stats.totalDrawCalls;
|
_totalDrawCalls = stats.totalDrawCalls;
|
||||||
_numCreatedTextures = stats.numCreatedTextures;
|
_numCreatedTextures = stats.numCreatedTextures;
|
||||||
_numActiveTextures = stats.numActiveTextures;
|
_numActiveTextures = stats.numActiveTextures;
|
||||||
_numTextureBindings = stats.numTextureBindings;
|
_numTextureBindings = stats.numTextureBindings;
|
||||||
_numTextureUpdates = stats.numTextureUpdates;
|
_numTextureUpdates = stats.numTextureUpdates;
|
||||||
_textureUpdateBytes = stats.textureUpdateBytes;
|
_textureUpdateBytes = stats.textureUpdateBytes;
|
||||||
_totalBuffers = stats.totalBuffers;
|
_totalBuffers = stats.totalBuffers;
|
||||||
_totalBufferObjs = stats.totalBufferObjs;
|
_totalBufferObjs = stats.totalBufferObjs;
|
||||||
_bufferUpdates = stats.bufferUpdates;
|
_bufferUpdates = stats.bufferUpdates;
|
||||||
_bufferObjUpdates = stats.bufferObjUpdates;
|
_bufferObjUpdates = stats.bufferObjUpdates;
|
||||||
_bufferUpdateBytes = stats.bufferUpdateBytes;
|
_bufferUpdateBytes = stats.bufferUpdateBytes;
|
||||||
_numIndexBuffers = stats.numIndexBuffers;
|
_numIndexBuffers = stats.numIndexBuffers;
|
||||||
_indexUpdateBytes = stats.indexUpdateBytes;
|
_indexUpdateBytes = stats.indexUpdateBytes;
|
||||||
_numVertexBuffers = stats.numVertexBuffers;
|
_numVertexBuffers = stats.numVertexBuffers;
|
||||||
_vertexUpdateBytes = stats.vertexUpdateBytes;
|
_vertexUpdateBytes = stats.vertexUpdateBytes;
|
||||||
_numUniformBuffers = stats.numUniformBuffers;
|
_numUniformBuffers = stats.numUniformBuffers;
|
||||||
_numUniformUpdates = stats.numUniformUpdates;
|
_numUniformUpdates = stats.numUniformUpdates;
|
||||||
_uniformUpdateBytes = stats.uniformUpdateBytes;
|
_uniformUpdateBytes = stats.uniformUpdateBytes;
|
||||||
_memTextures = stats.memTextures;
|
_memTextures = stats.memTextures;
|
||||||
_memBuffers = stats.memBuffers;
|
_memBuffers = stats.memBuffers;
|
||||||
_memIndexBuffers = stats.memIndexBuffers;
|
_memIndexBuffers = stats.memIndexBuffers;
|
||||||
_memVertexBuffers = stats.memVertexBuffers;
|
_memVertexBuffers = stats.memVertexBuffers;
|
||||||
_memUniformBuffers = stats.memUniformBuffers;
|
_memUniformBuffers = stats.memUniformBuffers;
|
||||||
_stencilClears = stats.stencilClears;
|
_stencilClears = stats.stencilClears;
|
||||||
_stencilUpdates = stats.stencilUpdates;
|
_stencilUpdates = stats.stencilUpdates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -18,215 +18,231 @@
|
|||||||
// MARK: - Internal
|
// MARK: - Internal
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
self.tileServerOptionsInternal = new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration());
|
self.tileServerOptionsInternal =
|
||||||
}
|
new mbgl::TileServerOptions(mbgl::TileServerOptions::DefaultConfiguration());
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)load {
|
+ (void)load {
|
||||||
// Read the initial configuration from Info.plist.
|
// Read the initial configuration from Info.plist.
|
||||||
NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"];
|
NSString *apiKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNApiKey"];
|
||||||
if (apiKey.length) {
|
if (apiKey.length) {
|
||||||
self.apiKey = apiKey;
|
self.apiKey = apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"];
|
NSString *apiBaseURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MLNTileServerBaseURL"];
|
||||||
|
|
||||||
// If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`.
|
// If apiBaseURL is not a valid URL, [NSURL URLWithString:] will be `nil`.
|
||||||
if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) {
|
if (apiBaseURL.length && [NSURL URLWithString:apiBaseURL]) {
|
||||||
[self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]];
|
[self setAPIBaseURL:[NSURL URLWithString:apiBaseURL]];
|
||||||
[self tileServerOptionsChanged];
|
[self tileServerOptionsChanged];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)sharedSettings {
|
+ (instancetype)sharedSettings {
|
||||||
#if TARGET_OS_OSX
|
#if TARGET_OS_OSX
|
||||||
if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
|
if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
static MLNSettings *_sharedSettings;
|
static MLNSettings *_sharedSettings;
|
||||||
void (^setupBlock)(void) = ^{
|
void (^setupBlock)(void) = ^{
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
_sharedSettings = [[self alloc] init];
|
_sharedSettings = [[self alloc] init];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
if (![[NSThread currentThread] isMainThread]) {
|
if (![[NSThread currentThread] isMainThread]) {
|
||||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||||
setupBlock();
|
setupBlock();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setupBlock();
|
setupBlock();
|
||||||
}
|
}
|
||||||
return _sharedSettings;
|
return _sharedSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setApiKey:(NSString *)apiKey {
|
+ (void)setApiKey:(NSString *)apiKey {
|
||||||
apiKey = [apiKey stringByTrimmingCharactersInSet:
|
apiKey =
|
||||||
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
[apiKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||||
if (!apiKey.length) {
|
if (!apiKey.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MLNSettings sharedSettings].apiKey = apiKey;
|
[MLNSettings sharedSettings].apiKey = apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)apiKey {
|
+ (NSString *)apiKey {
|
||||||
return [MLNSettings sharedSettings].apiKey;
|
return [MLNSettings sharedSettings].apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setAPIBaseURL:(NSURL *)apiBaseURL {
|
+ (void)setAPIBaseURL:(NSURL *)apiBaseURL {
|
||||||
auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal;
|
auto tileServerOptions = [MLNSettings sharedSettings].tileServerOptionsInternal;
|
||||||
NSString *baseUrlNSStr = apiBaseURL.absoluteString;
|
NSString *baseUrlNSStr = apiBaseURL.absoluteString;
|
||||||
auto baseUrl = std::string([baseUrlNSStr UTF8String]);
|
auto baseUrl = std::string([baseUrlNSStr UTF8String]);
|
||||||
|
|
||||||
[MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl);
|
[MLNSettings sharedSettings].tileServerOptionsInternal = &tileServerOptions->withBaseURL(baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSURL *)apiBaseURL {
|
+ (NSURL *)apiBaseURL {
|
||||||
auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL();
|
auto baseUrl = [MLNSettings sharedSettings].tileServerOptionsInternal->baseURL();
|
||||||
NSString* baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()];
|
NSString *baseUrlNSStr = [NSString stringWithUTF8String:baseUrl.c_str()];
|
||||||
NSURL *url = [NSURL URLWithString:baseUrlNSStr];
|
NSURL *url = [NSURL URLWithString:baseUrlNSStr];
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setTileServerOptionsInternal:(mbgl::TileServerOptions)options {
|
+ (void)setTileServerOptionsInternal:(mbgl::TileServerOptions)options {
|
||||||
[MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions();
|
[MLNSettings sharedSettings].tileServerOptionsInternal = new mbgl::TileServerOptions();
|
||||||
*[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone();
|
*[MLNSettings sharedSettings].tileServerOptionsInternal = options.clone();
|
||||||
[self tileServerOptionsChanged];
|
[self tileServerOptionsChanged];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (mbgl::TileServerOptions)tileServerOptionsInternal {
|
+ (mbgl::TileServerOptions)tileServerOptionsInternal {
|
||||||
auto options = [MLNSettings sharedSettings];
|
auto options = [MLNSettings sharedSettings];
|
||||||
return options.tileServerOptionsInternal->clone();
|
return options.tileServerOptionsInternal->clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)tileServerOptionsChanged {
|
+ (void)tileServerOptionsChanged {
|
||||||
[MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString];
|
[MLNSettings sharedSettings].tileServerOptionsChangeToken = [[NSUUID UUID] UUIDString];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)useWellKnownTileServer:(MLNWellKnownTileServer)tileServer {
|
+ (void)useWellKnownTileServer:(MLNWellKnownTileServer)tileServer {
|
||||||
switch(tileServer){
|
switch (tileServer) {
|
||||||
case MLNMapTiler:
|
case MLNMapTiler:
|
||||||
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()];
|
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapTilerConfiguration()];
|
||||||
break;
|
break;
|
||||||
case MLNMapLibre:
|
case MLNMapLibre:
|
||||||
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()];
|
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapLibreConfiguration()];
|
||||||
break;
|
break;
|
||||||
case MLNMapbox:
|
case MLNMapbox:
|
||||||
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()];
|
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::MapboxConfiguration()];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()];
|
[MLNSettings setTileServerOptionsInternal:mbgl::TileServerOptions::DefaultConfiguration()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)setTileServerOptions:(MLNTileServerOptions*)options {
|
+ (void)setTileServerOptions:(MLNTileServerOptions *)options {
|
||||||
|
auto opts = mbgl::TileServerOptions()
|
||||||
|
.withBaseURL(std::string([options.baseURL UTF8String]))
|
||||||
|
.withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String]))
|
||||||
|
.withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String]));
|
||||||
|
|
||||||
auto opts = mbgl::TileServerOptions()
|
opts.withSourceTemplate(std::string([options.sourceTemplate UTF8String]),
|
||||||
.withBaseURL(std::string([options.baseURL UTF8String]))
|
std::string([options.sourceDomainName UTF8String]),
|
||||||
.withUriSchemeAlias(std::string([options.uriSchemeAlias UTF8String]))
|
options.sourceVersionPrefix
|
||||||
.withApiKeyParameterName(std::string([options.apiKeyParameterName UTF8String]));
|
? std::string([options.sourceVersionPrefix UTF8String])
|
||||||
|
: std::optional<std::string>{});
|
||||||
|
|
||||||
opts.withSourceTemplate(
|
opts.withStyleTemplate(std::string([options.styleTemplate UTF8String]),
|
||||||
std::string([options.sourceTemplate UTF8String]),
|
std::string([options.styleDomainName UTF8String]),
|
||||||
std::string([options.sourceDomainName UTF8String]),
|
options.styleVersionPrefix
|
||||||
options.sourceVersionPrefix ? std::string([options.sourceVersionPrefix UTF8String]) : std::optional<std::string>{});
|
? std::string([options.styleVersionPrefix UTF8String])
|
||||||
|
: std::optional<std::string>{});
|
||||||
|
|
||||||
opts.withStyleTemplate(
|
opts.withSpritesTemplate(std::string([options.spritesTemplate UTF8String]),
|
||||||
std::string([options.styleTemplate UTF8String]),
|
std::string([options.spritesDomainName UTF8String]),
|
||||||
std::string([options.styleDomainName UTF8String]),
|
options.spritesVersionPrefix
|
||||||
options.styleVersionPrefix ? std::string([options.styleVersionPrefix UTF8String]) : std::optional<std::string>{});
|
? std::string([options.spritesVersionPrefix UTF8String])
|
||||||
|
: std::optional<std::string>{});
|
||||||
|
|
||||||
opts.withSpritesTemplate(
|
opts.withGlyphsTemplate(std::string([options.glyphsTemplate UTF8String]),
|
||||||
std::string([options.spritesTemplate UTF8String]),
|
std::string([options.glyphsDomainName UTF8String]),
|
||||||
std::string([options.spritesDomainName UTF8String]),
|
options.glyphsVersionPrefix
|
||||||
options.spritesVersionPrefix ? std::string([options.spritesVersionPrefix UTF8String]) : std::optional<std::string>{});
|
? std::string([options.glyphsVersionPrefix UTF8String])
|
||||||
|
: std::optional<std::string>{});
|
||||||
|
|
||||||
opts.withGlyphsTemplate(
|
opts.withTileTemplate(std::string([options.tileTemplate UTF8String]),
|
||||||
std::string([options.glyphsTemplate UTF8String]),
|
std::string([options.tileDomainName UTF8String]),
|
||||||
std::string([options.glyphsDomainName UTF8String]),
|
options.tileVersionPrefix
|
||||||
options.glyphsVersionPrefix ? std::string([options.glyphsVersionPrefix UTF8String]) : std::optional<std::string>{});
|
? std::string([options.tileVersionPrefix UTF8String])
|
||||||
|
: std::optional<std::string>{});
|
||||||
|
|
||||||
opts.withTileTemplate(
|
std::vector<mbgl::util::DefaultStyle> defaultStyles;
|
||||||
std::string([options.tileTemplate UTF8String]),
|
if (options.defaultStyles) {
|
||||||
std::string([options.tileDomainName UTF8String]),
|
for (MLNDefaultStyle *objCStyle in options.defaultStyles) {
|
||||||
options.tileVersionPrefix ? std::string([options.tileVersionPrefix UTF8String]) : std::optional<std::string>{});
|
mbgl::util::DefaultStyle cppStyle(std::string([objCStyle.url.absoluteString UTF8String]),
|
||||||
|
std::string([objCStyle.name UTF8String]),
|
||||||
std::vector<mbgl::util::DefaultStyle> defaultStyles;
|
objCStyle.version);
|
||||||
if (options.defaultStyles) {
|
defaultStyles.push_back(cppStyle);
|
||||||
for (MLNDefaultStyle* objCStyle in options.defaultStyles) {
|
|
||||||
mbgl::util::DefaultStyle cppStyle(
|
|
||||||
std::string([objCStyle.url.absoluteString UTF8String]),
|
|
||||||
std::string([objCStyle.name UTF8String]),
|
|
||||||
objCStyle.version
|
|
||||||
);
|
|
||||||
defaultStyles.push_back(cppStyle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
opts.withDefaultStyles(defaultStyles);
|
}
|
||||||
opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String]));
|
opts.withDefaultStyles(defaultStyles);
|
||||||
|
opts.withDefaultStyle(std::string([options.defaultStyle.name UTF8String]));
|
||||||
|
|
||||||
self.tileServerOptionsInternal = opts;
|
self.tileServerOptionsInternal = opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (MLNTileServerOptions*)tileServerOptions {
|
+ (MLNTileServerOptions *)tileServerOptions {
|
||||||
auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal;
|
auto cppOpts = [MLNSettings sharedSettings].tileServerOptionsInternal;
|
||||||
if (cppOpts) {
|
if (cppOpts) {
|
||||||
|
MLNTileServerOptions *retVal = [[MLNTileServerOptions alloc] init];
|
||||||
|
retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()];
|
||||||
|
retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()];
|
||||||
|
retVal.apiKeyParameterName =
|
||||||
|
[NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()];
|
||||||
|
|
||||||
MLNTileServerOptions* retVal = [[MLNTileServerOptions alloc] init];
|
retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()];
|
||||||
retVal.baseURL = [NSString stringWithUTF8String:cppOpts->baseURL().c_str()];
|
retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()];
|
||||||
retVal.uriSchemeAlias = [NSString stringWithUTF8String:cppOpts->uriSchemeAlias().c_str()];
|
retVal.sourceVersionPrefix =
|
||||||
retVal.apiKeyParameterName = [NSString stringWithUTF8String:cppOpts->apiKeyParameterName().c_str()];
|
cppOpts->sourceVersionPrefix().has_value()
|
||||||
|
? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()]
|
||||||
|
: nil;
|
||||||
|
|
||||||
retVal.sourceTemplate = [NSString stringWithUTF8String:cppOpts->sourceTemplate().c_str()];
|
retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()];
|
||||||
retVal.sourceDomainName = [NSString stringWithUTF8String:cppOpts->sourceDomainName().c_str()];
|
retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()];
|
||||||
retVal.sourceVersionPrefix = cppOpts->sourceVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->sourceVersionPrefix().value().c_str()] : nil;
|
retVal.styleVersionPrefix =
|
||||||
|
cppOpts->styleVersionPrefix().has_value()
|
||||||
|
? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()]
|
||||||
|
: nil;
|
||||||
|
|
||||||
retVal.styleTemplate = [NSString stringWithUTF8String:cppOpts->styleTemplate().c_str()];
|
retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()];
|
||||||
retVal.styleDomainName = [NSString stringWithUTF8String:cppOpts->styleDomainName().c_str()];
|
retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()];
|
||||||
retVal.styleVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->styleVersionPrefix().value().c_str()] : nil;
|
retVal.spritesVersionPrefix =
|
||||||
|
cppOpts->styleVersionPrefix().has_value()
|
||||||
|
? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()]
|
||||||
|
: nil;
|
||||||
|
|
||||||
retVal.spritesTemplate = [NSString stringWithUTF8String:cppOpts->spritesTemplate().c_str()];
|
retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()];
|
||||||
retVal.spritesDomainName = [NSString stringWithUTF8String:cppOpts->spritesDomainName().c_str()];
|
retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()];
|
||||||
retVal.spritesVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->spritesVersionPrefix().value().c_str()] : nil;
|
retVal.glyphsVersionPrefix =
|
||||||
|
cppOpts->styleVersionPrefix().has_value()
|
||||||
|
? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()]
|
||||||
|
: nil;
|
||||||
|
|
||||||
retVal.glyphsTemplate = [NSString stringWithUTF8String:cppOpts->glyphsTemplate().c_str()];
|
retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()];
|
||||||
retVal.glyphsDomainName = [NSString stringWithUTF8String:cppOpts->glyphsDomainName().c_str()];
|
retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()];
|
||||||
retVal.glyphsVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->glyphsVersionPrefix().value().c_str()] : nil;
|
retVal.tileVersionPrefix =
|
||||||
|
cppOpts->styleVersionPrefix().has_value()
|
||||||
|
? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()]
|
||||||
|
: nil;
|
||||||
|
|
||||||
retVal.tileTemplate = [NSString stringWithUTF8String:cppOpts->tileTemplate().c_str()];
|
std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles();
|
||||||
retVal.tileDomainName = [NSString stringWithUTF8String:cppOpts->tileDomainName().c_str()];
|
|
||||||
retVal.tileVersionPrefix = cppOpts->styleVersionPrefix().has_value() ? [NSString stringWithUTF8String:cppOpts->tileVersionPrefix().value().c_str()] : nil;
|
|
||||||
|
|
||||||
std::vector<mbgl::util::DefaultStyle> cppDefaultStyles = cppOpts->defaultStyles();
|
NSMutableArray<MLNDefaultStyle *> *mglStyles = [[NSMutableArray<MLNDefaultStyle *> alloc] init];
|
||||||
|
for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) {
|
||||||
|
NSString *url = [NSString stringWithUTF8String:it->getUrl().c_str()];
|
||||||
|
|
||||||
|
auto *mglDefaultStyle = [[MLNDefaultStyle alloc] init];
|
||||||
|
mglDefaultStyle.url = [NSURL URLWithString:url];
|
||||||
|
mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()];
|
||||||
|
mglDefaultStyle.version = it->getCurrentVersion();
|
||||||
|
[mglStyles addObject:mglDefaultStyle];
|
||||||
|
|
||||||
NSMutableArray<MLNDefaultStyle*>* mglStyles = [[NSMutableArray<MLNDefaultStyle*> alloc] init];
|
// set default style name
|
||||||
for (auto it = begin(cppDefaultStyles); it != end(cppDefaultStyles); ++it) {
|
if (it->getName() == cppOpts->defaultStyle()) {
|
||||||
NSString* url = [NSString stringWithUTF8String:it->getUrl().c_str()];
|
retVal.defaultStyle = mglDefaultStyle;
|
||||||
|
}
|
||||||
auto* mglDefaultStyle = [[MLNDefaultStyle alloc] init];
|
|
||||||
mglDefaultStyle.url = [NSURL URLWithString:url];
|
|
||||||
mglDefaultStyle.name = [NSString stringWithUTF8String:it->getName().c_str()];
|
|
||||||
mglDefaultStyle.version = it->getCurrentVersion();
|
|
||||||
[mglStyles addObject:mglDefaultStyle];
|
|
||||||
|
|
||||||
// set default style name
|
|
||||||
if (it->getName() == cppOpts->defaultStyle()) {
|
|
||||||
retVal.defaultStyle = mglDefaultStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
retVal.defaultStyles = mglStyles;
|
|
||||||
return retVal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil;
|
retVal.defaultStyles = mglStyles;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,110 +2,110 @@
|
|||||||
|
|
||||||
#import "MLNFeature_Private.h"
|
#import "MLNFeature_Private.h"
|
||||||
|
|
||||||
#import "NSString+MLNAdditions.h"
|
|
||||||
#import "MLNTypes.h"
|
#import "MLNTypes.h"
|
||||||
|
#import "NSString+MLNAdditions.h"
|
||||||
|
|
||||||
#import <mbgl/util/geo.hpp>
|
#import <mbgl/util/geo.hpp>
|
||||||
|
|
||||||
bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) {
|
bool operator==(const CLLocationCoordinate2D lhs, const CLLocationCoordinate2D rhs) {
|
||||||
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
|
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
@implementation MLNShape
|
@implementation MLNShape
|
||||||
|
|
||||||
+ (nullable MLNShape *)shapeWithData:(NSData *)data encoding:(NSStringEncoding)encoding error:(NSError * _Nullable *)outError {
|
+ (nullable MLNShape *)shapeWithData:(NSData *)data
|
||||||
NSString *string = [[NSString alloc] initWithData:data encoding:encoding];
|
encoding:(NSStringEncoding)encoding
|
||||||
if (!string) {
|
error:(NSError *_Nullable *)outError {
|
||||||
if (outError) {
|
NSString *string = [[NSString alloc] initWithData:data encoding:encoding];
|
||||||
*outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil];
|
if (!string) {
|
||||||
}
|
if (outError) {
|
||||||
return nil;
|
*outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:nil];
|
||||||
}
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const auto geojson = mapbox::geojson::parse(string.UTF8String);
|
const auto geojson = mapbox::geojson::parse(string.UTF8String);
|
||||||
return MLNShapeFromGeoJSON(geojson);
|
return MLNShapeFromGeoJSON(geojson);
|
||||||
} catch (std::runtime_error &err) {
|
} catch (std::runtime_error &err) {
|
||||||
if (outError) {
|
if (outError) {
|
||||||
*outError = [NSError errorWithDomain:MLNErrorDomain code:MLNErrorCodeUnknown userInfo:@{
|
*outError = [NSError errorWithDomain:MLNErrorDomain
|
||||||
NSLocalizedFailureReasonErrorKey: @(err.what()),
|
code:MLNErrorCodeUnknown
|
||||||
}];
|
userInfo:@{
|
||||||
}
|
NSLocalizedFailureReasonErrorKey : @(err.what()),
|
||||||
return nil;
|
}];
|
||||||
}
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::GeoJSON)geoJSONObject {
|
- (mbgl::GeoJSON)geoJSONObject {
|
||||||
return self.geometryObject;
|
return self.geometryObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"];
|
||||||
format:@"MLNShape is an abstract class"];
|
return mbgl::Point<double>();
|
||||||
return mbgl::Point<double>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding {
|
- (NSData *)geoJSONDataUsingEncoding:(NSStringEncoding)encoding {
|
||||||
auto geometry = self.geoJSONObject;
|
auto geometry = self.geoJSONObject;
|
||||||
NSString *string = @(mapbox::geojson::stringify(geometry).c_str());
|
NSString *string = @(mapbox::geojson::stringify(geometry).c_str());
|
||||||
return [string dataUsingEncoding:NSUTF8StringEncoding];
|
return [string dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding
|
+ (BOOL)supportsSecureCoding {
|
||||||
{
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
|
if (self = [super init]) {
|
||||||
|
_title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"];
|
||||||
|
_subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
|
||||||
|
#if !TARGET_OS_IPHONE
|
||||||
|
_toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
|
[coder encodeObject:_title forKey:@"title"];
|
||||||
|
[coder encodeObject:_subtitle forKey:@"subtitle"];
|
||||||
|
#if !TARGET_OS_IPHONE
|
||||||
|
[coder encodeObject:_toolTip forKey:@"toolTip"];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isEqual:(id)other {
|
||||||
|
if (other == self) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
id<MLNAnnotation> annotation = other;
|
||||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
|
||||||
{
|
|
||||||
if (self = [super init]) {
|
|
||||||
_title = [coder decodeObjectOfClass:[NSString class] forKey:@"title"];
|
|
||||||
_subtitle = [coder decodeObjectOfClass:[NSString class] forKey:@"subtitle"];
|
|
||||||
#if !TARGET_OS_IPHONE
|
|
||||||
_toolTip = [coder decodeObjectOfClass:[NSString class] forKey:@"toolTip"];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
|
||||||
{
|
|
||||||
[coder encodeObject:_title forKey:@"title"];
|
|
||||||
[coder encodeObject:_subtitle forKey:@"subtitle"];
|
|
||||||
#if !TARGET_OS_IPHONE
|
|
||||||
[coder encodeObject:_toolTip forKey:@"toolTip"];
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other
|
|
||||||
{
|
|
||||||
if (other == self) { return YES; }
|
|
||||||
id <MLNAnnotation> annotation = other;
|
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]])
|
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) &&
|
||||||
&& ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]]);
|
((!_subtitle && ![annotation subtitle]) ||
|
||||||
|
[_subtitle isEqualToString:[annotation subtitle]]);
|
||||||
#else
|
#else
|
||||||
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]])
|
return ((!_title && ![annotation title]) || [_title isEqualToString:[annotation title]]) &&
|
||||||
&& ((!_subtitle && ![annotation subtitle]) || [_subtitle isEqualToString:[annotation subtitle]])
|
((!_subtitle && ![annotation subtitle]) ||
|
||||||
&& ((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]);
|
[_subtitle isEqualToString:[annotation subtitle]]) &&
|
||||||
|
((!_toolTip && ![annotation toolTip]) || [_toolTip isEqualToString:[annotation toolTip]]);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash
|
- (NSUInteger)hash {
|
||||||
{
|
NSUInteger hash = _title.hash + _subtitle.hash;
|
||||||
NSUInteger hash = _title.hash + _subtitle.hash;
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
hash += _toolTip.hash;
|
hash += _toolTip.hash;
|
||||||
#endif
|
#endif
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
{
|
[NSException raise:MLNAbstractClassException format:@"MLNShape is an abstract class"];
|
||||||
[NSException raise:MLNAbstractClassException
|
return kCLLocationCoordinate2DInvalid;
|
||||||
format:@"MLNShape is an abstract class"];
|
|
||||||
return kCLLocationCoordinate2DInvalid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,85 +1,85 @@
|
|||||||
#import "MLNShapeCollection.h"
|
#import "MLNShapeCollection.h"
|
||||||
|
|
||||||
#import "MLNShape_Private.h"
|
|
||||||
#import "MLNFeature.h"
|
#import "MLNFeature.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNShape_Private.h"
|
||||||
|
|
||||||
#import <mbgl/style/conversion/geojson.hpp>
|
#import <mbgl/style/conversion/geojson.hpp>
|
||||||
|
|
||||||
@implementation MLNShapeCollection
|
@implementation MLNShapeCollection
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape *> *)shapes {
|
+ (instancetype)shapeCollectionWithShapes:(NSArray<MLNShape *> *)shapes {
|
||||||
return [[self alloc] initWithShapes:shapes];
|
return [[self alloc] initWithShapes:shapes];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithShapes:(NSArray<MLNShape *> *)shapes {
|
- (instancetype)initWithShapes:(NSArray<MLNShape *> *)shapes {
|
||||||
MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count);
|
MLNLogDebug(@"Initializing with %lu shapes.", (unsigned long)shapes.count);
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_shapes = shapes.copy;
|
_shapes = shapes.copy;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
if (self = [super initWithCoder:decoder]) {
|
if (self = [super initWithCoder:decoder]) {
|
||||||
NSSet<Class> *shapeClasses = [NSSet setWithArray:@[[NSDictionary class], [NSArray class], [MLNShape class]]];
|
NSSet<Class> *shapeClasses =
|
||||||
_shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"];
|
[NSSet setWithArray:@[ [NSDictionary class], [NSArray class], [MLNShape class] ]];
|
||||||
}
|
_shapes = [decoder decodeObjectOfClasses:shapeClasses forKey:@"shapes"];
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
[super encodeWithCoder:coder];
|
[super encodeWithCoder:coder];
|
||||||
[coder encodeObject:_shapes forKey:@"shapes"];
|
[coder encodeObject:_shapes forKey:@"shapes"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (self == other) return YES;
|
if (self == other) return YES;
|
||||||
if (![other isKindOfClass:[MLNShapeCollection class]]) return NO;
|
if (![other isKindOfClass:[MLNShapeCollection class]]) return NO;
|
||||||
|
|
||||||
MLNShapeCollection *otherShapeCollection = other;
|
MLNShapeCollection *otherShapeCollection = other;
|
||||||
return [super isEqual:otherShapeCollection]
|
return
|
||||||
&& [_shapes isEqualToArray:otherShapeCollection.shapes];
|
[super isEqual:otherShapeCollection] && [_shapes isEqualToArray:otherShapeCollection.shapes];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
NSUInteger hash = [super hash];
|
NSUInteger hash = [super hash];
|
||||||
for (MLNShape *shape in _shapes) {
|
for (MLNShape *shape in _shapes) {
|
||||||
hash += [shape hash];
|
hash += [shape hash];
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)coordinate {
|
- (CLLocationCoordinate2D)coordinate {
|
||||||
return _shapes.firstObject.coordinate;
|
return _shapes.firstObject.coordinate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDictionary *)geoJSONDictionary {
|
- (NSDictionary *)geoJSONDictionary {
|
||||||
return @{@"type": @"GeometryCollection",
|
return @{@"type" : @"GeometryCollection", @"geometries" : [self geometryCollection]};
|
||||||
@"geometries": [self geometryCollection]};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray *)geometryCollection {
|
- (NSArray *)geometryCollection {
|
||||||
NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count];
|
NSMutableArray *geometries = [[NSMutableArray alloc] initWithCapacity:self.shapes.count];
|
||||||
for (id shape in self.shapes) {
|
for (id shape in self.shapes) {
|
||||||
NSDictionary *geometry = [shape geoJSONDictionary];
|
NSDictionary *geometry = [shape geoJSONDictionary];
|
||||||
[geometries addObject:geometry];
|
[geometries addObject:geometry];
|
||||||
}
|
}
|
||||||
return [geometries copy];
|
return [geometries copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Geometry<double>)geometryObject {
|
- (mbgl::Geometry<double>)geometryObject {
|
||||||
mapbox::geojson::geometry_collection collection;
|
mapbox::geojson::geometry_collection collection;
|
||||||
collection.reserve(self.shapes.count);
|
collection.reserve(self.shapes.count);
|
||||||
for (MLNShape *shape in self.shapes) {
|
for (MLNShape *shape in self.shapes) {
|
||||||
collection.push_back([shape geometryObject]);
|
collection.push_back([shape geometryObject]);
|
||||||
}
|
}
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,139 +1,151 @@
|
|||||||
#import "MLNShapeOfflineRegion.h"
|
#import "MLNShapeOfflineRegion.h"
|
||||||
|
|
||||||
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
|
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#else
|
#else
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#import "MLNFeature_Private.h"
|
||||||
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNOfflineRegion_Private.h"
|
#import "MLNOfflineRegion_Private.h"
|
||||||
#import "MLNShapeOfflineRegion_Private.h"
|
#import "MLNShapeOfflineRegion_Private.h"
|
||||||
#import "MLNFeature_Private.h"
|
|
||||||
#import "MLNShape_Private.h"
|
#import "MLNShape_Private.h"
|
||||||
#import "MLNStyle.h"
|
#import "MLNStyle.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
|
||||||
|
|
||||||
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
|
@interface MLNShapeOfflineRegion () <MLNOfflineRegion_Private, MLNShapeOfflineRegion_Private>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNShapeOfflineRegion {
|
@implementation MLNShapeOfflineRegion {
|
||||||
NSURL *_styleURL;
|
NSURL *_styleURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize styleURL = _styleURL;
|
@synthesize styleURL = _styleURL;
|
||||||
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
||||||
|
|
||||||
-(NSDictionary *)offlineStartEventAttributes {
|
- (NSDictionary *)offlineStartEventAttributes {
|
||||||
return @{};
|
return @{};
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
MLNLogInfo(@"Calling this initializer is not allowed.");
|
MLNLogInfo(@"Calling this initializer is not allowed.");
|
||||||
[NSException raise:@"Method unavailable"
|
[NSException raise:@"Method unavailable"
|
||||||
format:
|
format:@"-[MLNShapeOfflineRegion init] is unavailable. "
|
||||||
@"-[MLNShapeOfflineRegion init] is unavailable. "
|
@"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."];
|
||||||
@"Use -initWithStyleURL:shape:fromZoomLevel:toZoomLevel: instead."];
|
return nil;
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithStyleURL:(NSURL *)styleURL shape:(MLNShape *)shape fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel {
|
- (instancetype)initWithStyleURL:(NSURL *)styleURL
|
||||||
MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, shape, minimumZoomLevel, maximumZoomLevel);
|
shape:(MLNShape *)shape
|
||||||
if (self = [super init]) {
|
fromZoomLevel:(double)minimumZoomLevel
|
||||||
if (!styleURL) {
|
toZoomLevel:(double)maximumZoomLevel {
|
||||||
styleURL = [MLNStyle defaultStyleURL];
|
MLNLogDebug(@"Initializing styleURL: %@ shape: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL,
|
||||||
}
|
shape, minimumZoomLevel, maximumZoomLevel);
|
||||||
|
if (self = [super init]) {
|
||||||
if (!styleURL.scheme) {
|
if (!styleURL) {
|
||||||
[NSException raise:@"Invalid style URL" format:
|
styleURL = [MLNStyle defaultStyleURL];
|
||||||
@"%@ does not support setting a relative file URL as the style URL. "
|
|
||||||
@"To download the online resources required by this style, "
|
|
||||||
@"specify a URL to an online copy of this style. "
|
|
||||||
@"For Mapbox-hosted styles, use the mapbox: scheme.",
|
|
||||||
NSStringFromClass([self class])];
|
|
||||||
}
|
|
||||||
|
|
||||||
_styleURL = styleURL;
|
|
||||||
_shape = shape;
|
|
||||||
_minimumZoomLevel = minimumZoomLevel;
|
|
||||||
_maximumZoomLevel = maximumZoomLevel;
|
|
||||||
_includesIdeographicGlyphs = NO;
|
|
||||||
}
|
}
|
||||||
return self;
|
|
||||||
|
if (!styleURL.scheme) {
|
||||||
|
[NSException raise:@"Invalid style URL"
|
||||||
|
format:@"%@ does not support setting a relative file URL as the style URL. "
|
||||||
|
@"To download the online resources required by this style, "
|
||||||
|
@"specify a URL to an online copy of this style. "
|
||||||
|
@"For Mapbox-hosted styles, use the mapbox: scheme.",
|
||||||
|
NSStringFromClass([self class])];
|
||||||
|
}
|
||||||
|
|
||||||
|
_styleURL = styleURL;
|
||||||
|
_shape = shape;
|
||||||
|
_minimumZoomLevel = minimumZoomLevel;
|
||||||
|
_maximumZoomLevel = maximumZoomLevel;
|
||||||
|
_includesIdeographicGlyphs = NO;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineGeometryRegionDefinition &)definition {
|
- (instancetype)initWithOfflineRegionDefinition:
|
||||||
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
|
(const mbgl::OfflineGeometryRegionDefinition &)definition {
|
||||||
MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry);
|
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
|
||||||
MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom];
|
MLNShape *shape = MLNShapeFromGeoJSON(definition.geometry);
|
||||||
result.includesIdeographicGlyphs = definition.includeIdeographs;
|
MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL
|
||||||
return result;
|
shape:shape
|
||||||
|
fromZoomLevel:definition.minZoom
|
||||||
|
toZoomLevel:definition.maxZoom];
|
||||||
|
result.includesIdeographicGlyphs = definition.includeIdeographs;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
|
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale];
|
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)]
|
||||||
|
? [[UIScreen mainScreen] nativeScale]
|
||||||
|
: [[UIScreen mainScreen] scale];
|
||||||
#elif TARGET_OS_MAC
|
#elif TARGET_OS_MAC
|
||||||
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
||||||
#endif
|
#endif
|
||||||
return mbgl::OfflineGeometryRegionDefinition(_styleURL.absoluteString.UTF8String,
|
return mbgl::OfflineGeometryRegionDefinition(
|
||||||
_shape.geometryObject,
|
_styleURL.absoluteString.UTF8String, _shape.geometryObject, _minimumZoomLevel,
|
||||||
_minimumZoomLevel, _maximumZoomLevel,
|
_maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs);
|
||||||
scaleFactor, _includesIdeographicGlyphs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
|
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
|
||||||
MLNShape * shape = [coder decodeObjectForKey:@"shape"];
|
MLNShape *shape = [coder decodeObjectForKey:@"shape"];
|
||||||
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
|
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
|
||||||
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
|
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
|
||||||
|
|
||||||
MLNShapeOfflineRegion* result = [self initWithStyleURL:styleURL shape:shape fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel];
|
MLNShapeOfflineRegion *result = [self initWithStyleURL:styleURL
|
||||||
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
|
shape:shape
|
||||||
return result;
|
fromZoomLevel:minimumZoomLevel
|
||||||
|
toZoomLevel:maximumZoomLevel];
|
||||||
|
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
{
|
[coder encodeObject:_styleURL forKey:@"styleURL"];
|
||||||
[coder encodeObject:_styleURL forKey:@"styleURL"];
|
[coder encodeObject:_shape forKey:@"shape"];
|
||||||
[coder encodeObject:_shape forKey:@"shape"];
|
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
|
||||||
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
|
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
|
||||||
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
|
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
|
||||||
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)copyWithZone:(nullable NSZone *)zone {
|
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||||
MLNShapeOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL shape:_shape fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel];
|
MLNShapeOfflineRegion *result =
|
||||||
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
[[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
|
||||||
return result;
|
shape:_shape
|
||||||
|
fromZoomLevel:_minimumZoomLevel
|
||||||
|
toZoomLevel:_maximumZoomLevel];
|
||||||
|
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (other == self) {
|
if (other == self) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
if (![other isKindOfClass:[self class]]) {
|
if (![other isKindOfClass:[self class]]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNShapeOfflineRegion *otherRegion = other;
|
MLNShapeOfflineRegion *otherRegion = other;
|
||||||
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel
|
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel &&
|
||||||
&& _maximumZoomLevel == otherRegion->_maximumZoomLevel
|
_maximumZoomLevel == otherRegion->_maximumZoomLevel &&
|
||||||
&& _shape.geometryObject == otherRegion->_shape.geometryObject
|
_shape.geometryObject == otherRegion->_shape.geometryObject &&
|
||||||
&& [_styleURL isEqual:otherRegion->_styleURL]
|
[_styleURL isEqual:otherRegion->_styleURL] &&
|
||||||
&& _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
|
_includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
return (_styleURL.hash
|
return (_styleURL.hash + _shape.hash + @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash +
|
||||||
+ _shape.hash
|
@(_includesIdeographicGlyphs).hash);
|
||||||
+ @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash
|
|
||||||
+ @(_includesIdeographicGlyphs).hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,169 +1,188 @@
|
|||||||
#import "MLNFoundation_Private.h"
|
#import "MLNFoundation_Private.h"
|
||||||
#import "MLNShapeSource_Private.h"
|
#import "MLNShapeSource_Private.h"
|
||||||
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
|
||||||
#import "MLNStyle_Private.h"
|
|
||||||
#import "MLNStyleValue_Private.h"
|
|
||||||
#import "MLNMapView_Private.h"
|
|
||||||
#import "MLNSource_Private.h"
|
|
||||||
#import "MLNFeature_Private.h"
|
|
||||||
#import "MLNShape_Private.h"
|
|
||||||
#import "MLNCluster.h"
|
#import "MLNCluster.h"
|
||||||
|
#import "MLNFeature_Private.h"
|
||||||
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNMapView_Private.h"
|
||||||
|
#import "MLNShape_Private.h"
|
||||||
|
#import "MLNSource_Private.h"
|
||||||
|
#import "MLNStyleValue_Private.h"
|
||||||
|
#import "MLNStyle_Private.h"
|
||||||
|
|
||||||
#import "NSPredicate+MLNPrivateAdditions.h"
|
#import "NSPredicate+MLNPrivateAdditions.h"
|
||||||
#import "NSURL+MLNAdditions.h"
|
#import "NSURL+MLNAdditions.h"
|
||||||
|
|
||||||
#include <mbgl/map/map.hpp>
|
#include <mbgl/map/map.hpp>
|
||||||
#include <mbgl/style/sources/geojson_source.hpp>
|
|
||||||
#include <mbgl/renderer/renderer.hpp>
|
#include <mbgl/renderer/renderer.hpp>
|
||||||
|
#include <mbgl/style/sources/geojson_source.hpp>
|
||||||
|
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer";
|
const MLNShapeSourceOption MLNShapeSourceOptionBuffer = @"MLNShapeSourceOptionBuffer";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius";
|
const MLNShapeSourceOption MLNShapeSourceOptionClusterRadius = @"MLNShapeSourceOptionClusterRadius";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints = @"MLNShapeSourceOptionClusterMinPoints";
|
const MLNShapeSourceOption MLNShapeSourceOptionClusterMinPoints =
|
||||||
|
@"MLNShapeSourceOptionClusterMinPoints";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered";
|
const MLNShapeSourceOption MLNShapeSourceOptionClustered = @"MLNShapeSourceOptionClustered";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties = @"MLNShapeSourceOptionClusterProperties";
|
const MLNShapeSourceOption MLNShapeSourceOptionClusterProperties =
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel = @"MLNShapeSourceOptionMaximumZoomLevel";
|
@"MLNShapeSourceOptionClusterProperties";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering = @"MLNShapeSourceOptionMaximumZoomLevelForClustering";
|
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevel =
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel = @"MLNShapeSourceOptionMinimumZoomLevel";
|
@"MLNShapeSourceOptionMaximumZoomLevel";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance = @"MLNShapeSourceOptionSimplificationTolerance";
|
const MLNShapeSourceOption MLNShapeSourceOptionMaximumZoomLevelForClustering =
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics = @"MLNShapeSourceOptionLineDistanceMetrics";
|
@"MLNShapeSourceOptionMaximumZoomLevelForClustering";
|
||||||
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate = @"MLNShapeSourceOptionSynchronousUpdate";
|
const MLNShapeSourceOption MLNShapeSourceOptionMinimumZoomLevel =
|
||||||
|
@"MLNShapeSourceOptionMinimumZoomLevel";
|
||||||
|
const MLNShapeSourceOption MLNShapeSourceOptionSimplificationTolerance =
|
||||||
|
@"MLNShapeSourceOptionSimplificationTolerance";
|
||||||
|
const MLNShapeSourceOption MLNShapeSourceOptionLineDistanceMetrics =
|
||||||
|
@"MLNShapeSourceOptionLineDistanceMetrics";
|
||||||
|
const MLNShapeSourceOption MLNShapeSourceOptionSynchronousUpdate =
|
||||||
|
@"MLNShapeSourceOptionSynchronousUpdate";
|
||||||
|
|
||||||
mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSDictionary<MLNShapeSourceOption, id> *options) {
|
mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(
|
||||||
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
|
NSDictionary<MLNShapeSourceOption, id> *options) {
|
||||||
|
auto geoJSONOptions = mbgl::makeMutable<mbgl::style::GeoJSONOptions>();
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionMinimumZoomLevel]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
||||||
}
|
}
|
||||||
geoJSONOptions->minzoom = value.integerValue;
|
geoJSONOptions->minzoom = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->maxzoom = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->buffer = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->tolerance = value.doubleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->clusterRadius = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->clusterMinPoints = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->clusterMaxZoom = value.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
|
||||||
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
|
||||||
|
}
|
||||||
|
geoJSONOptions->cluster = value.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
|
||||||
|
if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
|
||||||
|
[NSException
|
||||||
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString "
|
||||||
|
@"as a key and an array containing two NSExpression objects as a value."];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevel]) {
|
NSEnumerator *stringEnumerator = [value keyEnumerator];
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
NSString *key;
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionMaximumZoomLevel must be an NSNumber."];
|
while (key = [stringEnumerator nextObject]) {
|
||||||
}
|
NSArray *expressionsArray = value[key];
|
||||||
geoJSONOptions->maxzoom = value.integerValue;
|
if (![expressionsArray isKindOfClass:[NSArray class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be "
|
||||||
|
@"an array containing two objects."];
|
||||||
|
}
|
||||||
|
// Check that the array has 2 values. One should be a the reduce expression and one should be
|
||||||
|
// the map expression.
|
||||||
|
if ([expressionsArray count] != 2) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterProperties member value requires array of "
|
||||||
|
@"two objects."];
|
||||||
|
}
|
||||||
|
|
||||||
|
// reduceExpression should be a valid NSExpression
|
||||||
|
NSExpression *reduceExpression = expressionsArray[0];
|
||||||
|
if (![reduceExpression isKindOfClass:[NSExpression class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterProperties array value requires two "
|
||||||
|
@"expression objects."];
|
||||||
|
}
|
||||||
|
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
|
||||||
|
if (!reduce) {
|
||||||
|
[NSException
|
||||||
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapExpression should be a valid NSExpression
|
||||||
|
NSExpression *mapExpression = expressionsArray[1];
|
||||||
|
if (![mapExpression isKindOfClass:[NSExpression class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNShapeSourceOptionClusterProperties member value must contain a "
|
||||||
|
@"valid NSExpression."];
|
||||||
|
}
|
||||||
|
auto map = MLNClusterPropertyFromNSExpression(mapExpression);
|
||||||
|
if (!map) {
|
||||||
|
[NSException
|
||||||
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string keyString = std::string([key UTF8String]);
|
||||||
|
|
||||||
|
geoJSONOptions->clusterProperties.emplace(keyString,
|
||||||
|
std::make_pair(std::move(map), std::move(reduce)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionBuffer]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionBuffer must be an NSNumber."];
|
format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."];
|
||||||
}
|
|
||||||
geoJSONOptions->buffer = value.integerValue;
|
|
||||||
}
|
}
|
||||||
|
geoJSONOptions->lineMetrics = value.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionSimplificationTolerance]) {
|
if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) {
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
if (![value isKindOfClass:[NSNumber class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNShapeSourceOptionSimplificationTolerance must be an NSNumber."];
|
format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."];
|
||||||
}
|
|
||||||
geoJSONOptions->tolerance = value.doubleValue;
|
|
||||||
}
|
}
|
||||||
|
geoJSONOptions->synchronousUpdate = value.boolValue;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionClusterRadius]) {
|
return geoJSONOptions;
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterRadius must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->clusterRadius = value.integerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionClusterMinPoints]) {
|
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterMinPoints must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->clusterMinPoints = value.integerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionMaximumZoomLevelForClustering]) {
|
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->clusterMaxZoom = value.integerValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionClustered]) {
|
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClustered must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->cluster = value.boolValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSDictionary *value = options[MLNShapeSourceOptionClusterProperties]) {
|
|
||||||
if (![value isKindOfClass:[NSDictionary<NSString *, NSArray *> class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterProperties must be an NSDictionary with an NSString as a key and an array containing two NSExpression objects as a value."];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSEnumerator *stringEnumerator = [value keyEnumerator];
|
|
||||||
NSString *key;
|
|
||||||
|
|
||||||
while (key = [stringEnumerator nextObject]) {
|
|
||||||
NSArray *expressionsArray = value[key];
|
|
||||||
if (![expressionsArray isKindOfClass:[NSArray class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterProperties dictionary member value must be an array containing two objects."];
|
|
||||||
}
|
|
||||||
// Check that the array has 2 values. One should be a the reduce expression and one should be the map expression.
|
|
||||||
if ([expressionsArray count] != 2) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterProperties member value requires array of two objects."];
|
|
||||||
}
|
|
||||||
|
|
||||||
// reduceExpression should be a valid NSExpression
|
|
||||||
NSExpression *reduceExpression = expressionsArray[0];
|
|
||||||
if (![reduceExpression isKindOfClass:[NSExpression class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterProperties array value requires two expression objects."];
|
|
||||||
}
|
|
||||||
auto reduce = MLNClusterPropertyFromNSExpression(reduceExpression);
|
|
||||||
if (!reduce) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"Failed to convert MLNShapeSourceOptionClusterProperties reduce expression."];
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapExpression should be a valid NSExpression
|
|
||||||
NSExpression *mapExpression = expressionsArray[1];
|
|
||||||
if (![mapExpression isKindOfClass:[NSExpression class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionClusterProperties member value must contain a valid NSExpression."];
|
|
||||||
}
|
|
||||||
auto map = MLNClusterPropertyFromNSExpression(mapExpression);
|
|
||||||
if (!map) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"Failed to convert MLNShapeSourceOptionClusterProperties map expression."];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string keyString = std::string([key UTF8String]);
|
|
||||||
|
|
||||||
geoJSONOptions->clusterProperties.emplace(keyString, std::make_pair(std::move(map), std::move(reduce)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionLineDistanceMetrics]) {
|
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionLineDistanceMetrics must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->lineMetrics = value.boolValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSNumber *value = options[MLNShapeSourceOptionSynchronousUpdate]) {
|
|
||||||
if (![value isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNShapeSourceOptionSynchronousUpdate must be an NSNumber."];
|
|
||||||
}
|
|
||||||
geoJSONOptions->synchronousUpdate = value.boolValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return geoJSONOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface MLNShapeSource ()
|
@interface MLNShapeSource ()
|
||||||
@@ -175,203 +194,225 @@ mbgl::Immutable<mbgl::style::GeoJSONOptions> MLNGeoJSONOptionsFromDictionary(NSD
|
|||||||
|
|
||||||
@implementation MLNShapeSource
|
@implementation MLNShapeSource
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier URL:(NSURL *)url options:(NSDictionary<NSString *, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
|
URL:(NSURL *)url
|
||||||
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions));
|
options:(NSDictionary<NSString *, id> *)options {
|
||||||
if (self = [super initWithPendingSource:std::move(source)]) {
|
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
|
||||||
self.URL = url;
|
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
|
||||||
}
|
std::move(geoJSONOptions));
|
||||||
return self;
|
if (self = [super initWithPendingSource:std::move(source)]) {
|
||||||
|
self.URL = url;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier shape:(nullable MLNShape *)shape options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
|
shape:(nullable MLNShape *)shape
|
||||||
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String, std::move(geoJSONOptions));
|
options:(NSDictionary<MLNShapeSourceOption, id> *)options {
|
||||||
if (self = [super initWithPendingSource:std::move(source)]) {
|
auto geoJSONOptions = MLNGeoJSONOptionsFromDictionary(options);
|
||||||
if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
|
auto source = std::make_unique<mbgl::style::GeoJSONSource>(identifier.UTF8String,
|
||||||
static dispatch_once_t onceToken;
|
std::move(geoJSONOptions));
|
||||||
dispatch_once(&onceToken, ^{
|
if (self = [super initWithPendingSource:std::move(source)]) {
|
||||||
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
if ([shape isMemberOfClass:[MLNShapeCollection class]]) {
|
||||||
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
static dispatch_once_t onceToken;
|
||||||
@"This will be logged only once.");
|
dispatch_once(&onceToken, ^{
|
||||||
});
|
NSLog(@"MLNShapeCollection initialized with MLNFeatures will not retain attributes."
|
||||||
}
|
@"Use MLNShapeCollectionFeature to retain attributes instead."
|
||||||
self.shape = shape;
|
@"This will be logged only once.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return self;
|
self.shape = shape;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier features:(NSArray<MLNShape<MLNFeature> *> *)features options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
for (id <MLNFeature> feature in features) {
|
features:(NSArray<MLNShape<MLNFeature> *> *)features
|
||||||
if (![feature conformsToProtocol:@protocol(MLNFeature)]) {
|
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
|
||||||
[NSException raise:NSInvalidArgumentException format:@"The object %@ included in the features argument does not conform to the MLNFeature protocol.", feature];
|
for (id<MLNFeature> feature in features) {
|
||||||
}
|
if (![feature conformsToProtocol:@protocol(MLNFeature)]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"The object %@ included in the features argument does not conform to the "
|
||||||
|
@"MLNFeature protocol.",
|
||||||
|
feature];
|
||||||
}
|
}
|
||||||
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:features];
|
}
|
||||||
return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options];
|
MLNShapeCollectionFeature *shapeCollectionFeature =
|
||||||
|
[MLNShapeCollectionFeature shapeCollectionWithShapes:features];
|
||||||
|
return [self initWithIdentifier:identifier shape:shapeCollectionFeature options:options];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier shapes:(NSArray<MLNShape *> *)shapes options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
|
shapes:(NSArray<MLNShape *> *)shapes
|
||||||
return [self initWithIdentifier:identifier shape:shapeCollection options:options];
|
options:(nullable NSDictionary<MLNShapeSourceOption, id> *)options {
|
||||||
|
MLNShapeCollection *shapeCollection = [MLNShapeCollection shapeCollectionWithShapes:shapes];
|
||||||
|
return [self initWithIdentifier:identifier shape:shapeCollection options:options];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::GeoJSONSource *)rawSource {
|
- (mbgl::style::GeoJSONSource *)rawSource {
|
||||||
return (mbgl::style::GeoJSONSource *)super.rawSource;
|
return (mbgl::style::GeoJSONSource *)super.rawSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL *)URL {
|
- (NSURL *)URL {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
auto url = self.rawSource->getURL();
|
auto url = self.rawSource->getURL();
|
||||||
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setURL:(NSURL *)url {
|
- (void)setURL:(NSURL *)url {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
if (url) {
|
if (url) {
|
||||||
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
||||||
_shape = nil;
|
_shape = nil;
|
||||||
} else {
|
} else {
|
||||||
self.shape = nil;
|
self.shape = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setShape:(MLNShape *)shape {
|
- (void)setShape:(MLNShape *)shape {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
self.rawSource->setGeoJSON({ shape.geoJSONObject });
|
self.rawSource->setGeoJSON({shape.geoJSONObject});
|
||||||
_shape = shape;
|
_shape = shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
if (self.rawSource) {
|
if (self.rawSource) {
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>",
|
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = %@; shape = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier, self.URL, self.shape];
|
NSStringFromClass([self class]), (void *)self,
|
||||||
}
|
self.identifier, self.URL, self.shape];
|
||||||
else {
|
} else {
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>",
|
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; URL = <unknown>; shape = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier, self.shape];
|
NSStringFromClass([self class]), (void *)self,
|
||||||
}
|
self.identifier, self.shape];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id <MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate {
|
- (NSArray<id<MLNFeature>> *)featuresMatchingPredicate:(nullable NSPredicate *)predicate {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
std::optional<mbgl::style::Filter> optionalFilter;
|
std::optional<mbgl::style::Filter> optionalFilter;
|
||||||
if (predicate) {
|
if (predicate) {
|
||||||
optionalFilter = predicate.mgl_filter;
|
optionalFilter = predicate.mgl_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<mbgl::Feature> features;
|
std::vector<mbgl::Feature> features;
|
||||||
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
||||||
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
||||||
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter });
|
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), {{}, optionalFilter});
|
||||||
}
|
}
|
||||||
return MLNFeaturesFromMBGLFeatures(features);
|
return MLNFeaturesFromMBGLFeatures(features);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - MLNCluster management
|
// MARK: - MLNCluster management
|
||||||
|
|
||||||
- (std::optional<mbgl::FeatureExtensionValue>)featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster extension:(std::string)extension options:(const std::map<std::string, mbgl::Value>)options {
|
- (std::optional<mbgl::FeatureExtensionValue>)
|
||||||
MLNAssertStyleSourceIsValid();
|
featureExtensionValueOfCluster:(MLNShape<MLNCluster> *)cluster
|
||||||
std::optional<mbgl::FeatureExtensionValue> extensionValue;
|
extension:(std::string)extension
|
||||||
|
options:(const std::map<std::string, mbgl::Value>)options {
|
||||||
|
MLNAssertStyleSourceIsValid();
|
||||||
|
std::optional<mbgl::FeatureExtensionValue> extensionValue;
|
||||||
|
|
||||||
// Check parameters
|
// Check parameters
|
||||||
if (!self.rawSource || !self.stylable || !cluster) {
|
if (!self.rawSource || !self.stylable || !cluster) {
|
||||||
return extensionValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto geoJSON = [cluster geoJSONObject];
|
|
||||||
|
|
||||||
if (!geoJSON.is<mbgl::GeoJSONFeature>()) {
|
|
||||||
MLNAssert(0, @"cluster geoJSON object is not a feature.");
|
|
||||||
return extensionValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto clusterFeature = geoJSON.get<mbgl::GeoJSONFeature>();
|
|
||||||
|
|
||||||
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
|
||||||
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
|
||||||
extensionValue = mapView.renderer->queryFeatureExtensions(self.rawSource->getID(),
|
|
||||||
clusterFeature,
|
|
||||||
"supercluster",
|
|
||||||
extension,
|
|
||||||
options);
|
|
||||||
}
|
|
||||||
return extensionValue;
|
return extensionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto geoJSON = [cluster geoJSONObject];
|
||||||
|
|
||||||
|
if (!geoJSON.is<mbgl::GeoJSONFeature>()) {
|
||||||
|
MLNAssert(0, @"cluster geoJSON object is not a feature.");
|
||||||
|
return extensionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clusterFeature = geoJSON.get<mbgl::GeoJSONFeature>();
|
||||||
|
|
||||||
|
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
||||||
|
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
||||||
|
extensionValue = mapView.renderer->queryFeatureExtensions(
|
||||||
|
self.rawSource->getID(), clusterFeature, "supercluster", extension, options);
|
||||||
|
}
|
||||||
|
return extensionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id <MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster offset:(NSUInteger)offset limit:(NSUInteger)limit {
|
- (NSArray<id<MLNFeature>> *)leavesOfCluster:(MLNPointFeatureCluster *)cluster
|
||||||
const std::map<std::string, mbgl::Value> options = {
|
offset:(NSUInteger)offset
|
||||||
{ "limit", static_cast<uint64_t>(limit) },
|
limit:(NSUInteger)limit {
|
||||||
{ "offset", static_cast<uint64_t>(offset) }
|
const std::map<std::string, mbgl::Value> options = {{"limit", static_cast<uint64_t>(limit)},
|
||||||
};
|
{"offset", static_cast<uint64_t>(offset)}};
|
||||||
|
|
||||||
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"leaves" options:options];
|
auto featureExtension = [self featureExtensionValueOfCluster:cluster
|
||||||
|
extension:"leaves"
|
||||||
|
options:options];
|
||||||
|
|
||||||
if (!featureExtension) {
|
if (!featureExtension) {
|
||||||
return @[];
|
return @[];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!featureExtension->is<mbgl::FeatureCollection>()) {
|
if (!featureExtension->is<mbgl::FeatureCollection>()) {
|
||||||
return @[];
|
return @[];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
|
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
|
||||||
return MLNFeaturesFromMBGLFeatures(leaves);
|
return MLNFeaturesFromMBGLFeatures(leaves);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id <MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster {
|
- (NSArray<id<MLNFeature>> *)childrenOfCluster:(MLNPointFeatureCluster *)cluster {
|
||||||
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"children" options:{}];
|
auto featureExtension = [self featureExtensionValueOfCluster:cluster
|
||||||
|
extension:"children"
|
||||||
|
options:{}];
|
||||||
|
|
||||||
if (!featureExtension) {
|
if (!featureExtension) {
|
||||||
return @[];
|
return @[];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!featureExtension->is<mbgl::FeatureCollection>()) {
|
if (!featureExtension->is<mbgl::FeatureCollection>()) {
|
||||||
return @[];
|
return @[];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
|
std::vector<mbgl::GeoJSONFeature> leaves = featureExtension->get<mbgl::FeatureCollection>();
|
||||||
return MLNFeaturesFromMBGLFeatures(leaves);
|
return MLNFeaturesFromMBGLFeatures(leaves);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)cluster {
|
- (double)zoomLevelForExpandingCluster:(MLNPointFeatureCluster *)cluster {
|
||||||
auto featureExtension = [self featureExtensionValueOfCluster:cluster extension:"expansion-zoom" options:{}];
|
auto featureExtension = [self featureExtensionValueOfCluster:cluster
|
||||||
|
extension:"expansion-zoom"
|
||||||
if (!featureExtension) {
|
options:{}];
|
||||||
return -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!featureExtension->is<mbgl::Value>()) {
|
|
||||||
return -1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto value = featureExtension->get<mbgl::Value>();
|
|
||||||
if (value.is<uint64_t>()) {
|
|
||||||
auto zoom = value.get<uint64_t>();
|
|
||||||
return static_cast<double>(zoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!featureExtension) {
|
||||||
return -1.0;
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!featureExtension->is<mbgl::Value>()) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto value = featureExtension->get<mbgl::Value>();
|
||||||
|
if (value.is<uint64_t>()) {
|
||||||
|
auto zoom = value.get<uint64_t>();
|
||||||
|
return static_cast<double>(zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)debugRecursiveLogForFeature:(id <MLNFeature>)feature indent:(NSUInteger)indent {
|
- (void)debugRecursiveLogForFeature:(id<MLNFeature>)feature indent:(NSUInteger)indent {
|
||||||
NSString *description = feature.description;
|
NSString *description = feature.description;
|
||||||
|
|
||||||
// Want our recursive log on a single line
|
// Want our recursive log on a single line
|
||||||
NSString *log = [description stringByReplacingOccurrencesOfString:@"\\s+"
|
NSString *log =
|
||||||
withString:@" "
|
[description stringByReplacingOccurrencesOfString:@"\\s+"
|
||||||
options:NSRegularExpressionSearch
|
withString:@" "
|
||||||
range:NSMakeRange(0, description.length)];
|
options:NSRegularExpressionSearch
|
||||||
|
range:NSMakeRange(0, description.length)];
|
||||||
|
|
||||||
printf("%*s%s\n", (int)indent, "", log.UTF8String);
|
printf("%*s%s\n", (int)indent, "", log.UTF8String);
|
||||||
|
|
||||||
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
|
MLNPointFeatureCluster *cluster = MLN_OBJC_DYNAMIC_CAST(feature, MLNPointFeatureCluster);
|
||||||
|
|
||||||
if (cluster) {
|
if (cluster) {
|
||||||
for (id <MLNFeature> child in [self childrenOfCluster:cluster]) {
|
for (id<MLNFeature> child in [self childrenOfCluster:cluster]) {
|
||||||
[self debugRecursiveLogForFeature:child indent:indent + 4];
|
[self debugRecursiveLogForFeature:child indent:indent + 4];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
#import "MLNMapView_Private.h"
|
||||||
#import "MLNSource_Private.h"
|
#import "MLNSource_Private.h"
|
||||||
#import "MLNStyle_Private.h"
|
#import "MLNStyle_Private.h"
|
||||||
#import "MLNMapView_Private.h"
|
|
||||||
#import "NSBundle+MLNAdditions.h"
|
#import "NSBundle+MLNAdditions.h"
|
||||||
|
|
||||||
#include <mbgl/style/style.hpp>
|
|
||||||
#include <mbgl/map/map.hpp>
|
#include <mbgl/map/map.hpp>
|
||||||
#include <mbgl/style/source.hpp>
|
#include <mbgl/style/source.hpp>
|
||||||
|
#include <mbgl/style/style.hpp>
|
||||||
|
|
||||||
const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException";
|
const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceException";
|
||||||
|
|
||||||
@@ -15,95 +15,102 @@ const MLNExceptionName MLNInvalidStyleSourceException = @"MLNInvalidStyleSourceE
|
|||||||
// special internal source types like mbgl::AnnotationSource.
|
// special internal source types like mbgl::AnnotationSource.
|
||||||
@property (nonatomic, readonly) mbgl::style::Source *rawSource;
|
@property (nonatomic, readonly) mbgl::style::Source *rawSource;
|
||||||
|
|
||||||
@property (nonatomic, readonly, weak) id <MLNStylable> stylable;
|
@property (nonatomic, readonly, weak) id<MLNStylable> stylable;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNSource {
|
@implementation MLNSource {
|
||||||
std::unique_ptr<mbgl::style::Source> _pendingSource;
|
std::unique_ptr<mbgl::style::Source> _pendingSource;
|
||||||
mapbox::base::WeakPtr<mbgl::style::Source> _weakSource;
|
mapbox::base::WeakPtr<mbgl::style::Source> _weakSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithIdentifier:(NSString *)identifier {
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
if (self = [super init]) {
|
||||||
{
|
_identifier = [identifier copy];
|
||||||
if (self = [super init]) {
|
}
|
||||||
_identifier = [identifier copy];
|
return self;
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource stylable:(id <MLNStylable>)stylable {
|
- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource
|
||||||
NSString *identifier = @(rawSource->getID().c_str());
|
stylable:(id<MLNStylable>)stylable {
|
||||||
if (self = [self initWithIdentifier:identifier]) {
|
NSString *identifier = @(rawSource->getID().c_str());
|
||||||
_weakSource = rawSource->makeWeakPtr();
|
if (self = [self initWithIdentifier:identifier]) {
|
||||||
rawSource->peer = SourceWrapper { self };
|
_weakSource = rawSource->makeWeakPtr();
|
||||||
_stylable = stylable;
|
rawSource->peer = SourceWrapper{self};
|
||||||
}
|
_stylable = stylable;
|
||||||
return self;
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::Source *)rawSource
|
- (mbgl::style::Source *)rawSource {
|
||||||
{
|
return _weakSource.get();
|
||||||
return _weakSource.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource {
|
- (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource {
|
||||||
if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) {
|
if (self = [self initWithRawSource:pendingSource.get() stylable:nil]) {
|
||||||
_pendingSource = std::move(pendingSource);
|
_pendingSource = std::move(pendingSource);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addToStylable:(id <MLNStylable>)stylable {
|
- (void)addToStylable:(id<MLNStylable>)stylable {
|
||||||
if (_pendingSource == nullptr) {
|
if (_pendingSource == nullptr) {
|
||||||
[NSException raise:MLNRedundantSourceException
|
[NSException raise:MLNRedundantSourceException
|
||||||
format:@"This instance %@ was already added to %@. Adding the same source instance " \
|
format:@"This instance %@ was already added to %@. Adding the same source instance "
|
||||||
"to the style more than once is invalid.", self, stylable.style];
|
"to the style more than once is invalid.",
|
||||||
}
|
self, stylable.style];
|
||||||
|
}
|
||||||
|
|
||||||
_stylable = stylable;
|
_stylable = stylable;
|
||||||
_stylable.style.rawStyle->addSource(std::move(_pendingSource));
|
_stylable.style.rawStyle->addSource(std::move(_pendingSource));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)removeFromStylable:(id <MLNStylable>)mapView error:(NSError * __nullable * __nullable)outError {
|
- (BOOL)removeFromStylable:(id<MLNStylable>)mapView
|
||||||
MLNAssertStyleSourceIsValid();
|
error:(NSError *__nullable *__nullable)outError {
|
||||||
BOOL removed = NO;
|
MLNAssertStyleSourceIsValid();
|
||||||
|
BOOL removed = NO;
|
||||||
|
|
||||||
if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) {
|
if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) {
|
||||||
|
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String);
|
||||||
|
|
||||||
auto removedSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String);
|
if (removedSource) {
|
||||||
|
removed = YES;
|
||||||
if (removedSource) {
|
_pendingSource = std::move(removedSource);
|
||||||
removed = YES;
|
_stylable = nil;
|
||||||
_pendingSource = std::move(removedSource);
|
|
||||||
_stylable = nil;
|
|
||||||
} else if (outError) {
|
|
||||||
NSString *localizedDescription = [NSString stringWithFormat:
|
|
||||||
NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil, @"The source “%@” can’t be removed while it is in use.", @"User-friendly error description; first placeholder is the source’s identifier"),
|
|
||||||
self.identifier];
|
|
||||||
|
|
||||||
*outError = [NSError errorWithDomain:MLNErrorDomain
|
|
||||||
code:MLNErrorCodeSourceIsInUseCannotRemove
|
|
||||||
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }];
|
|
||||||
}
|
|
||||||
} else if (outError) {
|
} else if (outError) {
|
||||||
// TODO: Consider raising an exception here
|
NSString *localizedDescription = [NSString
|
||||||
NSString *localizedDescription = [NSString stringWithFormat:
|
stringWithFormat:
|
||||||
NSLocalizedStringWithDefaultValue(@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil, @"The source can’t be removed because its identifier, “%@”, belongs to a different source in this style.", @"User-friendly error description"),
|
NSLocalizedStringWithDefaultValue(
|
||||||
self.identifier];
|
@"REMOVE_SRC_FAIL_IN_USE_FMT", @"Foundation", nil,
|
||||||
|
@"The source “%@” can’t be removed while it is in use.",
|
||||||
|
@"User-friendly error description; first placeholder is the source’s identifier"),
|
||||||
|
self.identifier];
|
||||||
|
|
||||||
*outError = [NSError errorWithDomain:MLNErrorDomain
|
*outError = [NSError errorWithDomain:MLNErrorDomain
|
||||||
code:MLNErrorCodeSourceIdentifierMismatch
|
code:MLNErrorCodeSourceIsInUseCannotRemove
|
||||||
userInfo:@{ NSLocalizedDescriptionKey : localizedDescription }];
|
userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
|
||||||
}
|
}
|
||||||
|
} else if (outError) {
|
||||||
|
// TODO: Consider raising an exception here
|
||||||
|
NSString *localizedDescription =
|
||||||
|
[NSString stringWithFormat:NSLocalizedStringWithDefaultValue(
|
||||||
|
@"REMOVE_SRC_FAIL_MISMATCH_FMT", @"Foundation", nil,
|
||||||
|
@"The source can’t be removed because its identifier, “%@”, "
|
||||||
|
@"belongs to a different source in this style.",
|
||||||
|
@"User-friendly error description"),
|
||||||
|
self.identifier];
|
||||||
|
|
||||||
return removed;
|
*outError = [NSError errorWithDomain:MLNErrorDomain
|
||||||
|
code:MLNErrorCodeSourceIdentifierMismatch
|
||||||
|
userInfo:@{NSLocalizedDescriptionKey : localizedDescription}];
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@>",
|
return [NSString stringWithFormat:@"<%@: %p; identifier = %@>", NSStringFromClass([self class]),
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier];
|
(void *)self, self.identifier];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
#import "MLNStyleLayer_Private.h"
|
#import "MLNStyleLayer_Private.h"
|
||||||
#import "MLNStyle_Private.h"
|
#import "MLNStyle_Private.h"
|
||||||
|
|
||||||
#include <mbgl/style/style.hpp>
|
|
||||||
#include <mbgl/style/layer.hpp>
|
#include <mbgl/style/layer.hpp>
|
||||||
|
#include <mbgl/style/style.hpp>
|
||||||
|
|
||||||
const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerException";
|
const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerException";
|
||||||
|
|
||||||
@@ -13,111 +13,101 @@ const MLNExceptionName MLNInvalidStyleLayerException = @"MLNInvalidStyleLayerExc
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNStyleLayer {
|
@implementation MLNStyleLayer {
|
||||||
std::unique_ptr<mbgl::style::Layer> _pendingLayer;
|
std::unique_ptr<mbgl::style::Layer> _pendingLayer;
|
||||||
mapbox::base::WeakPtr<mbgl::style::Layer> _weakLayer;
|
mapbox::base::WeakPtr<mbgl::style::Layer> _weakLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer {
|
- (instancetype)initWithRawLayer:(mbgl::style::Layer *)rawLayer {
|
||||||
if (self = [super init]) {
|
if (self = [super init]) {
|
||||||
_identifier = @(rawLayer->getID().c_str());
|
_identifier = @(rawLayer->getID().c_str());
|
||||||
_weakLayer = rawLayer->makeWeakPtr();
|
_weakLayer = rawLayer->makeWeakPtr();
|
||||||
rawLayer->peer = LayerWrapper { self };
|
rawLayer->peer = LayerWrapper{self};
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
|
- (instancetype)initWithPendingLayer:(std::unique_ptr<mbgl::style::Layer>)pendingLayer {
|
||||||
if (self = [self initWithRawLayer:pendingLayer.get()]) {
|
if (self = [self initWithRawLayer:pendingLayer.get()]) {
|
||||||
_pendingLayer = std::move(pendingLayer);
|
_pendingLayer = std::move(pendingLayer);
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::Layer *)rawLayer
|
- (mbgl::style::Layer *)rawLayer {
|
||||||
{
|
return _weakLayer.get();
|
||||||
return _weakLayer.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer
|
- (void)addToStyle:(MLNStyle *)style belowLayer:(MLNStyleLayer *)otherLayer {
|
||||||
{
|
if (_pendingLayer == nullptr) {
|
||||||
if (_pendingLayer == nullptr) {
|
[NSException raise:MLNRedundantLayerException
|
||||||
[NSException raise:MLNRedundantLayerException
|
format:@"This instance %@ was already added to %@. Adding the same layer instance "
|
||||||
format:@"This instance %@ was already added to %@. Adding the same layer instance " \
|
"to the style more than once is invalid.",
|
||||||
"to the style more than once is invalid.", self, style];
|
self, style];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherLayer) {
|
if (otherLayer) {
|
||||||
const std::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String};
|
const std::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String};
|
||||||
style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId);
|
style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId);
|
||||||
} else {
|
} else {
|
||||||
style.rawStyle->addLayer(std::move(_pendingLayer));
|
style.rawStyle->addLayer(std::move(_pendingLayer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeFromStyle:(MLNStyle *)style
|
- (void)removeFromStyle:(MLNStyle *)style {
|
||||||
{
|
if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) {
|
||||||
if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) {
|
_pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String);
|
||||||
_pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setVisible:(BOOL)visible
|
- (void)setVisible:(BOOL)visible {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
mbgl::style::VisibilityType v = visible
|
mbgl::style::VisibilityType v =
|
||||||
? mbgl::style::VisibilityType::Visible
|
visible ? mbgl::style::VisibilityType::Visible : mbgl::style::VisibilityType::None;
|
||||||
: mbgl::style::VisibilityType::None;
|
self.rawLayer->setVisibility(v);
|
||||||
self.rawLayer->setVisibility(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isVisible
|
- (BOOL)isVisible {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
mbgl::style::VisibilityType v = self.rawLayer->getVisibility();
|
mbgl::style::VisibilityType v = self.rawLayer->getVisibility();
|
||||||
return (v == mbgl::style::VisibilityType::Visible);
|
return (v == mbgl::style::VisibilityType::Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setMaximumZoomLevel:(float)maximumZoomLevel
|
- (void)setMaximumZoomLevel:(float)maximumZoomLevel {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
self.rawLayer->setMaxZoom(maximumZoomLevel);
|
self.rawLayer->setMaxZoom(maximumZoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (float)maximumZoomLevel
|
- (float)maximumZoomLevel {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
return self.rawLayer->getMaxZoom();
|
return self.rawLayer->getMaxZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setMinimumZoomLevel:(float)minimumZoomLevel
|
- (void)setMinimumZoomLevel:(float)minimumZoomLevel {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
self.rawLayer->setMinZoom(minimumZoomLevel);
|
self.rawLayer->setMinZoom(minimumZoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (float)minimumZoomLevel
|
- (float)minimumZoomLevel {
|
||||||
{
|
MLNAssertStyleLayerIsValid();
|
||||||
MLNAssertStyleLayerIsValid();
|
|
||||||
|
|
||||||
return self.rawLayer->getMinZoom();
|
return self.rawLayer->getMinZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description
|
- (NSString *)description {
|
||||||
{
|
if (self.rawLayer) {
|
||||||
if (self.rawLayer) {
|
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>",
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>",
|
NSStringFromClass([self class]), (void *)self,
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier,
|
self.identifier, self.visible ? @"YES" : @"NO"];
|
||||||
self.visible ? @"YES" : @"NO"];
|
} else {
|
||||||
}
|
return
|
||||||
else {
|
[NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>",
|
||||||
return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = NO>",
|
NSStringFromClass([self class]), (void *)self, self.identifier];
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier];
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#import "MLNBackgroundStyleLayer_Private.h"
|
#import "MLNBackgroundStyleLayer_Private.h"
|
||||||
#import "MLNCircleStyleLayer_Private.h"
|
#import "MLNCircleStyleLayer_Private.h"
|
||||||
|
#import "MLNCustomStyleLayer_Private.h"
|
||||||
#import "MLNFillExtrusionStyleLayer_Private.h"
|
#import "MLNFillExtrusionStyleLayer_Private.h"
|
||||||
#import "MLNFillStyleLayer_Private.h"
|
#import "MLNFillStyleLayer_Private.h"
|
||||||
#import "MLNHeatmapStyleLayer_Private.h"
|
#import "MLNHeatmapStyleLayer_Private.h"
|
||||||
@@ -9,7 +10,6 @@
|
|||||||
#import "MLNLineStyleLayer_Private.h"
|
#import "MLNLineStyleLayer_Private.h"
|
||||||
#import "MLNRasterStyleLayer_Private.h"
|
#import "MLNRasterStyleLayer_Private.h"
|
||||||
#import "MLNSymbolStyleLayer_Private.h"
|
#import "MLNSymbolStyleLayer_Private.h"
|
||||||
#import "MLNCustomStyleLayer_Private.h"
|
|
||||||
|
|
||||||
#import "MLNCustomDrawableStyleLayer_Private.h"
|
#import "MLNCustomDrawableStyleLayer_Private.h"
|
||||||
|
|
||||||
@@ -19,138 +19,138 @@ namespace mbgl {
|
|||||||
|
|
||||||
LayerManagerDarwin::LayerManagerDarwin() {
|
LayerManagerDarwin::LayerManagerDarwin() {
|
||||||
#if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_FILL_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<FillLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<FillStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<FillStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_LINE_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_LINE_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<LineLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<LineLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_LINE_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_LINE_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<LineStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<LineStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_CIRCLE_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_CIRCLE_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<CircleLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<CircleLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_CIRCLE_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_CIRCLE_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<CircleStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<CircleStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_SYMBOL_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_SYMBOL_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<SymbolLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<SymbolLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_SYMBOL_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_SYMBOL_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<SymbolStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<SymbolStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_RASTER_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_RASTER_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<RasterLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<RasterLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_RASTER_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_RASTER_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<RasterStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<RasterStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_BACKGROUND_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_BACKGROUND_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<BackgroundLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<BackgroundLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_BACKGROUND_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_BACKGROUND_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<BackgroundStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<BackgroundStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_HILLSHADE_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_HILLSHADE_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<HillshadeLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<HillshadeLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<HillshadeStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<HillshadeStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<FillExtrusionLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<FillExtrusionLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<FillExtrusionStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<FillExtrusionStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_HEATMAP_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_HEATMAP_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<HeatmapLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<HeatmapLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<HeatmapStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<HeatmapStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
#if defined(MBGL_LAYER_CUSTOM_DISABLE_RUNTIME)
|
#if defined(MBGL_LAYER_CUSTOM_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<CustomLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<CustomLayerFactory>());
|
||||||
#elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL)
|
#elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<CustomStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<CustomStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME)
|
#if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME)
|
||||||
addLayerTypeCoreOnly(std::make_unique<CustomDrawableLayerFactory>());
|
addLayerTypeCoreOnly(std::make_unique<CustomDrawableLayerFactory>());
|
||||||
#elif !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL)
|
#elif !defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_ALL)
|
||||||
addLayerType(std::make_unique<CustomDrawableStyleLayerPeerFactory>());
|
addLayerType(std::make_unique<CustomDrawableStyleLayerPeerFactory>());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerManagerDarwin::~LayerManagerDarwin() = default;
|
LayerManagerDarwin::~LayerManagerDarwin() = default;
|
||||||
|
|
||||||
MLNStyleLayer* LayerManagerDarwin::createPeer(style::Layer* layer) {
|
MLNStyleLayer* LayerManagerDarwin::createPeer(style::Layer* layer) {
|
||||||
if (auto* factory = getPeerFactory(layer->getTypeInfo())) {
|
if (auto* factory = getPeerFactory(layer->getTypeInfo())) {
|
||||||
return factory->createPeer(layer);
|
return factory->createPeer(layer);
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayerManagerDarwin::addLayerType(std::unique_ptr<LayerPeerFactory> factory) {
|
void LayerManagerDarwin::addLayerType(std::unique_ptr<LayerPeerFactory> factory) {
|
||||||
NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr,
|
NSCAssert(getFactory(factory->getCoreLayerFactory()->getTypeInfo()) == nullptr,
|
||||||
@"A layer factory with the given info is already added.");
|
@"A layer factory with the given info is already added.");
|
||||||
registerCoreFactory(factory->getCoreLayerFactory());
|
registerCoreFactory(factory->getCoreLayerFactory());
|
||||||
peerFactories.emplace_back(std::move(factory));
|
peerFactories.emplace_back(std::move(factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr<LayerFactory> factory) {
|
void LayerManagerDarwin::addLayerTypeCoreOnly(std::unique_ptr<LayerFactory> factory) {
|
||||||
NSCAssert(getFactory(factory->getTypeInfo()) == nullptr,
|
NSCAssert(getFactory(factory->getTypeInfo()) == nullptr,
|
||||||
@"A layer factory with the given info is already added.");
|
@"A layer factory with the given info is already added.");
|
||||||
registerCoreFactory(factory.get());
|
registerCoreFactory(factory.get());
|
||||||
coreFactories.emplace_back(std::move(factory));
|
coreFactories.emplace_back(std::move(factory));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) {
|
void LayerManagerDarwin::registerCoreFactory(LayerFactory* factory) {
|
||||||
std::string type{factory->getTypeInfo()->type};
|
std::string type{factory->getTypeInfo()->type};
|
||||||
if (!type.empty()) {
|
if (!type.empty()) {
|
||||||
NSCAssert(typeToFactory.find(type) == typeToFactory.end(), @"A layer type can be registered only once.");
|
NSCAssert(typeToFactory.find(type) == typeToFactory.end(),
|
||||||
typeToFactory.emplace(std::make_pair(std::move(type), factory));
|
@"A layer type can be registered only once.");
|
||||||
}
|
typeToFactory.emplace(std::make_pair(std::move(type), factory));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) {
|
LayerPeerFactory* LayerManagerDarwin::getPeerFactory(const mbgl::style::LayerTypeInfo* typeInfo) {
|
||||||
for (const auto& factory: peerFactories) {
|
for (const auto& factory : peerFactories) {
|
||||||
if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) {
|
if (layerTypeInfoEquals(factory->getCoreLayerFactory()->getTypeInfo(), typeInfo)) {
|
||||||
return factory.get();
|
return factory.get();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nullptr;
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerFactory* LayerManagerDarwin::getFactory(const std::string& type) noexcept {
|
LayerFactory* LayerManagerDarwin::getFactory(const std::string& type) noexcept {
|
||||||
auto search = typeToFactory.find(type);
|
auto search = typeToFactory.find(type);
|
||||||
return (search != typeToFactory.end()) ? search->second : nullptr;
|
return (search != typeToFactory.end()) ? search->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* info) noexcept {
|
LayerFactory* LayerManagerDarwin::getFactory(const mbgl::style::LayerTypeInfo* info) noexcept {
|
||||||
if (LayerPeerFactory* peerFactory = getPeerFactory(info)) {
|
if (LayerPeerFactory* peerFactory = getPeerFactory(info)) {
|
||||||
return peerFactory->getCoreLayerFactory();
|
return peerFactory->getCoreLayerFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& factory: coreFactories) {
|
for (const auto& factory : coreFactories) {
|
||||||
if (layerTypeInfoEquals(factory->getTypeInfo(), info)) {
|
if (layerTypeInfoEquals(factory->getTypeInfo(), info)) {
|
||||||
// if (factory->getTypeInfo() == info) {
|
// if (factory->getTypeInfo() == info) {
|
||||||
return factory.get();
|
return factory.get();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
LayerManagerDarwin* LayerManagerDarwin::get() noexcept {
|
LayerManagerDarwin* LayerManagerDarwin::get() noexcept {
|
||||||
static LayerManagerDarwin impl;
|
static LayerManagerDarwin impl;
|
||||||
return &impl;
|
return &impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
LayerManager* LayerManager::get() noexcept {
|
LayerManager* LayerManager::get() noexcept { return LayerManagerDarwin::get(); }
|
||||||
return LayerManagerDarwin::get();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(MBGL_LAYER_LINE_DISABLE_ALL) || defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) || defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
#if defined(MBGL_LAYER_LINE_DISABLE_ALL) || defined(MBGL_LAYER_SYMBOL_DISABLE_ALL) || \
|
||||||
|
defined(MBGL_LAYER_FILL_DISABLE_ALL)
|
||||||
const bool LayerManager::annotationsEnabled = false;
|
const bool LayerManager::annotationsEnabled = false;
|
||||||
#else
|
#else
|
||||||
const bool LayerManager::annotationsEnabled = true;
|
const bool LayerManager::annotationsEnabled = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ struct LayerWrapper {
|
|||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
|
|
||||||
class LayerPeerFactory {
|
class LayerPeerFactory {
|
||||||
public:
|
public:
|
||||||
virtual ~LayerPeerFactory() = default;
|
virtual ~LayerPeerFactory() = default;
|
||||||
/**
|
/**
|
||||||
Get the corresponding core layer factory.
|
Get the corresponding core layer factory.
|
||||||
|
|||||||
@@ -2,60 +2,56 @@
|
|||||||
|
|
||||||
#include <mbgl/style/expression/expression.hpp>
|
#include <mbgl/style/expression/expression.hpp>
|
||||||
|
|
||||||
const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase = @"MLNStyleFunctionOptionInterpolationBase";
|
const MLNStyleFunctionOption MLNStyleFunctionOptionInterpolationBase =
|
||||||
const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue = @"MLNStyleFunctionOptionDefaultValue";
|
@"MLNStyleFunctionOptionInterpolationBase";
|
||||||
|
const MLNStyleFunctionOption MLNStyleFunctionOptionDefaultValue =
|
||||||
|
@"MLNStyleFunctionOptionDefaultValue";
|
||||||
|
|
||||||
id MLNJSONObjectFromMBGLValue(const mbgl::Value &value) {
|
id MLNJSONObjectFromMBGLValue(const mbgl::Value &value) {
|
||||||
return value.match([](const mbgl::NullValue) -> id {
|
return value.match(
|
||||||
return [NSNull null];
|
[](const mbgl::NullValue) -> id { return [NSNull null]; },
|
||||||
}, [](const bool value) {
|
[](const bool value) { return @(value); }, [](const float value) { return @(value); },
|
||||||
return @(value);
|
[](const int64_t value) { return @(value); }, [](const uint64_t value) { return @(value); },
|
||||||
}, [](const float value) {
|
[](const double value) { return @(value); },
|
||||||
return @(value);
|
[](const std::string &value) { return @(value.c_str()); },
|
||||||
}, [](const int64_t value) {
|
[](const mbgl::Color &value) { return [MLNColor mgl_colorWithColor:value]; },
|
||||||
return @(value);
|
[](const mbgl::style::Position &value) {
|
||||||
}, [](const uint64_t value) {
|
|
||||||
return @(value);
|
|
||||||
}, [](const double value) {
|
|
||||||
return @(value);
|
|
||||||
}, [](const std::string &value) {
|
|
||||||
return @(value.c_str());
|
|
||||||
}, [](const mbgl::Color &value) {
|
|
||||||
return [MLNColor mgl_colorWithColor:value];
|
|
||||||
}, [](const mbgl::style::Position &value) {
|
|
||||||
std::array<float, 3> spherical = value.getSpherical();
|
std::array<float, 3> spherical = value.getSpherical();
|
||||||
MLNSphericalPosition position = MLNSphericalPositionMake(spherical[0], spherical[1], spherical[2]);
|
MLNSphericalPosition position =
|
||||||
|
MLNSphericalPositionMake(spherical[0], spherical[1], spherical[2]);
|
||||||
return [NSValue valueWithMLNSphericalPosition:position];
|
return [NSValue valueWithMLNSphericalPosition:position];
|
||||||
}, [&](const std::vector<mbgl::Value> &vector) {
|
},
|
||||||
|
[&](const std::vector<mbgl::Value> &vector) {
|
||||||
NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()];
|
NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()];
|
||||||
for (auto value : vector) {
|
for (auto value : vector) {
|
||||||
[array addObject:MLNJSONObjectFromMBGLValue(value)];
|
[array addObject:MLNJSONObjectFromMBGLValue(value)];
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}, [&](const std::unordered_map<std::string, mbgl::Value> &map) {
|
},
|
||||||
|
[&](const std::unordered_map<std::string, mbgl::Value> &map) {
|
||||||
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:map.size()];
|
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:map.size()];
|
||||||
for (auto &item : map) {
|
for (auto &item : map) {
|
||||||
dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second);
|
dictionary[@(item.first.c_str())] = MLNJSONObjectFromMBGLValue(item.second);
|
||||||
}
|
}
|
||||||
return dictionary;
|
return dictionary;
|
||||||
}, [](const auto &) -> id {
|
},
|
||||||
return nil;
|
[](const auto &) -> id { return nil; });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) {
|
id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mbglExpression) {
|
||||||
return MLNJSONObjectFromMBGLValue(mbglExpression.serialize());
|
return MLNJSONObjectFromMBGLValue(mbglExpression.serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<mbgl::style::expression::Expression> MLNClusterPropertyFromNSExpression(
|
||||||
|
NSExpression *expression) {
|
||||||
|
if (!expression) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<mbgl::style::expression::Expression> MLNClusterPropertyFromNSExpression(NSExpression *expression) {
|
NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
|
||||||
if (!expression) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
|
auto expr = mbgl::style::expression::dsl::createExpression(
|
||||||
|
mbgl::style::conversion::makeConvertible(jsonExpression));
|
||||||
|
|
||||||
auto expr = mbgl::style::expression::dsl::createExpression(mbgl::style::conversion::makeConvertible(jsonExpression));
|
return expr;
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ id MLNJSONObjectFromMBGLExpression(const mbgl::style::expression::Expression &mb
|
|||||||
template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType,
|
template <typename MBGLType, typename ObjCType, typename MBGLElement = MBGLType,
|
||||||
typename ObjCEnum = ObjCType>
|
typename ObjCEnum = ObjCType>
|
||||||
class MLNStyleValueTransformer {
|
class MLNStyleValueTransformer {
|
||||||
public:
|
public:
|
||||||
/// Convert an mbgl property value into an mgl style value
|
/// Convert an mbgl property value into an mgl style value
|
||||||
NSExpression *toExpression(const mbgl::style::PropertyValue<MBGLType> &mbglValue) {
|
NSExpression *toExpression(const mbgl::style::PropertyValue<MBGLType> &mbglValue) {
|
||||||
PropertyExpressionEvaluator evaluator;
|
PropertyExpressionEvaluator evaluator;
|
||||||
@@ -139,7 +139,7 @@ class MLNStyleValueTransformer {
|
|||||||
return *value;
|
return *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private: // Private utilities for converting from mgl to mbgl values
|
private: // Private utilities for converting from mgl to mbgl values
|
||||||
/**
|
/**
|
||||||
As hack to allow converting enum => string values, we accept a second, dummy parameter in
|
As hack to allow converting enum => string values, we accept a second, dummy parameter in
|
||||||
the toRawStyleSpecValue() methods for converting 'atomic' (non-style-function) values.
|
the toRawStyleSpecValue() methods for converting 'atomic' (non-style-function) values.
|
||||||
@@ -307,7 +307,7 @@ class MLNStyleValueTransformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private: // Private utilities for converting from mbgl to mgl values
|
private: // Private utilities for converting from mbgl to mgl values
|
||||||
// Bool
|
// Bool
|
||||||
static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); }
|
static NSNumber *toMLNRawStyleValue(const bool mbglStopValue) { return @(mbglStopValue); }
|
||||||
|
|
||||||
@@ -390,7 +390,7 @@ class MLNStyleValueTransformer {
|
|||||||
|
|
||||||
/// Converts all types of mbgl property values into an equivalent NSExpression.
|
/// Converts all types of mbgl property values into an equivalent NSExpression.
|
||||||
class PropertyExpressionEvaluator {
|
class PropertyExpressionEvaluator {
|
||||||
public:
|
public:
|
||||||
NSExpression *operator()(const mbgl::style::Undefined) const { return nil; }
|
NSExpression *operator()(const mbgl::style::Undefined) const { return nil; }
|
||||||
|
|
||||||
NSExpression *operator()(const MBGLType &value) const {
|
NSExpression *operator()(const MBGLType &value) const {
|
||||||
|
|||||||
@@ -1,143 +1,159 @@
|
|||||||
#import "MLNTilePyramidOfflineRegion.h"
|
#import "MLNTilePyramidOfflineRegion.h"
|
||||||
|
|
||||||
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
|
#if !TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#import "MLNOfflineRegion_Private.h"
|
|
||||||
#import "MLNTilePyramidOfflineRegion_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
#import "MLNStyle.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNOfflineRegion_Private.h"
|
||||||
|
#import "MLNStyle.h"
|
||||||
|
#import "MLNTilePyramidOfflineRegion_Private.h"
|
||||||
|
|
||||||
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private, MLNTilePyramidOfflineRegion_Private>
|
@interface MLNTilePyramidOfflineRegion () <MLNOfflineRegion_Private,
|
||||||
|
MLNTilePyramidOfflineRegion_Private>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNTilePyramidOfflineRegion {
|
@implementation MLNTilePyramidOfflineRegion {
|
||||||
NSURL *_styleURL;
|
NSURL *_styleURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@synthesize styleURL = _styleURL;
|
@synthesize styleURL = _styleURL;
|
||||||
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
@synthesize includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
||||||
|
|
||||||
-(NSDictionary *)offlineStartEventAttributes {
|
- (NSDictionary *)offlineStartEventAttributes {
|
||||||
return @{};
|
return @{};
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (BOOL)supportsSecureCoding {
|
+ (BOOL)supportsSecureCoding {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
MLNLogInfo(@"Calling this initializer is not allowed.");
|
MLNLogInfo(@"Calling this initializer is not allowed.");
|
||||||
[NSException raise:NSGenericException format:
|
[NSException raise:NSGenericException
|
||||||
@"-[MLNTilePyramidOfflineRegion init] is unavailable. "
|
format:@"-[MLNTilePyramidOfflineRegion init] is unavailable. "
|
||||||
@"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."];
|
@"Use -initWithStyleURL:bounds:fromZoomLevel:toZoomLevel: instead."];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithStyleURL:(NSURL *)styleURL bounds:(MLNCoordinateBounds)bounds fromZoomLevel:(double)minimumZoomLevel toZoomLevel:(double)maximumZoomLevel {
|
- (instancetype)initWithStyleURL:(NSURL *)styleURL
|
||||||
MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL, MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel);
|
bounds:(MLNCoordinateBounds)bounds
|
||||||
if (self = [super init]) {
|
fromZoomLevel:(double)minimumZoomLevel
|
||||||
if (!styleURL) {
|
toZoomLevel:(double)maximumZoomLevel {
|
||||||
styleURL = [MLNStyle defaultStyleURL];
|
MLNLogDebug(@"Initializing styleURL: %@ bounds: %@ fromZoomLevel: %f toZoomLevel: %f", styleURL,
|
||||||
}
|
MLNStringFromCoordinateBounds(bounds), minimumZoomLevel, maximumZoomLevel);
|
||||||
|
if (self = [super init]) {
|
||||||
if (!styleURL.scheme) {
|
if (!styleURL) {
|
||||||
[NSException raise:MLNInvalidStyleURLException format:
|
styleURL = [MLNStyle defaultStyleURL];
|
||||||
@"%@ does not support setting a relative file URL as the style URL. "
|
|
||||||
@"To download the online resources required by this style, "
|
|
||||||
@"specify a URL to an online copy of this style. "
|
|
||||||
@"For Mapbox-hosted styles, use the mapbox: scheme.",
|
|
||||||
NSStringFromClass([self class])];
|
|
||||||
}
|
|
||||||
|
|
||||||
_styleURL = styleURL;
|
|
||||||
_bounds = bounds;
|
|
||||||
_minimumZoomLevel = minimumZoomLevel;
|
|
||||||
_maximumZoomLevel = maximumZoomLevel;
|
|
||||||
_includesIdeographicGlyphs = NO;
|
|
||||||
}
|
}
|
||||||
return self;
|
|
||||||
|
if (!styleURL.scheme) {
|
||||||
|
[NSException raise:MLNInvalidStyleURLException
|
||||||
|
format:@"%@ does not support setting a relative file URL as the style URL. "
|
||||||
|
@"To download the online resources required by this style, "
|
||||||
|
@"specify a URL to an online copy of this style. "
|
||||||
|
@"For Mapbox-hosted styles, use the mapbox: scheme.",
|
||||||
|
NSStringFromClass([self class])];
|
||||||
|
}
|
||||||
|
|
||||||
|
_styleURL = styleURL;
|
||||||
|
_bounds = bounds;
|
||||||
|
_minimumZoomLevel = minimumZoomLevel;
|
||||||
|
_maximumZoomLevel = maximumZoomLevel;
|
||||||
|
_includesIdeographicGlyphs = NO;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithOfflineRegionDefinition:(const mbgl::OfflineTilePyramidRegionDefinition &)definition {
|
- (instancetype)initWithOfflineRegionDefinition:
|
||||||
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
|
(const mbgl::OfflineTilePyramidRegionDefinition &)definition {
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds);
|
NSURL *styleURL = [NSURL URLWithString:@(definition.styleURL.c_str())];
|
||||||
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:definition.minZoom toZoomLevel:definition.maxZoom];
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsFromLatLngBounds(definition.bounds);
|
||||||
result.includesIdeographicGlyphs = definition.includeIdeographs;
|
MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
|
||||||
return result;
|
bounds:bounds
|
||||||
|
fromZoomLevel:definition.minZoom
|
||||||
|
toZoomLevel:definition.maxZoom];
|
||||||
|
result.includesIdeographicGlyphs = definition.includeIdeographs;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
|
- (const mbgl::OfflineRegionDefinition)offlineRegionDefinition {
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)] ? [[UIScreen mainScreen] nativeScale] : [[UIScreen mainScreen] scale];
|
const float scaleFactor = [UIScreen instancesRespondToSelector:@selector(nativeScale)]
|
||||||
|
? [[UIScreen mainScreen] nativeScale]
|
||||||
|
: [[UIScreen mainScreen] scale];
|
||||||
#elif TARGET_OS_MAC
|
#elif TARGET_OS_MAC
|
||||||
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
const float scaleFactor = [NSScreen mainScreen].backingScaleFactor;
|
||||||
#endif
|
#endif
|
||||||
return mbgl::OfflineTilePyramidRegionDefinition(_styleURL.absoluteString.UTF8String,
|
return mbgl::OfflineTilePyramidRegionDefinition(
|
||||||
MLNLatLngBoundsFromCoordinateBounds(_bounds),
|
_styleURL.absoluteString.UTF8String, MLNLatLngBoundsFromCoordinateBounds(_bounds),
|
||||||
_minimumZoomLevel, _maximumZoomLevel,
|
_minimumZoomLevel, _maximumZoomLevel, scaleFactor, _includesIdeographicGlyphs);
|
||||||
scaleFactor, _includesIdeographicGlyphs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
|
- (nullable instancetype)initWithCoder:(NSCoder *)coder {
|
||||||
MLNLogInfo(@"Initializing with coder.");
|
MLNLogInfo(@"Initializing with coder.");
|
||||||
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
|
NSURL *styleURL = [coder decodeObjectForKey:@"styleURL"];
|
||||||
CLLocationCoordinate2D sw = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"],
|
CLLocationCoordinate2D sw =
|
||||||
[coder decodeDoubleForKey:@"southWestLongitude"]);
|
CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"southWestLatitude"],
|
||||||
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"],
|
[coder decodeDoubleForKey:@"southWestLongitude"]);
|
||||||
[coder decodeDoubleForKey:@"northEastLongitude"]);
|
CLLocationCoordinate2D ne =
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne);
|
CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"northEastLatitude"],
|
||||||
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
|
[coder decodeDoubleForKey:@"northEastLongitude"]);
|
||||||
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(sw, ne);
|
||||||
|
double minimumZoomLevel = [coder decodeDoubleForKey:@"minimumZoomLevel"];
|
||||||
|
double maximumZoomLevel = [coder decodeDoubleForKey:@"maximumZoomLevel"];
|
||||||
|
|
||||||
MLNTilePyramidOfflineRegion* result = [self initWithStyleURL:styleURL bounds:bounds fromZoomLevel:minimumZoomLevel toZoomLevel:maximumZoomLevel];
|
MLNTilePyramidOfflineRegion *result = [self initWithStyleURL:styleURL
|
||||||
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
|
bounds:bounds
|
||||||
return result;
|
fromZoomLevel:minimumZoomLevel
|
||||||
|
toZoomLevel:maximumZoomLevel];
|
||||||
|
result.includesIdeographicGlyphs = [coder decodeBoolForKey:@"includesIdeographicGlyphs"];
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeWithCoder:(NSCoder *)coder
|
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||||
{
|
[coder encodeObject:_styleURL forKey:@"styleURL"];
|
||||||
[coder encodeObject:_styleURL forKey:@"styleURL"];
|
[coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"];
|
||||||
[coder encodeDouble:_bounds.sw.latitude forKey:@"southWestLatitude"];
|
[coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"];
|
||||||
[coder encodeDouble:_bounds.sw.longitude forKey:@"southWestLongitude"];
|
[coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"];
|
||||||
[coder encodeDouble:_bounds.ne.latitude forKey:@"northEastLatitude"];
|
[coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"];
|
||||||
[coder encodeDouble:_bounds.ne.longitude forKey:@"northEastLongitude"];
|
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
|
||||||
[coder encodeDouble:_maximumZoomLevel forKey:@"maximumZoomLevel"];
|
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
|
||||||
[coder encodeDouble:_minimumZoomLevel forKey:@"minimumZoomLevel"];
|
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
|
||||||
[coder encodeBool:_includesIdeographicGlyphs forKey:@"includesIdeographicGlyphs"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)copyWithZone:(nullable NSZone *)zone {
|
- (id)copyWithZone:(nullable NSZone *)zone {
|
||||||
MLNTilePyramidOfflineRegion* result = [[[self class] allocWithZone:zone] initWithStyleURL:_styleURL bounds:_bounds fromZoomLevel:_minimumZoomLevel toZoomLevel:_maximumZoomLevel];
|
MLNTilePyramidOfflineRegion *result =
|
||||||
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
[[[self class] allocWithZone:zone] initWithStyleURL:_styleURL
|
||||||
return result;
|
bounds:_bounds
|
||||||
|
fromZoomLevel:_minimumZoomLevel
|
||||||
|
toZoomLevel:_maximumZoomLevel];
|
||||||
|
result.includesIdeographicGlyphs = _includesIdeographicGlyphs;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isEqual:(id)other {
|
- (BOOL)isEqual:(id)other {
|
||||||
if (other == self) {
|
if (other == self) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
if (![other isKindOfClass:[self class]]) {
|
if (![other isKindOfClass:[self class]]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNTilePyramidOfflineRegion *otherRegion = other;
|
MLNTilePyramidOfflineRegion *otherRegion = other;
|
||||||
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel
|
return (_minimumZoomLevel == otherRegion->_minimumZoomLevel &&
|
||||||
&& _maximumZoomLevel == otherRegion->_maximumZoomLevel
|
_maximumZoomLevel == otherRegion->_maximumZoomLevel &&
|
||||||
&& MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds)
|
MLNCoordinateBoundsEqualToCoordinateBounds(_bounds, otherRegion->_bounds) &&
|
||||||
&& [_styleURL isEqual:otherRegion->_styleURL]
|
[_styleURL isEqual:otherRegion->_styleURL] &&
|
||||||
&& _includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
|
_includesIdeographicGlyphs == otherRegion->_includesIdeographicGlyphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)hash {
|
- (NSUInteger)hash {
|
||||||
return (_styleURL.hash
|
return (_styleURL.hash + @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash +
|
||||||
+ @(_bounds.sw.latitude).hash + @(_bounds.sw.longitude).hash
|
@(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash + @(_minimumZoomLevel).hash +
|
||||||
+ @(_bounds.ne.latitude).hash + @(_bounds.ne.longitude).hash
|
@(_maximumZoomLevel).hash + @(_includesIdeographicGlyphs).hash);
|
||||||
+ @(_minimumZoomLevel).hash + @(_maximumZoomLevel).hash
|
|
||||||
+ @(_includesIdeographicGlyphs).hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -8,164 +8,180 @@
|
|||||||
#import "NSValue+MLNAdditions.h"
|
#import "NSValue+MLNAdditions.h"
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#else
|
#else
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mbgl/util/tileset.hpp>
|
#include <mbgl/util/tileset.hpp>
|
||||||
|
|
||||||
const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel = @"MLNTileSourceOptionMinimumZoomLevel";
|
const MLNTileSourceOption MLNTileSourceOptionMinimumZoomLevel =
|
||||||
const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel = @"MLNTileSourceOptionMaximumZoomLevel";
|
@"MLNTileSourceOptionMinimumZoomLevel";
|
||||||
const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds = @"MLNTileSourceOptionCoordinateBounds";
|
const MLNTileSourceOption MLNTileSourceOptionMaximumZoomLevel =
|
||||||
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString = @"MLNTileSourceOptionAttributionHTMLString";
|
@"MLNTileSourceOptionMaximumZoomLevel";
|
||||||
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos = @"MLNTileSourceOptionAttributionInfos";
|
const MLNTileSourceOption MLNTileSourceOptionCoordinateBounds =
|
||||||
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem = @"MLNTileSourceOptionTileCoordinateSystem";
|
@"MLNTileSourceOptionCoordinateBounds";
|
||||||
|
const MLNTileSourceOption MLNTileSourceOptionAttributionHTMLString =
|
||||||
|
@"MLNTileSourceOptionAttributionHTMLString";
|
||||||
|
const MLNTileSourceOption MLNTileSourceOptionAttributionInfos =
|
||||||
|
@"MLNTileSourceOptionAttributionInfos";
|
||||||
|
const MLNTileSourceOption MLNTileSourceOptionTileCoordinateSystem =
|
||||||
|
@"MLNTileSourceOptionTileCoordinateSystem";
|
||||||
|
|
||||||
@implementation MLNTileSource
|
@implementation MLNTileSource
|
||||||
|
|
||||||
- (NSURL *)configurationURL {
|
- (NSURL *)configurationURL {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
|
||||||
format:@"MLNTileSource is an abstract class"];
|
return nil;
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<MLNAttributionInfo *> *)attributionInfos {
|
- (NSArray<MLNAttributionInfo *> *)attributionInfos {
|
||||||
return [self attributionInfosWithFontSize:0 linkColor:nil];
|
return [self attributionInfosWithFontSize:0 linkColor:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MLNColor *)linkColor {
|
- (NSArray<MLNAttributionInfo *> *)attributionInfosWithFontSize:(CGFloat)fontSize
|
||||||
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
|
linkColor:(nullable MLNColor *)linkColor {
|
||||||
fontSize:fontSize
|
return [MLNAttributionInfo attributionInfosFromHTMLString:self.attributionHTMLString
|
||||||
linkColor:linkColor];
|
fontSize:fontSize
|
||||||
|
linkColor:linkColor];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)attributionHTMLString {
|
- (NSString *)attributionHTMLString {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException format:@"MLNTileSource is an abstract class"];
|
||||||
format:@"MLNTileSource is an abstract class"];
|
return nil;
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
mbgl::Tileset MLNTileSetFromTileURLTemplates(NSArray<NSString *> *tileURLTemplates, NSDictionary<MLNTileSourceOption, id> * _Nullable options) {
|
mbgl::Tileset MLNTileSetFromTileURLTemplates(
|
||||||
mbgl::Tileset tileSet;
|
NSArray<NSString *> *tileURLTemplates,
|
||||||
|
NSDictionary<MLNTileSourceOption, id> *_Nullable options) {
|
||||||
|
mbgl::Tileset tileSet;
|
||||||
|
|
||||||
for (NSString *tileURLTemplate in tileURLTemplates) {
|
for (NSString *tileURLTemplate in tileURLTemplates) {
|
||||||
tileSet.tiles.push_back(tileURLTemplate.UTF8String);
|
tileSet.tiles.push_back(tileURLTemplate.UTF8String);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the minimum / maximum zoom range to the values specified by this class if they
|
||||||
|
// were set. otherwise, use the core objects default values
|
||||||
|
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
|
||||||
|
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
|
||||||
|
}
|
||||||
|
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
|
||||||
|
}
|
||||||
|
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
|
||||||
|
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
|
||||||
|
}
|
||||||
|
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
|
||||||
|
}
|
||||||
|
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than "
|
||||||
|
@"MLNTileSourceOptionMaximumZoomLevel."];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
|
||||||
|
if (![coordinateBounds isKindOfClass:[NSValue class]] &&
|
||||||
|
strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue "
|
||||||
|
@"containing an MLNCoordinateBounds."];
|
||||||
|
}
|
||||||
|
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
|
||||||
|
if (![attribution isKindOfClass:[NSString class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
|
||||||
|
}
|
||||||
|
tileSet.attribution = attribution.UTF8String;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
|
||||||
|
if (![attributionInfos isKindOfClass:[NSArray class]]) {
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the minimum / maximum zoom range to the values specified by this class if they
|
NSAttributedString *attributedString =
|
||||||
// were set. otherwise, use the core objects default values
|
[MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
|
||||||
if (NSNumber *minimumZoomLevel = options[MLNTileSourceOptionMinimumZoomLevel]) {
|
|
||||||
if (![minimumZoomLevel isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
|
|
||||||
}
|
|
||||||
tileSet.zoomRange.min = minimumZoomLevel.integerValue;
|
|
||||||
}
|
|
||||||
if (NSNumber *maximumZoomLevel = options[MLNTileSourceOptionMaximumZoomLevel]) {
|
|
||||||
if (![maximumZoomLevel isKindOfClass:[NSNumber class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionMinimumZoomLevel must be set to an NSNumber."];
|
|
||||||
}
|
|
||||||
tileSet.zoomRange.max = maximumZoomLevel.integerValue;
|
|
||||||
}
|
|
||||||
if (tileSet.zoomRange.min > tileSet.zoomRange.max) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionMinimumZoomLevel must be less than MLNTileSourceOptionMaximumZoomLevel."];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSValue *coordinateBounds = options[MLNTileSourceOptionCoordinateBounds]) {
|
|
||||||
if (![coordinateBounds isKindOfClass:[NSValue class]]
|
|
||||||
&& strcmp(coordinateBounds.objCType, @encode(MLNCoordinateBounds)) == 0) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionCoordinateBounds must be set to an NSValue containing an MLNCoordinateBounds."];
|
|
||||||
}
|
|
||||||
tileSet.bounds = MLNLatLngBoundsFromCoordinateBounds(coordinateBounds.MLNCoordinateBoundsValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSString *attribution = options[MLNTileSourceOptionAttributionHTMLString]) {
|
|
||||||
if (![attribution isKindOfClass:[NSString class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionAttributionHTMLString must be set to a string."];
|
|
||||||
}
|
|
||||||
tileSet.attribution = attribution.UTF8String;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NSArray *attributionInfos = options[MLNTileSourceOptionAttributionInfos]) {
|
|
||||||
if (![attributionInfos isKindOfClass:[NSArray class]]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"MLNTileSourceOptionAttributionInfos must be set to a string."];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSAttributedString *attributedString = [MLNAttributionInfo attributedStringForAttributionInfos:attributionInfos];
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
static NSString * const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
|
static NSString *const NSExcludedElementsDocumentAttribute = @"ExcludedElements";
|
||||||
#endif
|
#endif
|
||||||
NSDictionary *documentAttributes = @{
|
NSDictionary *documentAttributes = @{
|
||||||
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
|
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
|
||||||
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding),
|
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
|
||||||
// The attribution string is meant to be a simple, inline fragment, not a full-fledged, validating document.
|
// The attribution string is meant to be a simple, inline fragment, not a full-fledged,
|
||||||
NSExcludedElementsDocumentAttribute: @[@"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p"],
|
// validating document.
|
||||||
};
|
NSExcludedElementsDocumentAttribute :
|
||||||
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange documentAttributes:documentAttributes error:NULL];
|
@[ @"XML", @"DOCTYPE", @"html", @"head", @"meta", @"title", @"style", @"body", @"p" ],
|
||||||
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
};
|
||||||
tileSet.attribution = html.UTF8String;
|
NSData *data = [attributedString dataFromRange:attributedString.mgl_wholeRange
|
||||||
}
|
documentAttributes:documentAttributes
|
||||||
|
error:NULL];
|
||||||
|
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||||
|
tileSet.attribution = html.UTF8String;
|
||||||
|
}
|
||||||
|
|
||||||
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
|
if (NSNumber *tileCoordinateSystemNumber = options[MLNTileSourceOptionTileCoordinateSystem]) {
|
||||||
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
|
if (![tileCoordinateSystemNumber isKindOfClass:[NSValue class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException
|
||||||
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
|
raise:NSInvalidArgumentException
|
||||||
}
|
format:@"MLNTileSourceOptionTileCoordinateSystem must be set to an NSValue or NSNumber."];
|
||||||
MLNTileCoordinateSystem tileCoordinateSystem;
|
|
||||||
[tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
|
|
||||||
switch (tileCoordinateSystem) {
|
|
||||||
case MLNTileCoordinateSystemXYZ:
|
|
||||||
tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
|
|
||||||
break;
|
|
||||||
case MLNTileCoordinateSystemTMS:
|
|
||||||
tileSet.scheme = mbgl::Tileset::Scheme::TMS;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
MLNTileCoordinateSystem tileCoordinateSystem;
|
||||||
NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding];
|
[tileCoordinateSystemNumber getValue:&tileCoordinateSystem];
|
||||||
if (encodingNumber) {
|
switch (tileCoordinateSystem) {
|
||||||
if (![encodingNumber isKindOfClass:[NSValue class]]) {
|
case MLNTileCoordinateSystemXYZ:
|
||||||
[NSException raise:NSInvalidArgumentException
|
tileSet.scheme = mbgl::Tileset::Scheme::XYZ;
|
||||||
format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
|
break;
|
||||||
}
|
case MLNTileCoordinateSystemTMS:
|
||||||
MLNDEMEncoding encoding;
|
tileSet.scheme = mbgl::Tileset::Scheme::TMS;
|
||||||
[encodingNumber getValue:&encoding];
|
break;
|
||||||
switch (encoding) {
|
|
||||||
case MLNDEMEncodingMapbox:
|
|
||||||
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
|
|
||||||
break;
|
|
||||||
case MLNDEMEncodingTerrarium:
|
|
||||||
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
encodingNumber = options[MLNVectorTileSourceOptionEncoding];
|
NSNumber *encodingNumber = options[MLNTileSourceOptionDEMEncoding];
|
||||||
if (encodingNumber) {
|
if (encodingNumber) {
|
||||||
if (![encodingNumber isKindOfClass:[NSValue class]]) {
|
if (![encodingNumber isKindOfClass:[NSValue class]]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
|
format:@"MLNTileSourceOptionDEMEncoding must be set to an NSValue or NSNumber."];
|
||||||
}
|
|
||||||
MLNVectorTileSourceEncoding encoding;
|
|
||||||
[encodingNumber getValue:&encoding];
|
|
||||||
switch (encoding) {
|
|
||||||
case MLNVectorTileSourceEncodingMapbox:
|
|
||||||
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
|
|
||||||
break;
|
|
||||||
case MLNVectorTileSourceEncodingMLT:
|
|
||||||
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
MLNDEMEncoding encoding;
|
||||||
|
[encodingNumber getValue:&encoding];
|
||||||
|
switch (encoding) {
|
||||||
|
case MLNDEMEncodingMapbox:
|
||||||
|
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Mapbox;
|
||||||
|
break;
|
||||||
|
case MLNDEMEncodingTerrarium:
|
||||||
|
tileSet.rasterEncoding = mbgl::Tileset::RasterEncoding::Terrarium;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tileSet;
|
encodingNumber = options[MLNVectorTileSourceOptionEncoding];
|
||||||
|
if (encodingNumber) {
|
||||||
|
if (![encodingNumber isKindOfClass:[NSValue class]]) {
|
||||||
|
[NSException
|
||||||
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"MLNTileSourceOptionVectorEncoding must be set to an NSValue or NSNumber."];
|
||||||
|
}
|
||||||
|
MLNVectorTileSourceEncoding encoding;
|
||||||
|
[encodingNumber getValue:&encoding];
|
||||||
|
switch (encoding) {
|
||||||
|
case MLNVectorTileSourceEncodingMapbox:
|
||||||
|
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::Mapbox;
|
||||||
|
break;
|
||||||
|
case MLNVectorTileSourceEncodingMLT:
|
||||||
|
tileSet.vectorEncoding = mbgl::Tileset::VectorEncoding::MLT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tileSet;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import <mbgl/util/feature.hpp>
|
||||||
#import <mbgl/util/geometry.hpp>
|
#import <mbgl/util/geometry.hpp>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Recursively transforms a C++ type into the corresponding Foundation type.
|
Recursively transforms a C++ type into the corresponding Foundation type.
|
||||||
*/
|
*/
|
||||||
class ValueEvaluator {
|
class ValueEvaluator {
|
||||||
public:
|
public:
|
||||||
id operator()(const mbgl::NullValue &) const { return [NSNull null]; }
|
id operator()(const mbgl::NullValue &) const { return [NSNull null]; }
|
||||||
|
|
||||||
id operator()(const bool &value) const { return value ? @YES : @NO; }
|
id operator()(const bool &value) const { return value ? @YES : @NO; }
|
||||||
|
|||||||
@@ -4,31 +4,29 @@
|
|||||||
@implementation MLNVectorStyleLayer
|
@implementation MLNVectorStyleLayer
|
||||||
|
|
||||||
- (void)setPredicate:(NSPredicate *)predicate {
|
- (void)setPredicate:(NSPredicate *)predicate {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
|
||||||
format:@"MLNVectorStyleLayer is an abstract class"];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSPredicate *)predicate {
|
- (NSPredicate *)predicate {
|
||||||
[NSException raise:MLNAbstractClassException
|
[NSException raise:MLNAbstractClassException format:@"MLNVectorStyleLayer is an abstract class"];
|
||||||
format:@"MLNVectorStyleLayer is an abstract class"];
|
return nil;
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)description {
|
- (NSString *)description {
|
||||||
if (self.rawLayer) {
|
if (self.rawLayer) {
|
||||||
return [NSString stringWithFormat:
|
return
|
||||||
@"<%@: %p; identifier = %@; sourceIdentifier = %@; "
|
[NSString stringWithFormat:@"<%@: %p; identifier = %@; sourceIdentifier = %@; "
|
||||||
@"sourceLayerIdentifier = %@; predicate = %@; visible = %@>",
|
@"sourceLayerIdentifier = %@; predicate = %@; visible = %@>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier,
|
NSStringFromClass([self class]), (void *)self, self.identifier,
|
||||||
self.sourceIdentifier, self.sourceLayerIdentifier, self.predicate,
|
self.sourceIdentifier, self.sourceLayerIdentifier,
|
||||||
self.visible ? @"YES" : @"NO"];
|
self.predicate, self.visible ? @"YES" : @"NO"];
|
||||||
}
|
} else {
|
||||||
else {
|
return [NSString
|
||||||
return [NSString stringWithFormat:
|
stringWithFormat:
|
||||||
@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; "
|
@"<%@: %p; identifier = %@; sourceIdentifier = <unknown>; "
|
||||||
@"sourceLayerIdentifier = <unknown>; predicate = <unknown>; visible = <unknown>>",
|
@"sourceLayerIdentifier = <unknown>; predicate = <unknown>; visible = <unknown>>",
|
||||||
NSStringFromClass([self class]), (void *)self, self.identifier];
|
NSStringFromClass([self class]), (void *)self, self.identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
#import "MLNFeature_Private.h"
|
#import "MLNFeature_Private.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNSource_Private.h"
|
|
||||||
#import "MLNTileSource_Private.h"
|
|
||||||
#import "MLNStyle_Private.h"
|
|
||||||
#import "MLNMapView_Private.h"
|
#import "MLNMapView_Private.h"
|
||||||
|
#import "MLNSource_Private.h"
|
||||||
|
#import "MLNStyle_Private.h"
|
||||||
|
#import "MLNTileSource_Private.h"
|
||||||
|
|
||||||
#import "NSPredicate+MLNPrivateAdditions.h"
|
#import "NSPredicate+MLNPrivateAdditions.h"
|
||||||
#import "NSURL+MLNAdditions.h"
|
#import "NSURL+MLNAdditions.h"
|
||||||
|
|
||||||
#include <mbgl/map/map.hpp>
|
#include <mbgl/map/map.hpp>
|
||||||
#include <mbgl/style/sources/vector_source.hpp>
|
|
||||||
#include <mbgl/renderer/renderer.hpp>
|
#include <mbgl/renderer/renderer.hpp>
|
||||||
|
#include <mbgl/style/sources/vector_source.hpp>
|
||||||
|
|
||||||
const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSourceOptionEncoding";
|
const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSourceOptionEncoding";
|
||||||
|
|
||||||
@@ -24,72 +24,78 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
|
|||||||
|
|
||||||
@implementation MLNVectorTileSource
|
@implementation MLNVectorTileSource
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier configurationURL:(NSURL *)configurationURL {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String,
|
configurationURL:(NSURL *)configurationURL {
|
||||||
configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
auto source = std::make_unique<mbgl::style::VectorSource>(
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
identifier.UTF8String,
|
||||||
|
configurationURL.mgl_URLByStandardizingScheme.absoluteString.UTF8String);
|
||||||
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
configurationURLString:(NSString *)configurationURLString
|
configurationURLString:(NSString *)configurationURLString {
|
||||||
{
|
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String,
|
||||||
auto source = std::make_unique<mbgl::style::VectorSource>(
|
configurationURLString.UTF8String);
|
||||||
identifier.UTF8String,
|
|
||||||
configurationURLString.UTF8String
|
|
||||||
);
|
|
||||||
|
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithIdentifier:(NSString *)identifier tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
|
- (instancetype)initWithIdentifier:(NSString *)identifier
|
||||||
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
|
tileURLTemplates:(NSArray<NSString *> *)tileURLTemplates
|
||||||
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet);
|
options:(nullable NSDictionary<MLNTileSourceOption, id> *)options {
|
||||||
return self = [super initWithPendingSource:std::move(source)];
|
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, options);
|
||||||
|
auto source = std::make_unique<mbgl::style::VectorSource>(identifier.UTF8String, tileSet);
|
||||||
|
return self = [super initWithPendingSource:std::move(source)];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::style::VectorSource *)rawSource {
|
- (mbgl::style::VectorSource *)rawSource {
|
||||||
return (mbgl::style::VectorSource *)super.rawSource;
|
return (mbgl::style::VectorSource *)super.rawSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL *)configurationURL {
|
- (NSURL *)configurationURL {
|
||||||
MLNAssertStyleSourceIsValid();
|
MLNAssertStyleSourceIsValid();
|
||||||
auto url = self.rawSource->getURL();
|
auto url = self.rawSource->getURL();
|
||||||
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
return url ? [NSURL URLWithString:@(url->c_str())] : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)attributionHTMLString {
|
- (NSString *)attributionHTMLString {
|
||||||
if (!self.rawSource) {
|
if (!self.rawSource) {
|
||||||
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change", self.identifier);
|
MLNAssert(0, @"Source with identifier `%@` was invalidated after a style change",
|
||||||
return nil;
|
self.identifier);
|
||||||
}
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
auto attribution = self.rawSource->getAttribution();
|
auto attribution = self.rawSource->getAttribution();
|
||||||
return attribution ? @(attribution->c_str()) : nil;
|
return attribution ? @(attribution->c_str()) : nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSArray<id <MLNFeature>> *)featuresInSourceLayersWithIdentifiers:(NSSet<NSString *> *)sourceLayerIdentifiers predicate:(nullable NSPredicate *)predicate {
|
- (NSArray<id<MLNFeature>> *)
|
||||||
MLNAssertStyleSourceIsValid();
|
featuresInSourceLayersWithIdentifiers:(NSSet<NSString *> *)sourceLayerIdentifiers
|
||||||
std::optional<std::vector<std::string>> optionalSourceLayerIDs;
|
predicate:(nullable NSPredicate *)predicate {
|
||||||
if (sourceLayerIdentifiers) {
|
MLNAssertStyleSourceIsValid();
|
||||||
__block std::vector<std::string> layerIDs;
|
std::optional<std::vector<std::string>> optionalSourceLayerIDs;
|
||||||
layerIDs.reserve(sourceLayerIdentifiers.count);
|
if (sourceLayerIdentifiers) {
|
||||||
[sourceLayerIdentifiers enumerateObjectsUsingBlock:^(NSString * _Nonnull identifier, BOOL * _Nonnull stop) {
|
__block std::vector<std::string> layerIDs;
|
||||||
layerIDs.push_back(identifier.UTF8String);
|
layerIDs.reserve(sourceLayerIdentifiers.count);
|
||||||
|
[sourceLayerIdentifiers
|
||||||
|
enumerateObjectsUsingBlock:^(NSString *_Nonnull identifier, BOOL *_Nonnull stop) {
|
||||||
|
layerIDs.push_back(identifier.UTF8String);
|
||||||
}];
|
}];
|
||||||
optionalSourceLayerIDs = layerIDs;
|
optionalSourceLayerIDs = layerIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<mbgl::style::Filter> optionalFilter;
|
std::optional<mbgl::style::Filter> optionalFilter;
|
||||||
if (predicate) {
|
if (predicate) {
|
||||||
optionalFilter = predicate.mgl_filter;
|
optionalFilter = predicate.mgl_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<mbgl::Feature> features;
|
std::vector<mbgl::Feature> features;
|
||||||
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
if ([self.stylable isKindOfClass:[MLNMapView class]]) {
|
||||||
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
MLNMapView *mapView = (MLNMapView *)self.stylable;
|
||||||
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(), { optionalSourceLayerIDs, optionalFilter });
|
features = mapView.renderer->querySourceFeatures(self.rawSource->getID(),
|
||||||
}
|
{optionalSourceLayerIDs, optionalFilter});
|
||||||
return MLNFeaturesFromMBGLFeatures(features);
|
}
|
||||||
|
return MLNFeaturesFromMBGLFeatures(features);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -102,67 +108,94 @@ const MLNTileSourceOption MLNVectorTileSourceOptionEncoding = @"MLNVectorTileSou
|
|||||||
|
|
||||||
https://www.mapbox.com/vector-tiles/mapbox-streets-v8/
|
https://www.mapbox.com/vector-tiles/mapbox-streets-v8/
|
||||||
*/
|
*/
|
||||||
static NSArray * const MLNMapboxStreetsLanguages = @[
|
static NSArray *const MLNMapboxStreetsLanguages = @[
|
||||||
@"ar", @"de", @"en", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi",
|
@"ar",
|
||||||
@"zh-Hans", @"zh-Hant",
|
@"de",
|
||||||
|
@"en",
|
||||||
|
@"es",
|
||||||
|
@"fr",
|
||||||
|
@"ja",
|
||||||
|
@"ko",
|
||||||
|
@"pt",
|
||||||
|
@"ru",
|
||||||
|
@"vi",
|
||||||
|
@"zh-Hans",
|
||||||
|
@"zh-Hant",
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Like ``MLNMapboxStreetsLanguages``, but deanglicized for use with
|
Like ``MLNMapboxStreetsLanguages``, but deanglicized for use with
|
||||||
`+[NSBundle preferredLocalizationsFromArray:forPreferences:]`.
|
`+[NSBundle preferredLocalizationsFromArray:forPreferences:]`.
|
||||||
*/
|
*/
|
||||||
static NSArray * const MLNMapboxStreetsAlternativeLanguages = @[
|
static NSArray *const MLNMapboxStreetsAlternativeLanguages = @[
|
||||||
@"mul", @"ar", @"de", @"es", @"fr", @"ja", @"ko", @"pt", @"ru", @"vi",
|
@"mul",
|
||||||
@"zh-Hans", @"zh-Hant",
|
@"ar",
|
||||||
|
@"de",
|
||||||
|
@"es",
|
||||||
|
@"fr",
|
||||||
|
@"ja",
|
||||||
|
@"ko",
|
||||||
|
@"pt",
|
||||||
|
@"ru",
|
||||||
|
@"vi",
|
||||||
|
@"zh-Hans",
|
||||||
|
@"zh-Hant",
|
||||||
];
|
];
|
||||||
|
|
||||||
+ (NSSet<NSString *> *)mapboxStreetsLanguages {
|
+ (NSSet<NSString *> *)mapboxStreetsLanguages {
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
static NSSet<NSString *> *mapboxStreetsLanguages;
|
static NSSet<NSString *> *mapboxStreetsLanguages;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages];
|
mapboxStreetsLanguages = [NSSet setWithArray:MLNMapboxStreetsLanguages];
|
||||||
});
|
});
|
||||||
return mapboxStreetsLanguages;
|
return mapboxStreetsLanguages;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)preferredMapboxStreetsLanguage {
|
+ (NSString *)preferredMapboxStreetsLanguage {
|
||||||
return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]];
|
return [self preferredMapboxStreetsLanguageForPreferences:[NSLocale preferredLanguages]];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray {
|
+ (NSString *)preferredMapboxStreetsLanguageForPreferences:(NSArray<NSString *> *)preferencesArray {
|
||||||
BOOL acceptsEnglish = [preferencesArray filteredArrayUsingPredicate:
|
BOOL acceptsEnglish =
|
||||||
[NSPredicate predicateWithBlock:^BOOL(NSString * _Nullable language, NSDictionary<NSString *,id> * _Nullable bindings) {
|
[preferencesArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(
|
||||||
NSString *languageCode;
|
NSString *_Nullable language,
|
||||||
|
NSDictionary<NSString *, id>
|
||||||
|
*_Nullable bindings) {
|
||||||
|
NSString *languageCode;
|
||||||
|
|
||||||
if (@available(iOS 10.0, macOS 10.12.0, *)) {
|
if (@available(iOS 10.0, macOS 10.12.0, *)) {
|
||||||
languageCode = [NSLocale localeWithLocaleIdentifier:language].languageCode;
|
languageCode =
|
||||||
}
|
[NSLocale localeWithLocaleIdentifier:language].languageCode;
|
||||||
else {
|
} else {
|
||||||
languageCode = [[NSLocale localeWithLocaleIdentifier:language] objectForKey:NSLocaleLanguageCode];
|
languageCode = [[NSLocale localeWithLocaleIdentifier:language]
|
||||||
}
|
objectForKey:NSLocaleLanguageCode];
|
||||||
|
}
|
||||||
|
|
||||||
return [languageCode isEqualToString:@"en"];
|
return [languageCode isEqualToString:@"en"];
|
||||||
}]].count;
|
}]]
|
||||||
|
.count;
|
||||||
|
|
||||||
NSArray<NSString *> *availableLanguages = acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages;
|
NSArray<NSString *> *availableLanguages =
|
||||||
NSArray<NSString *> *preferredLanguages = [NSBundle preferredLocalizationsFromArray:availableLanguages
|
acceptsEnglish ? MLNMapboxStreetsLanguages : MLNMapboxStreetsAlternativeLanguages;
|
||||||
forPreferences:preferencesArray];
|
NSArray<NSString *> *preferredLanguages =
|
||||||
NSString *mostSpecificLanguage;
|
[NSBundle preferredLocalizationsFromArray:availableLanguages forPreferences:preferencesArray];
|
||||||
for (NSString *language in preferredLanguages) {
|
NSString *mostSpecificLanguage;
|
||||||
if (language.length > mostSpecificLanguage.length) {
|
for (NSString *language in preferredLanguages) {
|
||||||
mostSpecificLanguage = language;
|
if (language.length > mostSpecificLanguage.length) {
|
||||||
}
|
mostSpecificLanguage = language;
|
||||||
}
|
}
|
||||||
return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage;
|
}
|
||||||
|
return [mostSpecificLanguage isEqualToString:@"mul"] ? nil : mostSpecificLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)isMapboxStreets {
|
- (BOOL)isMapboxStreets {
|
||||||
NSURL *url = self.configurationURL;
|
NSURL *url = self.configurationURL;
|
||||||
if (![url.scheme isEqualToString:@"mapbox"]) {
|
if (![url.scheme isEqualToString:@"mapbox"]) {
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
NSArray *identifiers = [url.host componentsSeparatedByString:@","];
|
NSArray *identifiers = [url.host componentsSeparatedByString:@","];
|
||||||
return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] || [identifiers containsObject:@"mapbox.mapbox-streets-v7"];
|
return [identifiers containsObject:@"mapbox.mapbox-streets-v8"] ||
|
||||||
|
[identifiers containsObject:@"mapbox.mapbox-streets-v7"];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,66 +1,67 @@
|
|||||||
#import "NSArray+MLNAdditions.h"
|
#import "NSArray+MLNAdditions.h"
|
||||||
|
|
||||||
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "NSDictionary+MLNAdditions.h"
|
#import "NSDictionary+MLNAdditions.h"
|
||||||
#import "NSExpression+MLNPrivateAdditions.h"
|
#import "NSExpression+MLNPrivateAdditions.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
|
||||||
|
|
||||||
@implementation NSArray (MLNAdditions)
|
@implementation NSArray (MLNAdditions)
|
||||||
|
|
||||||
- (std::vector<mbgl::Value>)mgl_vector {
|
- (std::vector<mbgl::Value>)mgl_vector {
|
||||||
std::vector<mbgl::Value> vector;
|
std::vector<mbgl::Value> vector;
|
||||||
vector.reserve(self.count);
|
vector.reserve(self.count);
|
||||||
for (id value in self) {
|
for (id value in self) {
|
||||||
if ([value isKindOfClass:[NSArray class]]) {
|
if ([value isKindOfClass:[NSArray class]]) {
|
||||||
std::vector<mbgl::Value> innerVector = [value mgl_vector];
|
std::vector<mbgl::Value> innerVector = [value mgl_vector];
|
||||||
vector.push_back(innerVector);
|
vector.push_back(innerVector);
|
||||||
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
} else if ([value isKindOfClass:[NSDictionary class]]) {
|
||||||
mbgl::PropertyMap propertyMap = [value mgl_propertyMap];
|
mbgl::PropertyMap propertyMap = [value mgl_propertyMap];
|
||||||
vector.push_back(propertyMap);
|
vector.push_back(propertyMap);
|
||||||
} else {
|
} else {
|
||||||
NSExpression *expression = [NSExpression expressionForConstantValue:value];
|
NSExpression *expression = [NSExpression expressionForConstantValue:value];
|
||||||
vector.push_back(expression.mgl_constantMBGLValue);
|
vector.push_back(expression.mgl_constantMBGLValue);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return vector;
|
}
|
||||||
|
return vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator {
|
- (NSAttributedString *)mgl_attributedComponentsJoinedByString:(NSString *)separator {
|
||||||
NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator];
|
NSAttributedString *attributedSeparator = [[NSAttributedString alloc] initWithString:separator];
|
||||||
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
|
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
|
||||||
BOOL isSubsequentItem = NO;
|
BOOL isSubsequentItem = NO;
|
||||||
for (NSAttributedString *component in self) {
|
for (NSAttributedString *component in self) {
|
||||||
MLNAssert([component isKindOfClass:[NSAttributedString class]], @"Items in array must be attributed strings.");
|
MLNAssert([component isKindOfClass:[NSAttributedString class]],
|
||||||
if (isSubsequentItem) {
|
@"Items in array must be attributed strings.");
|
||||||
[attributedString appendAttributedString:attributedSeparator];
|
if (isSubsequentItem) {
|
||||||
}
|
[attributedString appendAttributedString:attributedSeparator];
|
||||||
isSubsequentItem = YES;
|
|
||||||
[attributedString appendAttributedString:component];
|
|
||||||
}
|
}
|
||||||
return attributedString;
|
isSubsequentItem = YES;
|
||||||
|
[attributedString appendAttributedString:component];
|
||||||
|
}
|
||||||
|
return attributedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector<CLLocationCoordinate2D>)coords {
|
+ (NSArray *)mgl_coordinatesFromCoordinates:(std::vector<CLLocationCoordinate2D>)coords {
|
||||||
NSMutableArray *coordinates = [NSMutableArray array];
|
NSMutableArray *coordinates = [NSMutableArray array];
|
||||||
for (auto coord : coords) {
|
for (auto coord : coords) {
|
||||||
[coordinates addObject:@{@"latitude": @(coord.latitude),
|
[coordinates addObject:@{@"latitude" : @(coord.latitude), @"longitude" : @(coord.longitude)}];
|
||||||
@"longitude": @(coord.longitude)}];
|
}
|
||||||
}
|
return coordinates;
|
||||||
return coordinates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::vector<CLLocationCoordinate2D>)mgl_coordinates {
|
- (std::vector<CLLocationCoordinate2D>)mgl_coordinates {
|
||||||
NSUInteger numberOfCoordinates = [self count];
|
NSUInteger numberOfCoordinates = [self count];
|
||||||
CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
|
CLLocationCoordinate2D *coords =
|
||||||
|
(CLLocationCoordinate2D *)malloc(numberOfCoordinates * sizeof(CLLocationCoordinate2D));
|
||||||
|
|
||||||
for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
|
for (NSUInteger i = 0; i < numberOfCoordinates; i++) {
|
||||||
coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue],
|
coords[i] = CLLocationCoordinate2DMake([self[i][@"latitude"] doubleValue],
|
||||||
[self[i][@"longitude"] doubleValue]);
|
[self[i][@"longitude"] doubleValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CLLocationCoordinate2D> coordinates = { coords, coords + numberOfCoordinates };
|
std::vector<CLLocationCoordinate2D> coordinates = {coords, coords + numberOfCoordinates};
|
||||||
free(coords);
|
free(coords);
|
||||||
|
|
||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -7,46 +7,48 @@ const MLNExceptionName MLNBundleNotFoundException = @"MLNBundleNotFoundException
|
|||||||
@implementation NSBundle (MLNAdditions)
|
@implementation NSBundle (MLNAdditions)
|
||||||
|
|
||||||
+ (instancetype)mgl_frameworkBundle {
|
+ (instancetype)mgl_frameworkBundle {
|
||||||
NSBundle *bundle = [self bundleForClass:[MLNSettings class]];
|
NSBundle *bundle = [self bundleForClass:[MLNSettings class]];
|
||||||
|
|
||||||
#ifndef MLN_CUSTOM_COMBINED_BUNDLE
|
#ifndef MLN_CUSTOM_COMBINED_BUNDLE
|
||||||
if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
|
if (![bundle.infoDictionary[@"CFBundlePackageType"] isEqualToString:@"FMWK"]) {
|
||||||
// For static frameworks, the bundle is the containing application
|
// For static frameworks, the bundle is the containing application
|
||||||
// bundle but the resources are in Mapbox.bundle.
|
// bundle but the resources are in Mapbox.bundle.
|
||||||
NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"];
|
NSString *bundlePath = [bundle pathForResource:@"Mapbox" ofType:@"bundle"];
|
||||||
if (bundlePath) {
|
if (bundlePath) {
|
||||||
bundle = [self bundleWithPath:bundlePath];
|
bundle = [self bundleWithPath:bundlePath];
|
||||||
} else {
|
} else {
|
||||||
[NSException raise:MLNBundleNotFoundException
|
[NSException raise:MLNBundleNotFoundException
|
||||||
format:@"The MapLibre framework bundle could not be found. If using MapLibre iOS as a static framework, make sure that MapLibre bundle is copied into the root of the app bundle."];
|
format:@"The MapLibre framework bundle could not be found. If using MapLibre "
|
||||||
}
|
@"iOS as a static framework, make sure that MapLibre bundle is copied "
|
||||||
|
@"into the root of the app bundle."];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (nullable NSString *)mgl_frameworkBundleIdentifier {
|
+ (nullable NSString *)mgl_frameworkBundleIdentifier {
|
||||||
return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"];
|
return self.mgl_frameworkInfoDictionary[@"CFBundleIdentifier"];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (nullable NSDictionary<NSString *, id> *)mgl_frameworkInfoDictionary {
|
+ (nullable NSDictionary<NSString *, id> *)mgl_frameworkInfoDictionary {
|
||||||
NSBundle *bundle = self.mgl_frameworkBundle;
|
NSBundle *bundle = self.mgl_frameworkBundle;
|
||||||
return bundle.infoDictionary;
|
return bundle.infoDictionary;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (nullable NSString *)mgl_applicationBundleIdentifier {
|
+ (nullable NSString *)mgl_applicationBundleIdentifier {
|
||||||
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
||||||
|
|
||||||
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \
|
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && (__IPHONE_OS_VERSION_MAX_ALLOWED < 120200)) || \
|
||||||
(defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101404))
|
(defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED < 101404))
|
||||||
// Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a unit test bundle.
|
// Before SDK 12.2 (bundled with Xcode 10.2): There’s no main bundle identifier when running in a
|
||||||
// 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool
|
// unit test bundle. 12.2 and after: the above bundle identifier is: com.apple.dt.xctest.tool
|
||||||
if (!bundleIdentifier) {
|
if (!bundleIdentifier) {
|
||||||
bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier;
|
bundleIdentifier = [NSBundle bundleForClass:[MLNSettings class]].bundleIdentifier;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return bundleIdentifier;
|
return bundleIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -5,23 +5,26 @@
|
|||||||
|
|
||||||
@implementation NSCoder (MLNAdditions)
|
@implementation NSCoder (MLNAdditions)
|
||||||
|
|
||||||
- (void)mgl_encodeLocationCoordinates2D:(std::vector<CLLocationCoordinate2D>)coordinates forKey:(NSString *)key {
|
- (void)mgl_encodeLocationCoordinates2D:(std::vector<CLLocationCoordinate2D>)coordinates
|
||||||
[self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key];
|
forKey:(NSString *)key {
|
||||||
|
[self encodeObject:[NSArray mgl_coordinatesFromCoordinates:coordinates] forKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::vector<CLLocationCoordinate2D>)mgl_decodeLocationCoordinates2DForKey:(NSString *)key {
|
- (std::vector<CLLocationCoordinate2D>)mgl_decodeLocationCoordinates2DForKey:(NSString *)key {
|
||||||
NSSet<Class> *coordinateClasses = [NSSet setWithArray:@[[NSArray class], [NSDictionary class]]];
|
NSSet<Class> *coordinateClasses = [NSSet setWithArray:@[ [NSArray class], [NSDictionary class] ]];
|
||||||
NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key];
|
NSArray *coordinates = [self decodeObjectOfClasses:coordinateClasses forKey:key];
|
||||||
return [coordinates mgl_coordinates];
|
return [coordinates mgl_coordinates];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)encodeMLNCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key {
|
- (void)encodeMLNCoordinate:(CLLocationCoordinate2D)coordinate forKey:(NSString *)key {
|
||||||
[self encodeObject:@{@"latitude": @(coordinate.latitude), @"longitude": @(coordinate.longitude)} forKey:key];
|
[self encodeObject:@{@"latitude" : @(coordinate.latitude), @"longitude" : @(coordinate.longitude)}
|
||||||
|
forKey:key];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CLLocationCoordinate2D)decodeMLNCoordinateForKey:(NSString *)key {
|
- (CLLocationCoordinate2D)decodeMLNCoordinateForKey:(NSString *)key {
|
||||||
NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key];
|
NSDictionary *coordinate = [self decodeObjectOfClass:[NSDictionary class] forKey:key];
|
||||||
return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue], [coordinate[@"longitude"] doubleValue]);
|
return CLLocationCoordinate2DMake([coordinate[@"latitude"] doubleValue],
|
||||||
|
[coordinate[@"longitude"] doubleValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,76 +2,80 @@
|
|||||||
|
|
||||||
#import "MLNStyleValue_Private.h"
|
#import "MLNStyleValue_Private.h"
|
||||||
|
|
||||||
#import "NSPredicate+MLNAdditions.h"
|
|
||||||
#import "NSExpression+MLNPrivateAdditions.h"
|
#import "NSExpression+MLNPrivateAdditions.h"
|
||||||
|
#import "NSPredicate+MLNAdditions.h"
|
||||||
|
|
||||||
@implementation NSComparisonPredicate (MLNAdditions)
|
@implementation NSComparisonPredicate (MLNAdditions)
|
||||||
|
|
||||||
- (NSString *)mgl_keyPath {
|
- (NSString *)mgl_keyPath {
|
||||||
NSExpression *leftExpression = self.leftExpression;
|
NSExpression *leftExpression = self.leftExpression;
|
||||||
NSExpression *rightExpression = self.rightExpression;
|
NSExpression *rightExpression = self.rightExpression;
|
||||||
NSExpressionType leftType = leftExpression.expressionType;
|
NSExpressionType leftType = leftExpression.expressionType;
|
||||||
NSExpressionType rightType = rightExpression.expressionType;
|
NSExpressionType rightType = rightExpression.expressionType;
|
||||||
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
||||||
return leftExpression.keyPath;
|
return leftExpression.keyPath;
|
||||||
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
||||||
return rightExpression.keyPath;
|
return rightExpression.keyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
|
format:@"Comparison predicate must compare an attribute (as a key path) to a "
|
||||||
return nil;
|
@"constant or vice versa."];
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::Value)mgl_constantValue {
|
- (mbgl::Value)mgl_constantValue {
|
||||||
NSExpression *leftExpression = self.leftExpression;
|
NSExpression *leftExpression = self.leftExpression;
|
||||||
NSExpression *rightExpression = self.rightExpression;
|
NSExpression *rightExpression = self.rightExpression;
|
||||||
NSExpressionType leftType = leftExpression.expressionType;
|
NSExpressionType leftType = leftExpression.expressionType;
|
||||||
NSExpressionType rightType = rightExpression.expressionType;
|
NSExpressionType rightType = rightExpression.expressionType;
|
||||||
mbgl::Value value;
|
mbgl::Value value;
|
||||||
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
||||||
value = rightExpression.mgl_constantMBGLValue;
|
value = rightExpression.mgl_constantMBGLValue;
|
||||||
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
||||||
value = leftExpression.mgl_constantMBGLValue;
|
value = leftExpression.mgl_constantMBGLValue;
|
||||||
} else {
|
} else {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
|
format:@"Comparison predicate must compare an attribute (as a key path) to a "
|
||||||
}
|
@"constant or vice versa."];
|
||||||
return value;
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::FeatureType)mgl_featureType {
|
- (mbgl::FeatureType)mgl_featureType {
|
||||||
NSExpression *leftExpression = self.leftExpression;
|
NSExpression *leftExpression = self.leftExpression;
|
||||||
NSExpression *rightExpression = self.rightExpression;
|
NSExpression *rightExpression = self.rightExpression;
|
||||||
NSExpressionType leftType = leftExpression.expressionType;
|
NSExpressionType leftType = leftExpression.expressionType;
|
||||||
NSExpressionType rightType = rightExpression.expressionType;
|
NSExpressionType rightType = rightExpression.expressionType;
|
||||||
mbgl::FeatureType type;
|
mbgl::FeatureType type;
|
||||||
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
||||||
type = rightExpression.mgl_featureType;
|
type = rightExpression.mgl_featureType;
|
||||||
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
||||||
type = leftExpression.mgl_featureType;
|
type = leftExpression.mgl_featureType;
|
||||||
} else {
|
} else {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
|
format:@"Comparison predicate must compare an attribute (as a key path) to a "
|
||||||
}
|
@"constant or vice versa."];
|
||||||
return type;
|
}
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (mbgl::FeatureIdentifier)mgl_featureIdentifier {
|
- (mbgl::FeatureIdentifier)mgl_featureIdentifier {
|
||||||
NSExpression *leftExpression = self.leftExpression;
|
NSExpression *leftExpression = self.leftExpression;
|
||||||
NSExpression *rightExpression = self.rightExpression;
|
NSExpression *rightExpression = self.rightExpression;
|
||||||
NSExpressionType leftType = leftExpression.expressionType;
|
NSExpressionType leftType = leftExpression.expressionType;
|
||||||
NSExpressionType rightType = rightExpression.expressionType;
|
NSExpressionType rightType = rightExpression.expressionType;
|
||||||
mbgl::FeatureIdentifier identifier;
|
mbgl::FeatureIdentifier identifier;
|
||||||
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
if (leftType == NSKeyPathExpressionType && rightType == NSConstantValueExpressionType) {
|
||||||
identifier = rightExpression.mgl_featureIdentifier;
|
identifier = rightExpression.mgl_featureIdentifier;
|
||||||
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
} else if (leftType == NSConstantValueExpressionType && rightType == NSKeyPathExpressionType) {
|
||||||
identifier = leftExpression.mgl_featureIdentifier;
|
identifier = leftExpression.mgl_featureIdentifier;
|
||||||
} else {
|
} else {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Comparison predicate must compare an attribute (as a key path) to a constant or vice versa."];
|
format:@"Comparison predicate must compare an attribute (as a key path) to a "
|
||||||
}
|
@"constant or vice versa."];
|
||||||
return identifier;
|
}
|
||||||
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -79,138 +83,158 @@
|
|||||||
@implementation NSComparisonPredicate (MLNExpressionAdditions)
|
@implementation NSComparisonPredicate (MLNExpressionAdditions)
|
||||||
|
|
||||||
- (id)mgl_jsonExpressionObject {
|
- (id)mgl_jsonExpressionObject {
|
||||||
switch (self.comparisonPredicateModifier) {
|
switch (self.comparisonPredicateModifier) {
|
||||||
case NSDirectPredicateModifier:
|
case NSDirectPredicateModifier:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NSAllPredicateModifier:
|
case NSAllPredicateModifier:
|
||||||
// “ALL x != y” is logically equivalent to “NOT y IN x”.
|
// “ALL x != y” is logically equivalent to “NOT y IN x”.
|
||||||
if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) {
|
if (self.predicateOperatorType == NSNotEqualToPredicateOperatorType) {
|
||||||
// https://github.com/mapbox/mapbox-gl-js/issues/9339
|
// https://github.com/mapbox/mapbox-gl-js/issues/9339
|
||||||
if (self.options) {
|
if (self.options) {
|
||||||
[NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."];
|
[NSException
|
||||||
}
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"NSComparisonPredicateOptions not supported for “ALL … !=” comparisons."];
|
||||||
NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
|
||||||
rightExpression:self.leftExpression
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:NSInPredicateOperatorType
|
|
||||||
options:self.options];
|
|
||||||
NSPredicate *invertedPredicate = [NSCompoundPredicate notPredicateWithSubpredicate:directPredicate];
|
|
||||||
return invertedPredicate.mgl_jsonExpressionObject;
|
|
||||||
} else {
|
|
||||||
[NSException raise:NSInvalidArgumentException format:@"“ALL” is only supported for the “!=” operator."];
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSAnyPredicateModifier:
|
|
||||||
// “ANY x = y” is logically equivalent to “y IN x”.
|
|
||||||
if (self.predicateOperatorType == NSEqualToPredicateOperatorType) {
|
|
||||||
// https://github.com/mapbox/mapbox-gl-js/issues/9339
|
|
||||||
if (self.options) {
|
|
||||||
[NSException raise:NSInvalidArgumentException format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSPredicate *directPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
|
||||||
rightExpression:self.leftExpression
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:NSInPredicateOperatorType
|
|
||||||
options:self.options];
|
|
||||||
return directPredicate.mgl_jsonExpressionObject;
|
|
||||||
} else {
|
|
||||||
[NSException raise:NSInvalidArgumentException format:@"“ANY” or “SOME” is only supported for the “=” operator."];
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"NSComparisonPredicateModifier:%lu is not supported.", (unsigned long)self.comparisonPredicateModifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *op;
|
|
||||||
switch (self.predicateOperatorType) {
|
|
||||||
case NSLessThanPredicateOperatorType:
|
|
||||||
op = @"<";
|
|
||||||
break;
|
|
||||||
case NSLessThanOrEqualToPredicateOperatorType:
|
|
||||||
op = @"<=";
|
|
||||||
break;
|
|
||||||
case NSGreaterThanPredicateOperatorType:
|
|
||||||
op = @">";
|
|
||||||
break;
|
|
||||||
case NSGreaterThanOrEqualToPredicateOperatorType:
|
|
||||||
op = @">=";
|
|
||||||
break;
|
|
||||||
case NSEqualToPredicateOperatorType:
|
|
||||||
op = @"==";
|
|
||||||
break;
|
|
||||||
case NSNotEqualToPredicateOperatorType:
|
|
||||||
op = @"!=";
|
|
||||||
break;
|
|
||||||
case NSBetweenPredicateOperatorType: {
|
|
||||||
op = @"all";
|
|
||||||
NSArray *limits = self.rightExpression.constantValue;
|
|
||||||
NSPredicate *leftHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:limits[0]
|
|
||||||
rightExpression:self.leftExpression
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:NSLessThanOrEqualToPredicateOperatorType
|
|
||||||
options:0];
|
|
||||||
NSPredicate *rightHandPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
|
|
||||||
rightExpression:limits[1]
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:NSLessThanOrEqualToPredicateOperatorType
|
|
||||||
options:0];
|
|
||||||
return @[op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject];
|
|
||||||
}
|
}
|
||||||
case NSInPredicateOperatorType: {
|
|
||||||
if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) {
|
NSPredicate *directPredicate =
|
||||||
return @[@"within", self.rightExpression.mgl_jsonExpressionObject];
|
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
||||||
}
|
rightExpression:self.leftExpression
|
||||||
// An “in” expression comparing two string literals is unfortunately
|
modifier:NSDirectPredicateModifier
|
||||||
// misinterpreted as a legacy “in” filter due to ambiguity. Wrap one
|
type:NSInPredicateOperatorType
|
||||||
// argument in a “literal” expression to force an expression.
|
options:self.options];
|
||||||
// https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077
|
NSPredicate *invertedPredicate =
|
||||||
if (self.leftExpression.expressionType == NSConstantValueExpressionType &&
|
[NSCompoundPredicate notPredicateWithSubpredicate:directPredicate];
|
||||||
self.rightExpression.expressionType == NSConstantValueExpressionType &&
|
return invertedPredicate.mgl_jsonExpressionObject;
|
||||||
[self.leftExpression.constantValue isKindOfClass:[NSString class]] &&
|
} else {
|
||||||
[self.rightExpression.constantValue isKindOfClass:[NSString class]]) {
|
[NSException raise:NSInvalidArgumentException
|
||||||
NSExpression *rightLiteralExpression = [NSExpression expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression];
|
format:@"“ALL” is only supported for the “!=” operator."];
|
||||||
NSPredicate *literalPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
|
}
|
||||||
rightExpression:rightLiteralExpression
|
|
||||||
modifier:NSDirectPredicateModifier
|
case NSAnyPredicateModifier:
|
||||||
type:NSInPredicateOperatorType
|
// “ANY x = y” is logically equivalent to “y IN x”.
|
||||||
options:self.options];
|
if (self.predicateOperatorType == NSEqualToPredicateOperatorType) {
|
||||||
return literalPredicate.mgl_jsonExpressionObject;
|
// https://github.com/mapbox/mapbox-gl-js/issues/9339
|
||||||
}
|
if (self.options) {
|
||||||
op = @"in";
|
[NSException
|
||||||
break;
|
raise:NSInvalidArgumentException
|
||||||
|
format:@"NSComparisonPredicateOptions not supported for “ANY … =” comparisons."];
|
||||||
}
|
}
|
||||||
case NSContainsPredicateOperatorType: {
|
|
||||||
NSPredicate *inPredicate = [NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
NSPredicate *directPredicate =
|
||||||
rightExpression:self.leftExpression
|
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
||||||
modifier:self.comparisonPredicateModifier
|
rightExpression:self.leftExpression
|
||||||
type:NSInPredicateOperatorType
|
modifier:NSDirectPredicateModifier
|
||||||
options:self.options];
|
type:NSInPredicateOperatorType
|
||||||
return inPredicate.mgl_jsonExpressionObject;
|
options:self.options];
|
||||||
}
|
return directPredicate.mgl_jsonExpressionObject;
|
||||||
case NSMatchesPredicateOperatorType:
|
} else {
|
||||||
case NSLikePredicateOperatorType:
|
[NSException raise:NSInvalidArgumentException
|
||||||
case NSBeginsWithPredicateOperatorType:
|
format:@"“ANY” or “SOME” is only supported for the “=” operator."];
|
||||||
case NSEndsWithPredicateOperatorType:
|
}
|
||||||
case NSCustomSelectorPredicateOperatorType:
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
default:
|
||||||
format:@"NSPredicateOperatorType:%lu is not supported.", (unsigned long)self.predicateOperatorType];
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"NSComparisonPredicateModifier:%lu is not supported.",
|
||||||
|
(unsigned long)self.comparisonPredicateModifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *op;
|
||||||
|
switch (self.predicateOperatorType) {
|
||||||
|
case NSLessThanPredicateOperatorType:
|
||||||
|
op = @"<";
|
||||||
|
break;
|
||||||
|
case NSLessThanOrEqualToPredicateOperatorType:
|
||||||
|
op = @"<=";
|
||||||
|
break;
|
||||||
|
case NSGreaterThanPredicateOperatorType:
|
||||||
|
op = @">";
|
||||||
|
break;
|
||||||
|
case NSGreaterThanOrEqualToPredicateOperatorType:
|
||||||
|
op = @">=";
|
||||||
|
break;
|
||||||
|
case NSEqualToPredicateOperatorType:
|
||||||
|
op = @"==";
|
||||||
|
break;
|
||||||
|
case NSNotEqualToPredicateOperatorType:
|
||||||
|
op = @"!=";
|
||||||
|
break;
|
||||||
|
case NSBetweenPredicateOperatorType: {
|
||||||
|
op = @"all";
|
||||||
|
NSArray *limits = self.rightExpression.constantValue;
|
||||||
|
NSPredicate *leftHandPredicate = [NSComparisonPredicate
|
||||||
|
predicateWithLeftExpression:limits[0]
|
||||||
|
rightExpression:self.leftExpression
|
||||||
|
modifier:NSDirectPredicateModifier
|
||||||
|
type:NSLessThanOrEqualToPredicateOperatorType
|
||||||
|
options:0];
|
||||||
|
NSPredicate *rightHandPredicate = [NSComparisonPredicate
|
||||||
|
predicateWithLeftExpression:self.leftExpression
|
||||||
|
rightExpression:limits[1]
|
||||||
|
modifier:NSDirectPredicateModifier
|
||||||
|
type:NSLessThanOrEqualToPredicateOperatorType
|
||||||
|
options:0];
|
||||||
|
return @[
|
||||||
|
op, leftHandPredicate.mgl_jsonExpressionObject, rightHandPredicate.mgl_jsonExpressionObject
|
||||||
|
];
|
||||||
}
|
}
|
||||||
if (!op) {
|
case NSInPredicateOperatorType: {
|
||||||
return nil;
|
if (self.leftExpression.expressionType == NSEvaluatedObjectExpressionType) {
|
||||||
|
return @[ @"within", self.rightExpression.mgl_jsonExpressionObject ];
|
||||||
|
}
|
||||||
|
// An “in” expression comparing two string literals is unfortunately
|
||||||
|
// misinterpreted as a legacy “in” filter due to ambiguity. Wrap one
|
||||||
|
// argument in a “literal” expression to force an expression.
|
||||||
|
// https://github.com/mapbox/mapbox-gl-js/issues/9373#issuecomment-594537077
|
||||||
|
if (self.leftExpression.expressionType == NSConstantValueExpressionType &&
|
||||||
|
self.rightExpression.expressionType == NSConstantValueExpressionType &&
|
||||||
|
[self.leftExpression.constantValue isKindOfClass:[NSString class]] &&
|
||||||
|
[self.rightExpression.constantValue isKindOfClass:[NSString class]]) {
|
||||||
|
NSExpression *rightLiteralExpression = [NSExpression
|
||||||
|
expressionWithFormat:@"MLN_FUNCTION('literal', %@)", self.rightExpression];
|
||||||
|
NSPredicate *literalPredicate =
|
||||||
|
[NSComparisonPredicate predicateWithLeftExpression:self.leftExpression
|
||||||
|
rightExpression:rightLiteralExpression
|
||||||
|
modifier:NSDirectPredicateModifier
|
||||||
|
type:NSInPredicateOperatorType
|
||||||
|
options:self.options];
|
||||||
|
return literalPredicate.mgl_jsonExpressionObject;
|
||||||
|
}
|
||||||
|
op = @"in";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
NSArray *comparisonArray = @[op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject];
|
case NSContainsPredicateOperatorType: {
|
||||||
if (self.options) {
|
NSPredicate *inPredicate =
|
||||||
NSDictionary *collatorObject = @{
|
[NSComparisonPredicate predicateWithLeftExpression:self.rightExpression
|
||||||
@"case-sensitive": @(!(self.options & NSCaseInsensitivePredicateOption)),
|
rightExpression:self.leftExpression
|
||||||
@"diacritic-sensitive": @(!(self.options & NSDiacriticInsensitivePredicateOption)),
|
modifier:self.comparisonPredicateModifier
|
||||||
};
|
type:NSInPredicateOperatorType
|
||||||
return [comparisonArray arrayByAddingObject:@[@"collator", collatorObject]];
|
options:self.options];
|
||||||
|
return inPredicate.mgl_jsonExpressionObject;
|
||||||
}
|
}
|
||||||
return comparisonArray;
|
case NSMatchesPredicateOperatorType:
|
||||||
|
case NSLikePredicateOperatorType:
|
||||||
|
case NSBeginsWithPredicateOperatorType:
|
||||||
|
case NSEndsWithPredicateOperatorType:
|
||||||
|
case NSCustomSelectorPredicateOperatorType:
|
||||||
|
[NSException raise:NSInvalidArgumentException
|
||||||
|
format:@"NSPredicateOperatorType:%lu is not supported.",
|
||||||
|
(unsigned long)self.predicateOperatorType];
|
||||||
|
}
|
||||||
|
if (!op) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
NSArray *comparisonArray = @[
|
||||||
|
op, self.leftExpression.mgl_jsonExpressionObject, self.rightExpression.mgl_jsonExpressionObject
|
||||||
|
];
|
||||||
|
if (self.options) {
|
||||||
|
NSDictionary *collatorObject = @{
|
||||||
|
@"case-sensitive" : @(!(self.options & NSCaseInsensitivePredicateOption)),
|
||||||
|
@"diacritic-sensitive" : @(!(self.options & NSDiacriticInsensitivePredicateOption)),
|
||||||
|
};
|
||||||
|
return [comparisonArray arrayByAddingObject:@[ @"collator", collatorObject ]];
|
||||||
|
}
|
||||||
|
return comparisonArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,21 +2,20 @@
|
|||||||
|
|
||||||
#import "MLNStyleValue_Private.h"
|
#import "MLNStyleValue_Private.h"
|
||||||
|
|
||||||
#import "NSPredicate+MLNPrivateAdditions.h"
|
|
||||||
#import "NSExpression+MLNPrivateAdditions.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "NSExpression+MLNPrivateAdditions.h"
|
||||||
|
#import "NSPredicate+MLNPrivateAdditions.h"
|
||||||
|
|
||||||
#include <mbgl/style/conversion/property_value.hpp>
|
#include <mbgl/style/conversion/property_value.hpp>
|
||||||
|
|
||||||
@implementation NSCompoundPredicate (MLNAdditions)
|
@implementation NSCompoundPredicate (MLNAdditions)
|
||||||
|
|
||||||
- (std::vector<mbgl::style::Filter>)mgl_subfilters
|
- (std::vector<mbgl::style::Filter>)mgl_subfilters {
|
||||||
{
|
std::vector<mbgl::style::Filter> filters;
|
||||||
std::vector<mbgl::style::Filter>filters;
|
for (NSPredicate *predicate in self.subpredicates) {
|
||||||
for (NSPredicate *predicate in self.subpredicates) {
|
filters.push_back(predicate.mgl_filter);
|
||||||
filters.push_back(predicate.mgl_filter);
|
}
|
||||||
}
|
return filters;
|
||||||
return filters;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -24,27 +23,27 @@
|
|||||||
@implementation NSCompoundPredicate (MLNExpressionAdditions)
|
@implementation NSCompoundPredicate (MLNExpressionAdditions)
|
||||||
|
|
||||||
- (id)mgl_jsonExpressionObject {
|
- (id)mgl_jsonExpressionObject {
|
||||||
switch (self.compoundPredicateType) {
|
switch (self.compoundPredicateType) {
|
||||||
case NSNotPredicateType: {
|
case NSNotPredicateType: {
|
||||||
MLNAssert(self.subpredicates.count <= 1, @"NOT predicate cannot have multiple subpredicates.");
|
MLNAssert(self.subpredicates.count <= 1,
|
||||||
NSPredicate *subpredicate = self.subpredicates.firstObject;
|
@"NOT predicate cannot have multiple subpredicates.");
|
||||||
return @[@"!", subpredicate.mgl_jsonExpressionObject];
|
NSPredicate *subpredicate = self.subpredicates.firstObject;
|
||||||
}
|
return @[ @"!", subpredicate.mgl_jsonExpressionObject ];
|
||||||
|
|
||||||
case NSAndPredicateType: {
|
|
||||||
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
|
|
||||||
return [@[@"all"] arrayByAddingObjectsFromArray:subarrays];
|
|
||||||
}
|
|
||||||
|
|
||||||
case NSOrPredicateType: {
|
|
||||||
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
|
|
||||||
return [@[@"any"] arrayByAddingObjectsFromArray:subarrays];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[NSException raise:@"Compound predicate type not handled"
|
case NSAndPredicateType: {
|
||||||
format:@""];
|
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
|
||||||
return nil;
|
return [@[ @"all" ] arrayByAddingObjectsFromArray:subarrays];
|
||||||
|
}
|
||||||
|
|
||||||
|
case NSOrPredicateType: {
|
||||||
|
NSArray *subarrays = [self.subpredicates valueForKeyPath:@"mgl_jsonExpressionObject"];
|
||||||
|
return [@[ @"any" ] arrayByAddingObjectsFromArray:subarrays];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[NSException raise:@"Compound predicate type not handled" format:@""];
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#import "NSDate+MLNAdditions.h"
|
|
||||||
#import <ratio>
|
#import <ratio>
|
||||||
|
#import "NSDate+MLNAdditions.h"
|
||||||
|
|
||||||
mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration)
|
mbgl::Duration MLNDurationFromTimeInterval(NSTimeInterval duration) {
|
||||||
{
|
return std::chrono::duration_cast<mbgl::Duration>(
|
||||||
return std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(duration));
|
std::chrono::duration<NSTimeInterval>(duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration)
|
NSTimeInterval MLNTimeIntervalFromDuration(mbgl::Duration duration) {
|
||||||
{
|
return std::chrono::duration<NSTimeInterval, std::ratio<1>>(duration).count();
|
||||||
return std::chrono::duration<NSTimeInterval, std::ratio<1>>(duration).count();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
#import "NSDictionary+MLNAdditions.h"
|
#import "NSDictionary+MLNAdditions.h"
|
||||||
|
|
||||||
#import "NSExpression+MLNPrivateAdditions.h"
|
|
||||||
#import "NSArray+MLNAdditions.h"
|
#import "NSArray+MLNAdditions.h"
|
||||||
|
#import "NSExpression+MLNPrivateAdditions.h"
|
||||||
|
|
||||||
@implementation NSDictionary (MLNAdditions)
|
@implementation NSDictionary (MLNAdditions)
|
||||||
|
|
||||||
- (mbgl::PropertyMap)mgl_propertyMap {
|
- (mbgl::PropertyMap)mgl_propertyMap {
|
||||||
mbgl::PropertyMap propertyMap;
|
mbgl::PropertyMap propertyMap;
|
||||||
for (NSString *key in self.allKeys) {
|
for (NSString *key in self.allKeys) {
|
||||||
if ([self[key] isKindOfClass:[NSDictionary class]]) {
|
if ([self[key] isKindOfClass:[NSDictionary class]]) {
|
||||||
propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap];
|
propertyMap[[key UTF8String]] = [self[key] mgl_propertyMap];
|
||||||
} else if ([self[key] isKindOfClass:[NSArray class]]) {
|
} else if ([self[key] isKindOfClass:[NSArray class]]) {
|
||||||
NSArray *array = self[key];
|
NSArray *array = self[key];
|
||||||
propertyMap[[key UTF8String]] = [array mgl_vector];
|
propertyMap[[key UTF8String]] = [array mgl_vector];
|
||||||
} else {
|
} else {
|
||||||
NSExpression *expression = [NSExpression expressionForConstantValue:self[key]];
|
NSExpression *expression = [NSExpression expressionForConstantValue:self[key]];
|
||||||
propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue;
|
propertyMap[[key UTF8String]] = expression.mgl_constantMBGLValue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return propertyMap;
|
}
|
||||||
|
return propertyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,36 @@
|
|||||||
#import "NSPredicate+MLNPrivateAdditions.h"
|
#import "NSPredicate+MLNPrivateAdditions.h"
|
||||||
|
|
||||||
#import "MLNValueEvaluator.h"
|
|
||||||
#import "MLNStyleValue_Private.h"
|
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
|
#import "MLNStyleValue_Private.h"
|
||||||
|
#import "MLNValueEvaluator.h"
|
||||||
|
|
||||||
#include <mbgl/style/conversion/filter.hpp>
|
#include <mbgl/style/conversion/filter.hpp>
|
||||||
|
|
||||||
@implementation NSPredicate (MLNPrivateAdditions)
|
@implementation NSPredicate (MLNPrivateAdditions)
|
||||||
|
|
||||||
- (mbgl::style::Filter)mgl_filter
|
- (mbgl::style::Filter)mgl_filter {
|
||||||
{
|
mbgl::style::conversion::Error valueError;
|
||||||
mbgl::style::conversion::Error valueError;
|
NSArray *jsonObject = self.mgl_jsonExpressionObject;
|
||||||
NSArray *jsonObject = self.mgl_jsonExpressionObject;
|
auto value = mbgl::style::conversion::convert<mbgl::style::Filter>(
|
||||||
auto value = mbgl::style::conversion::convert<mbgl::style::Filter>(mbgl::style::conversion::makeConvertible(jsonObject), valueError);
|
mbgl::style::conversion::makeConvertible(jsonObject), valueError);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Invalid filter value: %@", @(valueError.message.c_str())];
|
format:@"Invalid filter value: %@", @(valueError.message.c_str())];
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
mbgl::style::Filter filter = std::move(*value);
|
mbgl::style::Filter filter = std::move(*value);
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter
|
+ (instancetype)mgl_predicateWithFilter:(mbgl::style::Filter)filter {
|
||||||
{
|
if (filter.expression) {
|
||||||
if (filter.expression) {
|
id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression);
|
||||||
id jsonObject = MLNJSONObjectFromMBGLExpression(**filter.expression);
|
return [NSPredicate predicateWithMLNJSONObject:jsonObject];
|
||||||
return [NSPredicate predicateWithMLNJSONObject:jsonObject];
|
} else {
|
||||||
} else {
|
return nil;
|
||||||
return nil;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -39,156 +38,173 @@
|
|||||||
@implementation NSPredicate (MLNAdditions)
|
@implementation NSPredicate (MLNAdditions)
|
||||||
|
|
||||||
NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) {
|
NSArray *MLNSubpredicatesWithJSONObjects(NSArray *objects) {
|
||||||
NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count];
|
NSMutableArray *subpredicates = [NSMutableArray arrayWithCapacity:objects.count];
|
||||||
for (id object in objects) {
|
for (id object in objects) {
|
||||||
NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object];
|
NSPredicate *predicate = [NSPredicate predicateWithMLNJSONObject:object];
|
||||||
[subpredicates addObject:predicate];
|
[subpredicates addObject:predicate];
|
||||||
}
|
}
|
||||||
return subpredicates;
|
return subpredicates;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
|
static NSDictionary *const MLNPredicateOperatorTypesByJSONOperator = @{
|
||||||
@"==": @(NSEqualToPredicateOperatorType),
|
@"==" : @(NSEqualToPredicateOperatorType),
|
||||||
@"!=": @(NSNotEqualToPredicateOperatorType),
|
@"!=" : @(NSNotEqualToPredicateOperatorType),
|
||||||
@"<": @(NSLessThanPredicateOperatorType),
|
@"<" : @(NSLessThanPredicateOperatorType),
|
||||||
@"<=": @(NSLessThanOrEqualToPredicateOperatorType),
|
@"<=" : @(NSLessThanOrEqualToPredicateOperatorType),
|
||||||
@">": @(NSGreaterThanPredicateOperatorType),
|
@">" : @(NSGreaterThanPredicateOperatorType),
|
||||||
@">=": @(NSGreaterThanOrEqualToPredicateOperatorType),
|
@">=" : @(NSGreaterThanOrEqualToPredicateOperatorType),
|
||||||
@"in": @(NSInPredicateOperatorType),
|
@"in" : @(NSInPredicateOperatorType),
|
||||||
};
|
};
|
||||||
|
|
||||||
+ (instancetype)predicateWithMLNJSONObject:(id)object {
|
+ (instancetype)predicateWithMLNJSONObject:(id)object {
|
||||||
if ([object isEqual:@YES]) {
|
if ([object isEqual:@YES]) {
|
||||||
return [NSPredicate predicateWithValue:YES];
|
return [NSPredicate predicateWithValue:YES];
|
||||||
}
|
}
|
||||||
if ([object isEqual:@NO]) {
|
if ([object isEqual:@NO]) {
|
||||||
return [NSPredicate predicateWithValue:NO];
|
return [NSPredicate predicateWithValue:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNAssert([object isKindOfClass:[NSArray class]], @"Condition for case expression should be an expression.");
|
MLNAssert([object isKindOfClass:[NSArray class]],
|
||||||
NSArray *objects = (NSArray *)object;
|
@"Condition for case expression should be an expression.");
|
||||||
NSString *op = objects.firstObject;
|
NSArray *objects = (NSArray *)object;
|
||||||
|
NSString *op = objects.firstObject;
|
||||||
|
|
||||||
NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op];
|
NSNumber *operatorTypeNumber = MLNPredicateOperatorTypesByJSONOperator[op];
|
||||||
if (operatorTypeNumber) {
|
if (operatorTypeNumber) {
|
||||||
NSPredicateOperatorType operatorType = (NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue];
|
NSPredicateOperatorType operatorType =
|
||||||
|
(NSPredicateOperatorType)[operatorTypeNumber unsignedIntegerValue];
|
||||||
|
|
||||||
NSComparisonPredicateOptions options = 0;
|
NSComparisonPredicateOptions options = 0;
|
||||||
if (objects.count > 3) {
|
if (objects.count > 3) {
|
||||||
NSArray *collatorExpression = objects[3];
|
NSArray *collatorExpression = objects[3];
|
||||||
MLNCAssert([collatorExpression isKindOfClass:[NSArray class]], @"Collators must be dictionaries.");
|
MLNCAssert([collatorExpression isKindOfClass:[NSArray class]],
|
||||||
MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression");
|
@"Collators must be dictionaries.");
|
||||||
NSDictionary *collator = collatorExpression[1];
|
MLNCAssert(collatorExpression.count == 2, @"Malformed collator expression");
|
||||||
MLNCAssert([collator isKindOfClass:[NSDictionary class]], @"Malformed collator in collator expression");
|
NSDictionary *collator = collatorExpression[1];
|
||||||
|
MLNCAssert([collator isKindOfClass:[NSDictionary class]],
|
||||||
|
@"Malformed collator in collator expression");
|
||||||
|
|
||||||
// Predicate options can’t express specific locales as collators can.
|
// Predicate options can’t express specific locales as collators can.
|
||||||
if (!collator[@"locale"]) {
|
if (!collator[@"locale"]) {
|
||||||
if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) {
|
if ([(collator[@"case-sensitive"] ?: @YES) isEqual:@NO]) {
|
||||||
options |= NSCaseInsensitivePredicateOption;
|
options |= NSCaseInsensitivePredicateOption;
|
||||||
}
|
|
||||||
if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) {
|
|
||||||
options |= NSDiacriticInsensitivePredicateOption;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if ([(collator[@"diacritic-sensitive"] ?: @YES) isEqual:@NO]) {
|
||||||
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
options |= NSDiacriticInsensitivePredicateOption;
|
||||||
return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
|
|
||||||
rightExpression:subexpressions[1]
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:operatorType
|
|
||||||
options:options];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([op isEqualToString:@"!"]) {
|
|
||||||
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
|
||||||
if (subpredicates.count > 1) {
|
|
||||||
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
|
|
||||||
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
|
|
||||||
}
|
}
|
||||||
if (subpredicates.count) {
|
}
|
||||||
return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
|
|
||||||
}
|
|
||||||
return [NSPredicate predicateWithValue:YES];
|
|
||||||
}
|
|
||||||
if ([op isEqualToString:@"all"]) {
|
|
||||||
NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
|
||||||
if (subpredicates.count == 2) {
|
|
||||||
// Determine if the expression is of BETWEEN type
|
|
||||||
if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] &&
|
|
||||||
[subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) {
|
|
||||||
NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0];
|
|
||||||
NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1];
|
|
||||||
|
|
||||||
NSArray *limits;
|
|
||||||
NSExpression *leftConditionExpression;
|
|
||||||
|
|
||||||
if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
|
|
||||||
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
|
|
||||||
limits = @[leftCondition.rightExpression, rightCondition.rightExpression];
|
|
||||||
leftConditionExpression = leftCondition.leftExpression;
|
|
||||||
|
|
||||||
} else if (leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType &&
|
|
||||||
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
|
|
||||||
limits = @[leftCondition.leftExpression, rightCondition.rightExpression];
|
|
||||||
leftConditionExpression = leftCondition.rightExpression;
|
|
||||||
|
|
||||||
} else if(leftCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType &&
|
|
||||||
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) {
|
|
||||||
limits = @[leftCondition.leftExpression, rightCondition.leftExpression];
|
|
||||||
leftConditionExpression = leftCondition.rightExpression;
|
|
||||||
|
|
||||||
} else if(leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
|
|
||||||
rightCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType) {
|
|
||||||
limits = @[leftCondition.rightExpression, rightCondition.leftExpression];
|
|
||||||
leftConditionExpression = leftCondition.leftExpression;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (limits && leftConditionExpression) {
|
|
||||||
return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression, [NSExpression expressionForAggregate:limits]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
|
|
||||||
}
|
|
||||||
if ([op isEqualToString:@"any"]) {
|
|
||||||
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
|
||||||
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
|
|
||||||
}
|
|
||||||
if ([op isEqualToString:@"within"]) {
|
|
||||||
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects([objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
|
||||||
return [NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
|
|
||||||
rightExpression:subexpressions[0]
|
|
||||||
modifier:NSDirectPredicateModifier
|
|
||||||
type:NSInPredicateOperatorType
|
|
||||||
options:0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object];
|
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
|
||||||
return [NSComparisonPredicate predicateWithLeftExpression:expression
|
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
||||||
rightExpression:[NSExpression expressionForConstantValue:@YES]
|
return [NSComparisonPredicate predicateWithLeftExpression:subexpressions[0]
|
||||||
|
rightExpression:subexpressions[1]
|
||||||
modifier:NSDirectPredicateModifier
|
modifier:NSDirectPredicateModifier
|
||||||
type:NSEqualToPredicateOperatorType
|
type:operatorType
|
||||||
options:0];
|
options:options];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([op isEqualToString:@"!"]) {
|
||||||
|
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
|
||||||
|
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
||||||
|
if (subpredicates.count > 1) {
|
||||||
|
NSCompoundPredicate *predicate =
|
||||||
|
[NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
|
||||||
|
return [NSCompoundPredicate notPredicateWithSubpredicate:predicate];
|
||||||
|
}
|
||||||
|
if (subpredicates.count) {
|
||||||
|
return [NSCompoundPredicate notPredicateWithSubpredicate:subpredicates.firstObject];
|
||||||
|
}
|
||||||
|
return [NSPredicate predicateWithValue:YES];
|
||||||
|
}
|
||||||
|
if ([op isEqualToString:@"all"]) {
|
||||||
|
NSArray<NSPredicate *> *subpredicates = MLNSubpredicatesWithJSONObjects(
|
||||||
|
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
||||||
|
if (subpredicates.count == 2) {
|
||||||
|
// Determine if the expression is of BETWEEN type
|
||||||
|
if ([subpredicates[0] isKindOfClass:[NSComparisonPredicate class]] &&
|
||||||
|
[subpredicates[1] isKindOfClass:[NSComparisonPredicate class]]) {
|
||||||
|
NSComparisonPredicate *leftCondition = (NSComparisonPredicate *)subpredicates[0];
|
||||||
|
NSComparisonPredicate *rightCondition = (NSComparisonPredicate *)subpredicates[1];
|
||||||
|
|
||||||
|
NSArray *limits;
|
||||||
|
NSExpression *leftConditionExpression;
|
||||||
|
|
||||||
|
if (leftCondition.predicateOperatorType == NSGreaterThanOrEqualToPredicateOperatorType &&
|
||||||
|
rightCondition.predicateOperatorType == NSLessThanOrEqualToPredicateOperatorType) {
|
||||||
|
limits = @[ leftCondition.rightExpression, rightCondition.rightExpression ];
|
||||||
|
leftConditionExpression = leftCondition.leftExpression;
|
||||||
|
|
||||||
|
} else if (leftCondition.predicateOperatorType ==
|
||||||
|
NSLessThanOrEqualToPredicateOperatorType &&
|
||||||
|
rightCondition.predicateOperatorType ==
|
||||||
|
NSLessThanOrEqualToPredicateOperatorType) {
|
||||||
|
limits = @[ leftCondition.leftExpression, rightCondition.rightExpression ];
|
||||||
|
leftConditionExpression = leftCondition.rightExpression;
|
||||||
|
|
||||||
|
} else if (leftCondition.predicateOperatorType ==
|
||||||
|
NSLessThanOrEqualToPredicateOperatorType &&
|
||||||
|
rightCondition.predicateOperatorType ==
|
||||||
|
NSGreaterThanOrEqualToPredicateOperatorType) {
|
||||||
|
limits = @[ leftCondition.leftExpression, rightCondition.leftExpression ];
|
||||||
|
leftConditionExpression = leftCondition.rightExpression;
|
||||||
|
|
||||||
|
} else if (leftCondition.predicateOperatorType ==
|
||||||
|
NSGreaterThanOrEqualToPredicateOperatorType &&
|
||||||
|
rightCondition.predicateOperatorType ==
|
||||||
|
NSGreaterThanOrEqualToPredicateOperatorType) {
|
||||||
|
limits = @[ leftCondition.rightExpression, rightCondition.leftExpression ];
|
||||||
|
leftConditionExpression = leftCondition.leftExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limits && leftConditionExpression) {
|
||||||
|
return [NSPredicate predicateWithFormat:@"%@ BETWEEN %@", leftConditionExpression,
|
||||||
|
[NSExpression expressionForAggregate:limits]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];
|
||||||
|
}
|
||||||
|
if ([op isEqualToString:@"any"]) {
|
||||||
|
NSArray *subpredicates = MLNSubpredicatesWithJSONObjects(
|
||||||
|
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
||||||
|
return [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
|
||||||
|
}
|
||||||
|
if ([op isEqualToString:@"within"]) {
|
||||||
|
NSArray *subexpressions = MLNSubexpressionsWithJSONObjects(
|
||||||
|
[objects subarrayWithRange:NSMakeRange(1, objects.count - 1)]);
|
||||||
|
return [NSComparisonPredicate
|
||||||
|
predicateWithLeftExpression:[NSExpression expressionForEvaluatedObject]
|
||||||
|
rightExpression:subexpressions[0]
|
||||||
|
modifier:NSDirectPredicateModifier
|
||||||
|
type:NSInPredicateOperatorType
|
||||||
|
options:0];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSExpression *expression = [NSExpression expressionWithMLNJSONObject:object];
|
||||||
|
return [NSComparisonPredicate
|
||||||
|
predicateWithLeftExpression:expression
|
||||||
|
rightExpression:[NSExpression expressionForConstantValue:@YES]
|
||||||
|
modifier:NSDirectPredicateModifier
|
||||||
|
type:NSEqualToPredicateOperatorType
|
||||||
|
options:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)mgl_jsonExpressionObject {
|
- (id)mgl_jsonExpressionObject {
|
||||||
if ([self isEqual:[NSPredicate predicateWithValue:YES]]) {
|
if ([self isEqual:[NSPredicate predicateWithValue:YES]]) {
|
||||||
return @YES;
|
return @YES;
|
||||||
}
|
}
|
||||||
if ([self isEqual:[NSPredicate predicateWithValue:NO]]) {
|
if ([self isEqual:[NSPredicate predicateWithValue:NO]]) {
|
||||||
return @NO;
|
return @NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) {
|
|
||||||
[NSException raise:NSInvalidArgumentException
|
|
||||||
format:@"Block-based predicates are not supported."];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ([self.predicateFormat hasPrefix:@"BLOCKPREDICATE("]) {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Unrecognized predicate type."];
|
format:@"Block-based predicates are not supported."];
|
||||||
return nil;
|
}
|
||||||
|
|
||||||
|
[NSException raise:NSInvalidArgumentException format:@"Unrecognized predicate type."];
|
||||||
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -196,34 +212,33 @@ static NSDictionary * const MLNPredicateOperatorTypesByJSONOperator = @{
|
|||||||
@implementation NSPredicate (MLNExpressionAdditions)
|
@implementation NSPredicate (MLNExpressionAdditions)
|
||||||
|
|
||||||
- (id)mgl_if:(id)firstValue, ... {
|
- (id)mgl_if:(id)firstValue, ... {
|
||||||
|
if ([self evaluateWithObject:nil]) {
|
||||||
|
return firstValue;
|
||||||
|
}
|
||||||
|
|
||||||
if ([self evaluateWithObject:nil]) {
|
id eachExpression;
|
||||||
return firstValue;
|
va_list argumentList;
|
||||||
}
|
va_start(argumentList, firstValue);
|
||||||
|
|
||||||
id eachExpression;
|
while ((eachExpression = va_arg(argumentList, id))) {
|
||||||
va_list argumentList;
|
if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) {
|
||||||
va_start(argumentList, firstValue);
|
id valueExpression = va_arg(argumentList, id);
|
||||||
|
if ([eachExpression evaluateWithObject:nil]) {
|
||||||
|
return valueExpression;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return eachExpression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_end(argumentList);
|
||||||
|
|
||||||
while ((eachExpression = va_arg(argumentList, id))) {
|
return nil;
|
||||||
if ([eachExpression isKindOfClass:[NSComparisonPredicate class]]) {
|
|
||||||
id valueExpression = va_arg(argumentList, id);
|
|
||||||
if ([eachExpression evaluateWithObject:nil]) {
|
|
||||||
return valueExpression;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return eachExpression;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
va_end(argumentList);
|
|
||||||
|
|
||||||
return nil;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)mgl_match:(NSExpression *)firstCase, ... {
|
- (id)mgl_match:(NSExpression *)firstCase, ... {
|
||||||
[NSException raise:NSInvalidArgumentException
|
[NSException raise:NSInvalidArgumentException
|
||||||
format:@"Match expressions lack underlying Objective-C implementations."];
|
format:@"Match expressions lack underlying Objective-C implementations."];
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,76 +1,73 @@
|
|||||||
#import "NSValue+MLNStyleAttributeAdditions.h"
|
#import "MLNGeometry_Private.h"
|
||||||
#import "MLNLight.h"
|
#import "MLNLight.h"
|
||||||
#import "MLNLoggingConfiguration_Private.h"
|
#import "MLNLoggingConfiguration_Private.h"
|
||||||
#import "MLNGeometry_Private.h"
|
#import "NSValue+MLNStyleAttributeAdditions.h"
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@implementation NSValue (MLNStyleAttributeAdditions)
|
@implementation NSValue (MLNStyleAttributeAdditions)
|
||||||
|
|
||||||
+ (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray
|
+ (instancetype)mgl_valueWithOffsetArray:(std::array<float, 2>)offsetArray {
|
||||||
{
|
CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]);
|
||||||
CGVector vector = CGVectorMake(offsetArray[0], offsetArray[1]);
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
// Style specification assumes an origin at the upper-left corner.
|
// Style specification assumes an origin at the upper-left corner.
|
||||||
// macOS defines an origin at the lower-left corner.
|
// macOS defines an origin at the lower-left corner.
|
||||||
vector.dy *= -1;
|
vector.dy *= -1;
|
||||||
#endif
|
#endif
|
||||||
return [NSValue value:&vector withObjCType:@encode(CGVector)];
|
return [NSValue value:&vector withObjCType:@encode(CGVector)];
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray
|
+ (instancetype)mgl_valueWithPaddingArray:(std::array<float, 4>)paddingArray {
|
||||||
{
|
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
||||||
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
// Foundation defines padding in counterclockwise order: top, left, bottom, right.
|
||||||
// Foundation defines padding in counterclockwise order: top, left, bottom, right.
|
MLNEdgeInsets insets = {
|
||||||
MLNEdgeInsets insets = {
|
.top = paddingArray[0],
|
||||||
.top = paddingArray[0],
|
.right = paddingArray[1],
|
||||||
.right = paddingArray[1],
|
.bottom = paddingArray[2],
|
||||||
.bottom = paddingArray[2],
|
.left = paddingArray[3],
|
||||||
.left = paddingArray[3],
|
};
|
||||||
};
|
return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)];
|
||||||
return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::array<float, 2>)mgl_offsetArrayValue
|
- (std::array<float, 2>)mgl_offsetArrayValue {
|
||||||
{
|
MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector");
|
||||||
MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector");
|
CGVector vector;
|
||||||
CGVector vector;
|
[self getValue:&vector];
|
||||||
[self getValue:&vector];
|
|
||||||
#if !TARGET_OS_IPHONE
|
#if !TARGET_OS_IPHONE
|
||||||
vector.dy *= -1;
|
vector.dy *= -1;
|
||||||
#endif
|
#endif
|
||||||
return {
|
return {
|
||||||
static_cast<float>(vector.dx),
|
static_cast<float>(vector.dx),
|
||||||
static_cast<float>(vector.dy),
|
static_cast<float>(vector.dy),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::array<float, 4>)mgl_paddingArrayValue
|
- (std::array<float, 4>)mgl_paddingArrayValue {
|
||||||
{
|
MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0,
|
||||||
MLNAssert(strcmp(self.objCType, @encode(MLNEdgeInsets)) == 0, @"Value does not represent an NSEdgeInsets/UIEdgeInsets");
|
@"Value does not represent an NSEdgeInsets/UIEdgeInsets");
|
||||||
MLNEdgeInsets insets;
|
MLNEdgeInsets insets;
|
||||||
[self getValue:&insets];
|
[self getValue:&insets];
|
||||||
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
||||||
return {
|
return {
|
||||||
static_cast<float>(insets.top),
|
static_cast<float>(insets.top),
|
||||||
static_cast<float>(insets.right),
|
static_cast<float>(insets.right),
|
||||||
static_cast<float>(insets.bottom),
|
static_cast<float>(insets.bottom),
|
||||||
static_cast<float>(insets.left),
|
static_cast<float>(insets.left),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
- (std::array<float, 3>)mgl_lightPositionArrayValue
|
- (std::array<float, 3>)mgl_lightPositionArrayValue {
|
||||||
{
|
MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0,
|
||||||
MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, @"Value does not represent an MLNSphericalPosition");
|
@"Value does not represent an MLNSphericalPosition");
|
||||||
MLNSphericalPosition lightPosition;
|
MLNSphericalPosition lightPosition;
|
||||||
[self getValue:&lightPosition];
|
[self getValue:&lightPosition];
|
||||||
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
// Style specification defines padding in clockwise order: top, right, bottom, left.
|
||||||
return {
|
return {
|
||||||
static_cast<float>(lightPosition.radial),
|
static_cast<float>(lightPosition.radial),
|
||||||
static_cast<float>(lightPosition.azimuthal),
|
static_cast<float>(lightPosition.azimuthal),
|
||||||
static_cast<float>(lightPosition.polar),
|
static_cast<float>(lightPosition.polar),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
#include <mbgl/gl/headless_backend.hpp>
|
#include <mbgl/gl/headless_backend.hpp>
|
||||||
#include <mbgl/util/logging.hpp>
|
#include <mbgl/util/logging.hpp>
|
||||||
|
|
||||||
#include <OpenGL/OpenGL.h>
|
|
||||||
#include <Foundation/NSString.h>
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
#include <Foundation/NSString.h>
|
||||||
|
#include <OpenGL/OpenGL.h>
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
namespace gl {
|
namespace gl {
|
||||||
@@ -15,113 +15,113 @@ namespace gl {
|
|||||||
// instantiating new headless rendering contexts.
|
// instantiating new headless rendering contexts.
|
||||||
class CGLDisplayConfig {
|
class CGLDisplayConfig {
|
||||||
private:
|
private:
|
||||||
// Key for singleton construction.
|
// Key for singleton construction.
|
||||||
struct Key { explicit Key() = default; };
|
struct Key {
|
||||||
|
explicit Key() = default;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CGLDisplayConfig(Key) {
|
CGLDisplayConfig(Key) {
|
||||||
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
|
// TODO: test if OpenGL 4.1 with GL_ARB_ES2_compatibility is supported
|
||||||
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
|
// If it is, use kCGLOGLPVersion_3_2_Core and enable that extension.
|
||||||
CGLPixelFormatAttribute attributes[] = {
|
CGLPixelFormatAttribute attributes[] = {
|
||||||
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
|
kCGLPFAOpenGLProfile, static_cast<CGLPixelFormatAttribute>(kCGLOGLPVersion_Legacy),
|
||||||
// Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when running in VMs.
|
// Not adding kCGLPFAAccelerated, as this will make headless rendering impossible when
|
||||||
kCGLPFAClosestPolicy,
|
// running in VMs.
|
||||||
kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
|
kCGLPFAClosestPolicy, kCGLPFAAccumSize, static_cast<CGLPixelFormatAttribute>(32),
|
||||||
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24),
|
kCGLPFAColorSize, static_cast<CGLPixelFormatAttribute>(24), kCGLPFAAlphaSize,
|
||||||
kCGLPFAAlphaSize, static_cast<CGLPixelFormatAttribute>(8),
|
static_cast<CGLPixelFormatAttribute>(8), kCGLPFADepthSize,
|
||||||
kCGLPFADepthSize, static_cast<CGLPixelFormatAttribute>(16),
|
static_cast<CGLPixelFormatAttribute>(16), kCGLPFAStencilSize,
|
||||||
kCGLPFAStencilSize, static_cast<CGLPixelFormatAttribute>(8),
|
static_cast<CGLPixelFormatAttribute>(8), kCGLPFASupportsAutomaticGraphicsSwitching,
|
||||||
kCGLPFASupportsAutomaticGraphicsSwitching,
|
kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
|
||||||
kCGLPFAAllowOfflineRenderers, // Allows using the integrated GPU
|
static_cast<CGLPixelFormatAttribute>(0)};
|
||||||
static_cast<CGLPixelFormatAttribute>(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
GLint num;
|
GLint num;
|
||||||
// TODO: obtain all configurations and choose the best one.
|
// TODO: obtain all configurations and choose the best one.
|
||||||
const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
|
const CGLError error = CGLChoosePixelFormat(attributes, &pixelFormat, &num);
|
||||||
if (error != kCGLNoError) {
|
if (error != kCGLNoError) {
|
||||||
throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) + "\n");
|
throw std::runtime_error(std::string("Error choosing pixel format:") + CGLErrorString(error) +
|
||||||
}
|
"\n");
|
||||||
if (num <= 0) {
|
|
||||||
throw std::runtime_error("No pixel formats found.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (num <= 0) {
|
||||||
|
throw std::runtime_error("No pixel formats found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
~CGLDisplayConfig() {
|
~CGLDisplayConfig() {
|
||||||
const CGLError error = CGLDestroyPixelFormat(pixelFormat);
|
const CGLError error = CGLDestroyPixelFormat(pixelFormat);
|
||||||
if (error != kCGLNoError) {
|
if (error != kCGLNoError) {
|
||||||
Log::Error(Event::OpenGL, std::string("Error destroying pixel format:") + CGLErrorString(error));
|
Log::Error(Event::OpenGL,
|
||||||
}
|
std::string("Error destroying pixel format:") + CGLErrorString(error));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static std::shared_ptr<const CGLDisplayConfig> create() {
|
static std::shared_ptr<const CGLDisplayConfig> create() {
|
||||||
static std::weak_ptr<const CGLDisplayConfig> instance;
|
static std::weak_ptr<const CGLDisplayConfig> instance;
|
||||||
auto shared = instance.lock();
|
auto shared = instance.lock();
|
||||||
if (!shared) {
|
if (!shared) {
|
||||||
instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
|
instance = shared = std::make_shared<CGLDisplayConfig>(Key{});
|
||||||
}
|
|
||||||
return shared;
|
|
||||||
}
|
}
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CGLPixelFormatObj pixelFormat = nullptr;
|
CGLPixelFormatObj pixelFormat = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CGLBackendImpl final : public HeadlessBackend::Impl {
|
class CGLBackendImpl final : public HeadlessBackend::Impl {
|
||||||
public:
|
public:
|
||||||
CGLBackendImpl() {
|
CGLBackendImpl() {
|
||||||
CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
|
CGLError error = CGLCreateContext(cglDisplay->pixelFormat, nullptr, &glContext);
|
||||||
if (error != kCGLNoError) {
|
if (error != kCGLNoError) {
|
||||||
throw std::runtime_error(std::string("Error creating GL context object:") +
|
throw std::runtime_error(std::string("Error creating GL context object:") +
|
||||||
CGLErrorString(error) + "\n");
|
CGLErrorString(error) + "\n");
|
||||||
}
|
|
||||||
|
|
||||||
error = CGLEnable(glContext, kCGLCEMPEngine);
|
|
||||||
if (error != kCGLNoError) {
|
|
||||||
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
|
|
||||||
CGLErrorString(error) + "\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~CGLBackendImpl() final {
|
error = CGLEnable(glContext, kCGLCEMPEngine);
|
||||||
CGLDestroyContext(glContext);
|
if (error != kCGLNoError) {
|
||||||
|
throw std::runtime_error(std::string("Error enabling OpenGL multithreading:") +
|
||||||
|
CGLErrorString(error) + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~CGLBackendImpl() final { CGLDestroyContext(glContext); }
|
||||||
|
|
||||||
|
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
|
||||||
|
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
|
||||||
|
if (!framework) {
|
||||||
|
throw std::runtime_error("Failed to load OpenGL framework.");
|
||||||
}
|
}
|
||||||
|
|
||||||
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
|
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
|
||||||
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl"));
|
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
|
||||||
if (!framework) {
|
}
|
||||||
throw std::runtime_error("Failed to load OpenGL framework.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
|
void activateContext() final {
|
||||||
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
|
CGLError error = CGLSetCurrentContext(glContext);
|
||||||
|
if (error != kCGLNoError) {
|
||||||
|
throw std::runtime_error(std::string("Switching OpenGL context failed:") +
|
||||||
|
CGLErrorString(error) + "\n");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void activateContext() final {
|
void deactivateContext() final {
|
||||||
CGLError error = CGLSetCurrentContext(glContext);
|
CGLError error = CGLSetCurrentContext(nullptr);
|
||||||
if (error != kCGLNoError) {
|
if (error != kCGLNoError) {
|
||||||
throw std::runtime_error(std::string("Switching OpenGL context failed:") +
|
throw std::runtime_error(std::string("Removing OpenGL context failed:") +
|
||||||
CGLErrorString(error) + "\n");
|
CGLErrorString(error) + "\n");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deactivateContext() final {
|
|
||||||
CGLError error = CGLSetCurrentContext(nullptr);
|
|
||||||
if (error != kCGLNoError) {
|
|
||||||
throw std::runtime_error(std::string("Removing OpenGL context failed:") +
|
|
||||||
CGLErrorString(error) + "\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
|
const std::shared_ptr<const CGLDisplayConfig> cglDisplay = CGLDisplayConfig::create();
|
||||||
CGLContextObj glContext = nullptr;
|
CGLContextObj glContext = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
void HeadlessBackend::createImpl() {
|
void HeadlessBackend::createImpl() {
|
||||||
assert(!impl);
|
assert(!impl);
|
||||||
impl = std::make_unique<CGLBackendImpl>();
|
impl = std::make_unique<CGLBackendImpl>();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace gl
|
} // namespace gl
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
@@ -9,43 +9,39 @@ namespace gl {
|
|||||||
|
|
||||||
class EAGLBackendImpl : public HeadlessBackend::Impl {
|
class EAGLBackendImpl : public HeadlessBackend::Impl {
|
||||||
public:
|
public:
|
||||||
EAGLBackendImpl() {
|
EAGLBackendImpl() {
|
||||||
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
|
||||||
if (glContext == nil) {
|
if (glContext == nil) {
|
||||||
throw std::runtime_error("Error creating GL context object");
|
throw std::runtime_error("Error creating GL context object");
|
||||||
}
|
}
|
||||||
glContext.multiThreaded = YES;
|
glContext.multiThreaded = YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for ARC to deallocate correctly.
|
||||||
|
~EAGLBackendImpl() final = default;
|
||||||
|
|
||||||
|
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
|
||||||
|
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
|
||||||
|
if (!framework) {
|
||||||
|
throw std::runtime_error("Failed to load OpenGL framework.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required for ARC to deallocate correctly.
|
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
|
||||||
~EAGLBackendImpl() final = default;
|
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
|
||||||
|
}
|
||||||
|
|
||||||
gl::ProcAddress getExtensionFunctionPointer(const char* name) final {
|
void activateContext() final { [EAGLContext setCurrentContext:glContext]; }
|
||||||
static CFBundleRef framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengles"));
|
|
||||||
if (!framework) {
|
|
||||||
throw std::runtime_error("Failed to load OpenGL framework.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return reinterpret_cast<gl::ProcAddress>(CFBundleGetFunctionPointerForName(
|
void deactivateContext() final { [EAGLContext setCurrentContext:nil]; }
|
||||||
framework, (__bridge CFStringRef)[NSString stringWithUTF8String:name]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void activateContext() final {
|
|
||||||
[EAGLContext setCurrentContext:glContext];
|
|
||||||
}
|
|
||||||
|
|
||||||
void deactivateContext() final {
|
|
||||||
[EAGLContext setCurrentContext:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EAGLContext* glContext = nullptr;
|
EAGLContext* glContext = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
void HeadlessBackend::createImpl() {
|
void HeadlessBackend::createImpl() {
|
||||||
assert(!impl);
|
assert(!impl);
|
||||||
impl = std::make_unique<EAGLBackendImpl>();
|
impl = std::make_unique<EAGLBackendImpl>();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace gl
|
} // namespace gl
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@
|
|||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#import <mbgl/util/geometry.hpp>
|
#import <mbgl/util/geometry.hpp>
|
||||||
#import "MLNFoundation_Private.h"
|
|
||||||
#import "../../darwin/src/MLNFeature_Private.h"
|
#import "../../darwin/src/MLNFeature_Private.h"
|
||||||
|
#import "MLNFoundation_Private.h"
|
||||||
|
|
||||||
@interface MLNFeatureTests : XCTestCase
|
@interface MLNFeatureTests : XCTestCase
|
||||||
|
|
||||||
@@ -12,349 +12,390 @@
|
|||||||
@implementation MLNFeatureTests
|
@implementation MLNFeatureTests
|
||||||
|
|
||||||
- (void)testGeometryConversion {
|
- (void)testGeometryConversion {
|
||||||
std::vector<mbgl::Feature> features;
|
std::vector<mbgl::Feature> features;
|
||||||
|
|
||||||
mbgl::Point<double> point = { -90.066667, 29.95 };
|
mbgl::Point<double> point = {-90.066667, 29.95};
|
||||||
features.push_back(mbgl::Feature { point });
|
features.push_back(mbgl::Feature{point});
|
||||||
|
|
||||||
mbgl::LineString<double> lineString = {
|
mbgl::LineString<double> lineString = {
|
||||||
{ -84.516667, 39.1 },
|
{-84.516667, 39.1},
|
||||||
{ -90.066667, 29.95 },
|
{-90.066667, 29.95},
|
||||||
};
|
};
|
||||||
features.push_back(mbgl::Feature { lineString });
|
features.push_back(mbgl::Feature{lineString});
|
||||||
|
|
||||||
mbgl::Polygon<double> polygon = {
|
mbgl::Polygon<double> polygon = {
|
||||||
{
|
{
|
||||||
{ 1, 1 },
|
{1, 1},
|
||||||
{ 4, 1 },
|
{4, 1},
|
||||||
{ 4, 4 },
|
{4, 4},
|
||||||
{ 1, 4 },
|
{1, 4},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{ 2, 2 },
|
{2, 2},
|
||||||
{ 3, 2 },
|
{3, 2},
|
||||||
{ 3, 3 },
|
{3, 3},
|
||||||
{ 2, 3 },
|
{2, 3},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
features.push_back(mbgl::Feature { polygon });
|
features.push_back(mbgl::Feature{polygon});
|
||||||
|
|
||||||
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
|
NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
|
||||||
XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes");
|
XCTAssertEqual(shapes.count, 3UL, @"All features should be converted into shapes");
|
||||||
|
|
||||||
MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0];
|
MLNPointFeature *pointShape = (MLNPointFeature *)shapes[0];
|
||||||
XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]);
|
XCTAssertTrue([pointShape isKindOfClass:[MLNPointFeature class]]);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:pointShape.coordinate],
|
XCTAssertEqualObjects(
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
[NSValue valueWithMLNCoordinate:pointShape.coordinate],
|
||||||
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
||||||
|
|
||||||
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
|
MLNPolylineFeature *polylineShape = (MLNPolylineFeature *)shapes[1];
|
||||||
XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]);
|
XCTAssertTrue([polylineShape isKindOfClass:[MLNPolylineFeature class]]);
|
||||||
XCTAssertEqual(polylineShape.pointCount, 2UL);
|
XCTAssertEqual(polylineShape.pointCount, 2UL);
|
||||||
CLLocationCoordinate2D polylineCoordinates[2];
|
CLLocationCoordinate2D polylineCoordinates[2];
|
||||||
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
|
[polylineShape getCoordinates:polylineCoordinates range:NSMakeRange(0, polylineShape.pointCount)];
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
|
XCTAssertEqualObjects(
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
|
[NSValue valueWithMLNCoordinate:polylineCoordinates[0]],
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(39.1, -84.516667)]);
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
XCTAssertEqualObjects(
|
||||||
|
[NSValue valueWithMLNCoordinate:polylineCoordinates[1]],
|
||||||
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(29.95, -90.066667)]);
|
||||||
|
|
||||||
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
|
MLNPolygonFeature *polygonShape = (MLNPolygonFeature *)shapes[2];
|
||||||
XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]);
|
XCTAssertTrue([polygonShape isKindOfClass:[MLNPolygonFeature class]]);
|
||||||
XCTAssertEqual(polygonShape.pointCount, 4UL);
|
XCTAssertEqual(polygonShape.pointCount, 4UL);
|
||||||
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
|
CLLocationCoordinate2D *polygonCoordinates = polygonShape.coordinates;
|
||||||
XCTAssertNotEqual(polygonCoordinates, nil);
|
XCTAssertNotEqual(polygonCoordinates, nil);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[0]],
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]);
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 1)]);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[1]],
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]);
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(1, 4)]);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[2]],
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]);
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 4)]);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:polygonCoordinates[3]],
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]);
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(4, 1)]);
|
||||||
NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons;
|
NSArray<MLNPolygon *> *interiorPolygons = polygonShape.interiorPolygons;
|
||||||
XCTAssertEqual(interiorPolygons.count, 1UL);
|
XCTAssertEqual(interiorPolygons.count, 1UL);
|
||||||
MLNPolygon *interiorPolygon = interiorPolygons.firstObject;
|
MLNPolygon *interiorPolygon = interiorPolygons.firstObject;
|
||||||
XCTAssertEqual(interiorPolygon.pointCount, 4UL);
|
XCTAssertEqual(interiorPolygon.pointCount, 4UL);
|
||||||
CLLocationCoordinate2D interiorPolygonCoordinates[4];
|
CLLocationCoordinate2D interiorPolygonCoordinates[4];
|
||||||
[interiorPolygon getCoordinates:interiorPolygonCoordinates range:NSMakeRange(0, interiorPolygon.pointCount)];
|
[interiorPolygon getCoordinates:interiorPolygonCoordinates
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
|
range:NSMakeRange(0, interiorPolygon.pointCount)];
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[0]],
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 2)]);
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]);
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[1]],
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]],
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(2, 3)]);
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]);
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[2]],
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]],
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 3)]);
|
||||||
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]);
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:interiorPolygonCoordinates[3]],
|
||||||
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(3, 2)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testClusterGeometryConversion {
|
- (void)testClusterGeometryConversion {
|
||||||
mbgl::Point<double> point = { -90.066667, 29.95 };
|
mbgl::Point<double> point = {-90.066667, 29.95};
|
||||||
mbgl::Feature pointFeature { point };
|
mbgl::Feature pointFeature{point};
|
||||||
pointFeature.id = { UINT64_MAX };
|
pointFeature.id = {UINT64_MAX};
|
||||||
pointFeature.properties["cluster"] = true;
|
pointFeature.properties["cluster"] = true;
|
||||||
pointFeature.properties["cluster_id"] = 1ULL;
|
pointFeature.properties["cluster_id"] = 1ULL;
|
||||||
pointFeature.properties["point_count"] = 5ULL;
|
pointFeature.properties["point_count"] = 5ULL;
|
||||||
|
|
||||||
id<MLNFeature> feature = MLNFeatureFromMBGLFeature(pointFeature);
|
id<MLNFeature> feature = MLNFeatureFromMBGLFeature(pointFeature);
|
||||||
|
|
||||||
XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]);
|
XCTAssert([feature conformsToProtocol:@protocol(MLNFeature)]);
|
||||||
|
|
||||||
id<MLNCluster> cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster);
|
id<MLNCluster> cluster = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(feature, MLNCluster);
|
||||||
XCTAssert(cluster);
|
XCTAssert(cluster);
|
||||||
XCTAssert(cluster.clusterIdentifier == 1);
|
XCTAssert(cluster.clusterIdentifier == 1);
|
||||||
XCTAssert(cluster.clusterPointCount == 5);
|
XCTAssert(cluster.clusterPointCount == 5);
|
||||||
|
|
||||||
XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]);
|
XCTAssert([cluster isMemberOfClass:[MLNPointFeatureCluster class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPropertyConversion {
|
- (void)testPropertyConversion {
|
||||||
std::vector<mbgl::Feature> features;
|
std::vector<mbgl::Feature> features;
|
||||||
|
|
||||||
mbgl::Point<double> point = { -90.066667, 29.95 };
|
mbgl::Point<double> point = {-90.066667, 29.95};
|
||||||
mbgl::Feature pointFeature { point };
|
mbgl::Feature pointFeature{point};
|
||||||
pointFeature.id = { UINT64_MAX };
|
pointFeature.id = {UINT64_MAX};
|
||||||
pointFeature.properties["null"] = mapbox::feature::null_value;
|
pointFeature.properties["null"] = mapbox::feature::null_value;
|
||||||
pointFeature.properties["bool"] = true;
|
pointFeature.properties["bool"] = true;
|
||||||
pointFeature.properties["unsigned int"] = UINT64_MAX;
|
pointFeature.properties["unsigned int"] = UINT64_MAX;
|
||||||
pointFeature.properties["int"] = INT64_MIN;
|
pointFeature.properties["int"] = INT64_MIN;
|
||||||
pointFeature.properties["double"] = DBL_MAX;
|
pointFeature.properties["double"] = DBL_MAX;
|
||||||
pointFeature.properties["string"] = std::string("🚏");
|
pointFeature.properties["string"] = std::string("🚏");
|
||||||
std::vector<bool> vector;
|
std::vector<bool> vector;
|
||||||
vector.push_back(true);
|
vector.push_back(true);
|
||||||
vector.push_back(false);
|
vector.push_back(false);
|
||||||
vector.push_back(true);
|
vector.push_back(true);
|
||||||
features.push_back(pointFeature);
|
features.push_back(pointFeature);
|
||||||
|
|
||||||
NSArray<MLNShape <MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
|
NSArray<MLNShape<MLNFeature> *> *shapes = MLNFeaturesFromMBGLFeatures(features);
|
||||||
XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes");
|
XCTAssertEqual(shapes.count, 1UL, @"All features should be converted into shapes");
|
||||||
|
|
||||||
MLNShape <MLNFeature> *shape = shapes.firstObject;
|
MLNShape<MLNFeature> *shape = shapes.firstObject;
|
||||||
XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]);
|
XCTAssertTrue([shape conformsToProtocol:@protocol(MLNFeature)]);
|
||||||
XCTAssertTrue([shape isKindOfClass:[MLNShape class]]);
|
XCTAssertTrue([shape isKindOfClass:[MLNShape class]]);
|
||||||
|
|
||||||
NSNumber *identifier = shape.identifier;
|
NSNumber *identifier = shape.identifier;
|
||||||
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]], @"Feature identifier should be NSNumber");
|
XCTAssertTrue([identifier isKindOfClass:[NSNumber class]],
|
||||||
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0, @"Feature identifier should be 64-bit unsigned integer");
|
@"Feature identifier should be NSNumber");
|
||||||
|
XCTAssertEqual(strcmp(identifier.objCType, @encode(uint64_t)), 0,
|
||||||
|
@"Feature identifier should be 64-bit unsigned integer");
|
||||||
|
|
||||||
NSNull *null = [shape attributeForKey:@"null"];
|
NSNull *null = [shape attributeForKey:@"null"];
|
||||||
XCTAssertNotNil(null);
|
XCTAssertNotNil(null);
|
||||||
XCTAssertTrue([null isKindOfClass:[NSNull class]]);
|
XCTAssertTrue([null isKindOfClass:[NSNull class]]);
|
||||||
XCTAssertEqual(null, shape.attributes[@"null"]);
|
XCTAssertEqual(null, shape.attributes[@"null"]);
|
||||||
|
|
||||||
NSNumber *boolean = [shape attributeForKey:@"bool"];
|
NSNumber *boolean = [shape attributeForKey:@"bool"];
|
||||||
XCTAssertNotNil(boolean);
|
XCTAssertNotNil(boolean);
|
||||||
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
|
XCTAssertTrue([boolean isKindOfClass:[NSNumber class]]);
|
||||||
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0, @"Boolean property should be converted to bool NSNumber");
|
XCTAssertEqual(strcmp(boolean.objCType, @encode(char)), 0,
|
||||||
XCTAssertTrue(boolean.boolValue);
|
@"Boolean property should be converted to bool NSNumber");
|
||||||
XCTAssertEqual(boolean, shape.attributes[@"bool"]);
|
XCTAssertTrue(boolean.boolValue);
|
||||||
|
XCTAssertEqual(boolean, shape.attributes[@"bool"]);
|
||||||
|
|
||||||
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
|
NSNumber *unsignedInteger = [shape attributeForKey:@"unsigned int"];
|
||||||
XCTAssertNotNil(unsignedInteger);
|
XCTAssertNotNil(unsignedInteger);
|
||||||
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]);
|
XCTAssertTrue([unsignedInteger isKindOfClass:[NSNumber class]]);
|
||||||
XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0, @"Unsigned integer property should be converted to unsigned long long NSNumber");
|
XCTAssertEqual(strcmp(unsignedInteger.objCType, @encode(uint64_t)), 0,
|
||||||
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
|
@"Unsigned integer property should be converted to unsigned long long NSNumber");
|
||||||
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
|
XCTAssertEqual(unsignedInteger.unsignedLongLongValue, UINT64_MAX);
|
||||||
|
XCTAssertEqual(unsignedInteger, shape.attributes[@"unsigned int"]);
|
||||||
|
|
||||||
NSNumber *integer = [shape attributeForKey:@"int"];
|
NSNumber *integer = [shape attributeForKey:@"int"];
|
||||||
XCTAssertNotNil(integer);
|
XCTAssertNotNil(integer);
|
||||||
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
|
XCTAssertTrue([integer isKindOfClass:[NSNumber class]]);
|
||||||
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0, @"Integer property should be converted to long long NSNumber");
|
XCTAssertEqual(strcmp(integer.objCType, @encode(int64_t)), 0,
|
||||||
XCTAssertEqual(integer.longLongValue, INT64_MIN);
|
@"Integer property should be converted to long long NSNumber");
|
||||||
XCTAssertEqual(integer, shape.attributes[@"int"]);
|
XCTAssertEqual(integer.longLongValue, INT64_MIN);
|
||||||
|
XCTAssertEqual(integer, shape.attributes[@"int"]);
|
||||||
|
|
||||||
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
|
NSNumber *floatingPointNumber = [shape attributeForKey:@"double"];
|
||||||
XCTAssertNotNil(floatingPointNumber);
|
XCTAssertNotNil(floatingPointNumber);
|
||||||
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
|
XCTAssertTrue([floatingPointNumber isKindOfClass:[NSNumber class]]);
|
||||||
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0, @"Floating-point number property should be converted to double NSNumber");
|
XCTAssertEqual(strcmp(floatingPointNumber.objCType, @encode(double)), 0,
|
||||||
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
|
@"Floating-point number property should be converted to double NSNumber");
|
||||||
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
|
XCTAssertEqual(floatingPointNumber.doubleValue, DBL_MAX);
|
||||||
|
XCTAssertEqual(floatingPointNumber, shape.attributes[@"double"]);
|
||||||
|
|
||||||
NSString *string = [shape attributeForKey:@"string"];
|
NSString *string = [shape attributeForKey:@"string"];
|
||||||
XCTAssertNotNil(string);
|
XCTAssertNotNil(string);
|
||||||
XCTAssertTrue([string isKindOfClass:[NSString class]]);
|
XCTAssertTrue([string isKindOfClass:[NSString class]]);
|
||||||
XCTAssertEqualObjects(string, @"🚏");
|
XCTAssertEqualObjects(string, @"🚏");
|
||||||
XCTAssertEqual(string, shape.attributes[@"string"]);
|
XCTAssertEqual(string, shape.attributes[@"string"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPointFeatureGeoJSONDictionary {
|
- (void)testPointFeatureGeoJSONDictionary {
|
||||||
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
|
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
|
||||||
CLLocationCoordinate2D coordinate = { 10, 10 };
|
CLLocationCoordinate2D coordinate = {10, 10};
|
||||||
pointFeature.coordinate = coordinate;
|
pointFeature.coordinate = coordinate;
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
// when there are no identifier or properties
|
// when there are no identifier or properties
|
||||||
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [pointFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct type
|
// it has the correct type
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
|
XCTAssertEqualObjects(geoJSONFeature[@"type"], @"Feature");
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"Point",
|
NSDictionary *expectedGeometry =
|
||||||
@"coordinates": @[@(coordinate.longitude), @(coordinate.latitude)]};
|
@{@"type" : @"Point", @"coordinates" : @[ @(coordinate.longitude), @(coordinate.latitude) ]};
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
// it has no "id" key (or value)
|
// it has no "id" key (or value)
|
||||||
XCTAssertNil(geoJSONFeature[@"id"]);
|
XCTAssertNil(geoJSONFeature[@"id"]);
|
||||||
// it has a null representation of the properties object
|
// it has a null representation of the properties object
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{});
|
XCTAssertEqualObjects(geoJSONFeature[@"properties"], @{});
|
||||||
|
|
||||||
// when there is a string identifier
|
// when there is a string identifier
|
||||||
pointFeature.identifier = @"string-id";
|
pointFeature.identifier = @"string-id";
|
||||||
|
|
||||||
// it has the identifier in the result
|
// it has the identifier in the result
|
||||||
geoJSONFeature = [pointFeature geoJSONDictionary];
|
geoJSONFeature = [pointFeature geoJSONDictionary];
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
|
XCTAssertEqualObjects(geoJSONFeature[@"id"], pointFeature.identifier);
|
||||||
|
|
||||||
// when there are properties
|
// when there are properties
|
||||||
pointFeature.attributes = @{@"name": @"name-value"};
|
pointFeature.attributes = @{@"name" : @"name-value"};
|
||||||
|
|
||||||
// it has the properties value in the result
|
// it has the properties value in the result
|
||||||
geoJSONFeature = [pointFeature geoJSONDictionary];
|
geoJSONFeature = [pointFeature geoJSONDictionary];
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
|
XCTAssertEqualObjects(geoJSONFeature[@"properties"], pointFeature.attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPolylineFeatureGeoJSONDictionary {
|
- (void)testPolylineFeatureGeoJSONDictionary {
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2};
|
||||||
MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
|
MLNPolylineFeature *polyLineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [polyLineFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"LineString",
|
NSDictionary *expectedGeometry = @{
|
||||||
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
@"type" : @"LineString",
|
||||||
@[@(coord2.longitude), @(coord2.latitude)]]};
|
@"coordinates" : @[
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPolygonFeatureGeoJSONDictionary {
|
- (void)testPolygonFeatureGeoJSONDictionary {
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coord3 = { 0, 0 };
|
CLLocationCoordinate2D coord3 = {0, 0};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
|
||||||
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3];
|
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coords count:3];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [polygonFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"Polygon",
|
NSDictionary *expectedGeometry = @{
|
||||||
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
|
@"type" : @"Polygon",
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
@"coordinates" : @[ @[
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]]]};
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
|
] ]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMultiPolylineFeatureGeoJSONDictionary {
|
- (void)testMultiPolylineFeatureGeoJSONDictionary {
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coord3 = { 0, 0 };
|
CLLocationCoordinate2D coord3 = {0, 0};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
|
||||||
|
|
||||||
MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3];
|
MLNPolyline *polyLine1 = [MLNPolyline polylineWithCoordinates:coords count:3];
|
||||||
MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3];
|
MLNPolyline *polyLine2 = [MLNPolyline polylineWithCoordinates:coords count:3];
|
||||||
|
|
||||||
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polyLine1, polyLine2]];
|
MLNMultiPolylineFeature *multiPolylineFeature =
|
||||||
|
[MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polyLine1, polyLine2 ]];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"MultiLineString",
|
NSDictionary *expectedGeometry = @{
|
||||||
@"coordinates": @[@[@[@(coord1.longitude), @(coord1.latitude)],
|
@"type" : @"MultiLineString",
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
@"coordinates" : @[
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]],
|
@[
|
||||||
@[@[@(coord1.longitude), @(coord1.latitude)],
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]]]};
|
],
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
@[
|
||||||
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMultiPolygonFeatureGeoJSONDictionary {
|
- (void)testMultiPolygonFeatureGeoJSONDictionary {
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coord3 = { 0, 0 };
|
CLLocationCoordinate2D coord3 = {0, 0};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
|
||||||
|
|
||||||
MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3];
|
MLNPolygon *polygon1 = [MLNPolygon polygonWithCoordinates:coords count:3];
|
||||||
MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3];
|
MLNPolygon *polygon2 = [MLNPolygon polygonWithCoordinates:coords count:3];
|
||||||
|
|
||||||
MLNMultiPolygonFeature *multiPolylineFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygon1, polygon2]];
|
MLNMultiPolygonFeature *multiPolylineFeature =
|
||||||
|
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygon1, polygon2 ]];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [multiPolylineFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"MultiPolygon",
|
NSDictionary *expectedGeometry = @{
|
||||||
@"coordinates": @[
|
@"type" : @"MultiPolygon",
|
||||||
@[@[@[@(coord1.longitude), @(coord1.latitude)],
|
@"coordinates" : @[
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
@[ @[
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]]],
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
@[@[@[@(coord1.longitude), @(coord1.latitude)],
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
] ],
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]]]]};
|
@[ @[
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
|
] ]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPointCollectionFeatureGeoJSONDictionary {
|
- (void)testPointCollectionFeatureGeoJSONDictionary {
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coord3 = { 0, 0 };
|
CLLocationCoordinate2D coord3 = {0, 0};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2, coord3 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2, coord3};
|
||||||
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
|
MLNPointCollectionFeature *pointCollectionFeature =
|
||||||
|
[MLNPointCollectionFeature pointCollectionWithCoordinates:coords count:3];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [pointCollectionFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"MultiPoint",
|
|
||||||
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
|
||||||
@[@(coord2.longitude), @(coord2.latitude)],
|
|
||||||
@[@(coord3.longitude), @(coord3.latitude)]]};
|
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
||||||
|
|
||||||
|
// it has the correct geometry
|
||||||
|
NSDictionary *expectedGeometry = @{
|
||||||
|
@"type" : @"MultiPoint",
|
||||||
|
@"coordinates" : @[
|
||||||
|
@[ @(coord1.longitude), @(coord1.latitude) ], @[ @(coord2.longitude), @(coord2.latitude) ],
|
||||||
|
@[ @(coord3.longitude), @(coord3.latitude) ]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testShapeCollectionFeatureGeoJSONDictionary {
|
- (void)testShapeCollectionFeatureGeoJSONDictionary {
|
||||||
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
|
MLNPointFeature *pointFeature = [[MLNPointFeature alloc] init];
|
||||||
CLLocationCoordinate2D pointCoordinate = { 10, 10 };
|
CLLocationCoordinate2D pointCoordinate = {10, 10};
|
||||||
pointFeature.coordinate = pointCoordinate;
|
pointFeature.coordinate = pointCoordinate;
|
||||||
|
|
||||||
CLLocationCoordinate2D coord1 = { 0, 0 };
|
CLLocationCoordinate2D coord1 = {0, 0};
|
||||||
CLLocationCoordinate2D coord2 = { 10, 10 };
|
CLLocationCoordinate2D coord2 = {10, 10};
|
||||||
CLLocationCoordinate2D coords[] = { coord1, coord2 };
|
CLLocationCoordinate2D coords[] = {coord1, coord2};
|
||||||
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
|
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coords count:2];
|
||||||
|
|
||||||
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[pointFeature, polylineFeature]];
|
MLNShapeCollectionFeature *shapeCollectionFeature =
|
||||||
|
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[ pointFeature, polylineFeature ]];
|
||||||
|
|
||||||
// A GeoJSON feature
|
// A GeoJSON feature
|
||||||
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
|
NSDictionary *geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
|
||||||
|
|
||||||
// it has the correct geometry
|
// it has the correct geometry
|
||||||
NSDictionary *expectedGeometry = @{@"type": @"GeometryCollection",
|
NSDictionary *expectedGeometry = @{
|
||||||
@"geometries": @[
|
@"type" : @"GeometryCollection",
|
||||||
@{ @"geometry": @{@"type": @"Point",
|
@"geometries" : @[
|
||||||
@"coordinates": @[@(pointCoordinate.longitude), @(pointCoordinate.latitude)]},
|
@{
|
||||||
@"properties": @{},
|
@"geometry" : @{
|
||||||
@"type": @"Feature",
|
@"type" : @"Point",
|
||||||
},
|
@"coordinates" : @[ @(pointCoordinate.longitude), @(pointCoordinate.latitude) ]
|
||||||
@{ @"geometry": @{@"type": @"LineString",
|
},
|
||||||
@"coordinates": @[@[@(coord1.longitude), @(coord1.latitude)],
|
@"properties" : @{},
|
||||||
@[@(coord2.longitude), @(coord2.latitude)]]},
|
@"type" : @"Feature",
|
||||||
@"properties": @{},
|
},
|
||||||
@"type": @"Feature",
|
@{
|
||||||
}
|
@"geometry" : @{
|
||||||
]
|
@"type" : @"LineString",
|
||||||
};
|
@"coordinates" : @[
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
@[ @(coord1.longitude), @(coord1.latitude) ],
|
||||||
|
@[ @(coord2.longitude), @(coord2.latitude) ]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
@"properties" : @{},
|
||||||
|
@"type" : @"Feature",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
|
|
||||||
// When the shape collection is created with an empty array of shapes
|
// When the shape collection is created with an empty array of shapes
|
||||||
shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
|
shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
|
||||||
|
|
||||||
// it has the correct (empty) geometry
|
// it has the correct (empty) geometry
|
||||||
geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
|
geoJSONFeature = [shapeCollectionFeature geoJSONDictionary];
|
||||||
expectedGeometry = @{@"type": @"GeometryCollection",
|
expectedGeometry = @{@"type" : @"GeometryCollection", @"geometries" : @[]};
|
||||||
@"geometries": @[]};
|
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
||||||
XCTAssertEqualObjects(geoJSONFeature[@"geometry"], expectedGeometry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -9,203 +9,237 @@
|
|||||||
@implementation MLNGeometryTests
|
@implementation MLNGeometryTests
|
||||||
|
|
||||||
- (void)testCoordinateBoundsIsEmpty {
|
- (void)testCoordinateBoundsIsEmpty {
|
||||||
MLNCoordinateBounds emptyBounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0));
|
MLNCoordinateBounds emptyBounds =
|
||||||
XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds));
|
MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 0));
|
||||||
XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds)));
|
XCTAssertTrue(MLNCoordinateBoundsIsEmpty(emptyBounds));
|
||||||
|
XCTAssertFalse(MLNCoordinateSpanEqualToCoordinateSpan(
|
||||||
|
MLNCoordinateSpanZero, MLNCoordinateBoundsGetCoordinateSpan(emptyBounds)));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAngleConversions {
|
- (void)testAngleConversions {
|
||||||
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5);
|
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(-M_PI), 1e-5);
|
||||||
XCTAssertEqual(0, MLNDegreesFromRadians(0));
|
XCTAssertEqual(0, MLNDegreesFromRadians(0));
|
||||||
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5);
|
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(M_PI_4), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5);
|
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(M_PI_2), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5);
|
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(M_PI), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5);
|
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(2 * M_PI), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5);
|
XCTAssertEqualWithAccuracy(720, MLNDegreesFromRadians(4 * M_PI), 1e-5);
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4);
|
XCTAssertEqualWithAccuracy(-360, MLNDegreesFromRadians(MLNRadiansFromDegrees(-360)), 1e-4);
|
||||||
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5);
|
XCTAssertEqualWithAccuracy(-180, MLNDegreesFromRadians(MLNRadiansFromDegrees(-180)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5);
|
XCTAssertEqualWithAccuracy(-90, MLNDegreesFromRadians(MLNRadiansFromDegrees(-90)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5);
|
XCTAssertEqualWithAccuracy(-45, MLNDegreesFromRadians(MLNRadiansFromDegrees(-45)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5);
|
XCTAssertEqualWithAccuracy(0, MLNDegreesFromRadians(MLNRadiansFromDegrees(0)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5);
|
XCTAssertEqualWithAccuracy(45, MLNDegreesFromRadians(MLNRadiansFromDegrees(45)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5);
|
XCTAssertEqualWithAccuracy(90, MLNDegreesFromRadians(MLNRadiansFromDegrees(90)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5);
|
XCTAssertEqualWithAccuracy(180, MLNDegreesFromRadians(MLNRadiansFromDegrees(180)), 1e-5);
|
||||||
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4);
|
XCTAssertEqualWithAccuracy(360, MLNDegreesFromRadians(MLNRadiansFromDegrees(360)), 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAltitudeConversions {
|
- (void)testAltitudeConversions {
|
||||||
CGSize tallSize = CGSizeMake(600, 1200);
|
CGSize tallSize = CGSizeMake(600, 1200);
|
||||||
CGSize midSize = CGSizeMake(600, 800);
|
CGSize midSize = CGSizeMake(600, 800);
|
||||||
CGSize shortSize = CGSizeMake(600, 400);
|
CGSize shortSize = CGSizeMake(600, 400);
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize), 1e-8);
|
XCTAssertEqualWithAccuracy(
|
||||||
XCTAssertLessThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, tallSize));
|
1800, MLNAltitudeForZoomLevel(MLNZoomLevelForAltitude(1800, 0, 0, midSize), 0, 0, midSize),
|
||||||
XCTAssertGreaterThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize), MLNZoomLevelForAltitude(1800, 0, 0, shortSize));
|
1e-8);
|
||||||
|
XCTAssertLessThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize),
|
||||||
|
MLNZoomLevelForAltitude(1800, 0, 0, tallSize));
|
||||||
|
XCTAssertGreaterThan(MLNZoomLevelForAltitude(1800, 0, 0, midSize),
|
||||||
|
MLNZoomLevelForAltitude(1800, 0, 0, shortSize));
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 0, midSize), 0, 0, midSize), 1e-8);
|
XCTAssertEqualWithAccuracy(
|
||||||
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 0, midSize), 0, 0, midSize), 1e-8);
|
0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 0, midSize), 0, 0, midSize), 1e-8);
|
||||||
|
XCTAssertEqualWithAccuracy(
|
||||||
|
18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 0, midSize), 0, 0, midSize), 1e-8);
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 40, midSize), 0, 40, midSize), 1e-8);
|
XCTAssertEqualWithAccuracy(
|
||||||
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 40, midSize), 0, 40, midSize), 1e-8);
|
0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 0, 40, midSize), 0, 40, midSize), 1e-8);
|
||||||
|
XCTAssertEqualWithAccuracy(
|
||||||
|
18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 0, 40, midSize), 0, 40, midSize),
|
||||||
|
1e-8);
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 60, 40, midSize), 60, 40, midSize), 1e-8);
|
XCTAssertEqualWithAccuracy(
|
||||||
XCTAssertEqualWithAccuracy(18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 60, 40, midSize), 60, 40, midSize), 1e-8);
|
0, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(0, 60, 40, midSize), 60, 40, midSize),
|
||||||
|
1e-8);
|
||||||
|
XCTAssertEqualWithAccuracy(
|
||||||
|
18, MLNZoomLevelForAltitude(MLNAltitudeForZoomLevel(18, 60, 40, midSize), 60, 40, midSize),
|
||||||
|
1e-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testGeometryBoxing {
|
- (void)testGeometryBoxing {
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(38.9131982, -77.0325453144239);
|
||||||
CLLocationCoordinate2D roundTrippedCoordinate = [NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue;
|
CLLocationCoordinate2D roundTrippedCoordinate =
|
||||||
|
[NSValue valueWithMLNCoordinate:coordinate].MLNCoordinateValue;
|
||||||
|
|
||||||
XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude, @"Latitude should round-trip.");
|
XCTAssertEqual(coordinate.latitude, roundTrippedCoordinate.latitude,
|
||||||
XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude, @"Longitude should round-trip.");
|
@"Latitude should round-trip.");
|
||||||
|
XCTAssertEqual(coordinate.longitude, roundTrippedCoordinate.longitude,
|
||||||
|
@"Longitude should round-trip.");
|
||||||
|
|
||||||
MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997);
|
MLNCoordinateSpan span = MLNCoordinateSpanMake(4.383333333333335, -4.299999999999997);
|
||||||
MLNCoordinateSpan roundTrippedSpan = [NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue;
|
MLNCoordinateSpan roundTrippedSpan =
|
||||||
|
[NSValue valueWithMLNCoordinateSpan:span].MLNCoordinateSpanValue;
|
||||||
|
|
||||||
XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta, @"Latitude delta should round-trip.");
|
XCTAssertEqual(span.latitudeDelta, roundTrippedSpan.latitudeDelta,
|
||||||
XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta, @"Longitude delta should round-trip.");
|
@"Latitude delta should round-trip.");
|
||||||
|
XCTAssertEqual(span.longitudeDelta, roundTrippedSpan.longitudeDelta,
|
||||||
|
@"Longitude delta should round-trip.");
|
||||||
|
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239),
|
MLNCoordinateBounds bounds =
|
||||||
CLLocationCoordinate2DMake(37.7757368, -122.4135302));
|
MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(38.9131982, -77.0325453144239),
|
||||||
MLNCoordinateBounds roundTrippedBounds = [NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue;
|
CLLocationCoordinate2DMake(37.7757368, -122.4135302));
|
||||||
|
MLNCoordinateBounds roundTrippedBounds =
|
||||||
|
[NSValue valueWithMLNCoordinateBounds:bounds].MLNCoordinateBoundsValue;
|
||||||
|
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
|
||||||
[NSValue valueWithMLNCoordinate:roundTrippedBounds.sw],
|
[NSValue valueWithMLNCoordinate:roundTrippedBounds.sw],
|
||||||
@"Southwest should round-trip.");
|
@"Southwest should round-trip.");
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
|
||||||
[NSValue valueWithMLNCoordinate:roundTrippedBounds.ne],
|
[NSValue valueWithMLNCoordinate:roundTrippedBounds.ne],
|
||||||
@"Northeast should round-trip.");
|
@"Northeast should round-trip.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testCoordinateInCoordinateBounds {
|
- (void)testCoordinateInCoordinateBounds {
|
||||||
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104);
|
CLLocationCoordinate2D ne = CLLocationCoordinate2DMake(45, -104);
|
||||||
CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111);
|
CLLocationCoordinate2D sw = CLLocationCoordinate2DMake(41, -111);
|
||||||
MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne);
|
MLNCoordinateBounds wyoming = MLNCoordinateBoundsMake(sw, ne);
|
||||||
|
|
||||||
CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5);
|
CLLocationCoordinate2D centerOfWyoming = CLLocationCoordinate2DMake(43, -107.5);
|
||||||
|
|
||||||
XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming));
|
XCTAssertTrue(MLNCoordinateInCoordinateBounds(ne, wyoming));
|
||||||
XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming));
|
XCTAssertTrue(MLNCoordinateInCoordinateBounds(sw, wyoming));
|
||||||
XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming));
|
XCTAssertTrue(MLNCoordinateInCoordinateBounds(centerOfWyoming, wyoming));
|
||||||
|
|
||||||
CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135);
|
CLLocationCoordinate2D australia = CLLocationCoordinate2DMake(-25, 135);
|
||||||
CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50);
|
CLLocationCoordinate2D brazil = CLLocationCoordinate2DMake(-12, -50);
|
||||||
CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100);
|
CLLocationCoordinate2D china = CLLocationCoordinate2DMake(35, 100);
|
||||||
|
|
||||||
XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming));
|
XCTAssertFalse(MLNCoordinateInCoordinateBounds(australia, wyoming));
|
||||||
XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming));
|
XCTAssertFalse(MLNCoordinateInCoordinateBounds(brazil, wyoming));
|
||||||
XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming));
|
XCTAssertFalse(MLNCoordinateInCoordinateBounds(china, wyoming));
|
||||||
XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming));
|
XCTAssertFalse(MLNCoordinateInCoordinateBounds(kCLLocationCoordinate2DInvalid, wyoming));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testGeoJSONDeserialization {
|
- (void)testGeoJSONDeserialization {
|
||||||
NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *data = [@"{\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": "
|
||||||
NSError *error;
|
@"[0, 0]}, \"properties\": {}}" dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
NSError *error;
|
||||||
XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization.");
|
MLNPointFeature *feature = (MLNPointFeature *)[MLNShape shapeWithData:data
|
||||||
XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization.");
|
encoding:NSUTF8StringEncoding
|
||||||
XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]], @"Valid GeoJSON point feature data should produce an MLNPointFeature.");
|
error:&error];
|
||||||
XCTAssertEqual(feature.attributes.count, 0UL);
|
XCTAssertNil(error, @"Valid GeoJSON data should produce no error on deserialization.");
|
||||||
XCTAssertEqual(feature.coordinate.latitude, 0);
|
XCTAssertNotNil(feature, @"Valid GeoJSON data should produce an object on deserialization.");
|
||||||
XCTAssertEqual(feature.coordinate.longitude, 0);
|
XCTAssertTrue([feature isKindOfClass:[MLNPointFeature class]],
|
||||||
|
@"Valid GeoJSON point feature data should produce an MLNPointFeature.");
|
||||||
|
XCTAssertEqual(feature.attributes.count, 0UL);
|
||||||
|
XCTAssertEqual(feature.coordinate.latitude, 0);
|
||||||
|
XCTAssertEqual(feature.coordinate.longitude, 0);
|
||||||
|
|
||||||
data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}" dataUsingEncoding:NSUTF8StringEncoding];
|
data = [@"{\"type\": \"Feature\", \"feature\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}"
|
||||||
error = nil;
|
dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
error = nil;
|
||||||
XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization.");
|
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
||||||
XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization.");
|
XCTAssertNotNil(error, @"Invalid GeoJSON data should produce an error on deserialization.");
|
||||||
|
XCTAssertNil(shape, @"Invalid GeoJSON data should produce no object on deserialization.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testGeoJSONSerialization {
|
- (void)testGeoJSONSerialization {
|
||||||
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
|
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
|
||||||
feature.identifier = @504;
|
feature.identifier = @504;
|
||||||
feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667);
|
feature.coordinate = CLLocationCoordinate2DMake(29.95, -90.066667);
|
||||||
|
|
||||||
NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding];
|
NSData *data = [feature geoJSONDataUsingEncoding:NSUTF8StringEncoding];
|
||||||
XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object.");
|
XCTAssertNotNil(data, @"MLNPointFeature should serialize as an UTF-8 string data object.");
|
||||||
NSError *error;
|
NSError *error;
|
||||||
NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
|
NSDictionary *serializedGeoJSON = [NSJSONSerialization JSONObjectWithData:data
|
||||||
XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON.");
|
options:0
|
||||||
XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON.");
|
error:&error];
|
||||||
XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]], @"Serialized GeoJSON data should be a JSON object.");
|
XCTAssertNil(error, @"Serialized GeoJSON data should be deserializable JSON.");
|
||||||
NSDictionary *geoJSON = @{
|
XCTAssertNotNil(serializedGeoJSON, @"Serialized GeoJSON data should be valid JSON.");
|
||||||
@"type": @"Feature",
|
XCTAssertTrue([serializedGeoJSON isKindOfClass:[NSDictionary class]],
|
||||||
@"id": @504,
|
@"Serialized GeoJSON data should be a JSON object.");
|
||||||
@"geometry": @{
|
NSDictionary *geoJSON = @{
|
||||||
@"type": @"Point",
|
@"type" : @"Feature",
|
||||||
@"coordinates": @[
|
@"id" : @504,
|
||||||
@(-90.066667),
|
@"geometry" : @{
|
||||||
@29.95,
|
@"type" : @"Point",
|
||||||
],
|
@"coordinates" : @[
|
||||||
},
|
@(-90.066667),
|
||||||
@"properties": @{},
|
@29.95,
|
||||||
};
|
],
|
||||||
XCTAssertEqualObjects(serializedGeoJSON, geoJSON, @"MLNPointFeature should serialize as a GeoJSON point feature.");
|
},
|
||||||
|
@"properties" : @{},
|
||||||
|
};
|
||||||
|
XCTAssertEqualObjects(serializedGeoJSON, geoJSON,
|
||||||
|
@"MLNPointFeature should serialize as a GeoJSON point feature.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNCoordinateBoundsToMLNCoordinateQuad {
|
- (void)testMLNCoordinateBoundsToMLNCoordinateQuad {
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425),
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425),
|
||||||
CLLocationCoordinate2DMake(46.437, -71.516));
|
CLLocationCoordinate2DMake(46.437, -71.516));
|
||||||
|
|
||||||
MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds);
|
MLNCoordinateQuad quad = MLNCoordinateQuadFromCoordinateBounds(bounds);
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.sw],
|
||||||
[NSValue valueWithMLNCoordinate:quad.bottomLeft],
|
[NSValue valueWithMLNCoordinate:quad.bottomLeft],
|
||||||
@"Bounds southwest should be bottom left of quad.");
|
@"Bounds southwest should be bottom left of quad.");
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
|
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:bounds.ne],
|
||||||
[NSValue valueWithMLNCoordinate:quad.topRight],
|
[NSValue valueWithMLNCoordinate:quad.topRight],
|
||||||
@"Bounds northeast should be top right of quad.");
|
@"Bounds northeast should be top right of quad.");
|
||||||
|
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)],
|
XCTAssertEqualObjects(
|
||||||
[NSValue valueWithMLNCoordinate:quad.topLeft],
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)],
|
||||||
@"Quad top left should be computed correctly.");
|
[NSValue valueWithMLNCoordinate:quad.topLeft],
|
||||||
XCTAssertEqualObjects([NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)],
|
@"Quad top left should be computed correctly.");
|
||||||
[NSValue valueWithMLNCoordinate:quad.bottomRight],
|
XCTAssertEqualObjects(
|
||||||
@"Quad bottom right should be computed correctly.");
|
[NSValue valueWithMLNCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)],
|
||||||
|
[NSValue valueWithMLNCoordinate:quad.bottomRight],
|
||||||
|
@"Quad bottom right should be computed correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNMapPoint {
|
- (void)testMLNMapPoint {
|
||||||
MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0);
|
MLNMapPoint point = MLNMapPointForCoordinate(CLLocationCoordinate2DMake(37.936, -80.425), 0.0);
|
||||||
|
|
||||||
MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue;
|
MLNMapPoint roundTrippedPoint = [NSValue valueWithMLNMapPoint:point].MLNMapPointValue;
|
||||||
XCTAssertEqual(point.x, roundTrippedPoint.x);
|
XCTAssertEqual(point.x, roundTrippedPoint.x);
|
||||||
XCTAssertEqual(point.y, roundTrippedPoint.y);
|
XCTAssertEqual(point.y, roundTrippedPoint.y);
|
||||||
XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel);
|
XCTAssertEqual(point.zoomLevel, roundTrippedPoint.zoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNLocationCoordinate2DIsValid {
|
- (void)testMLNLocationCoordinate2DIsValid {
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(37.936, -71.516);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(46.816368, 5.844469);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-21.512680, 23.334703);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-44.947936, -73.081313);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(19.333630, 203.555405);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(23.254696, -240.795323);
|
||||||
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertTrue(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(91, 361);
|
||||||
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361);
|
CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(-91, -361);
|
||||||
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
|
XCTAssertFalse(MLNLocationCoordinate2DIsValid(coordinate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -11,56 +11,83 @@
|
|||||||
@implementation MLNHeatmapColorTests
|
@implementation MLNHeatmapColorTests
|
||||||
|
|
||||||
- (void)testProperties {
|
- (void)testProperties {
|
||||||
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
|
MLNPointFeature *feature = [[MLNPointFeature alloc] init];
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
|
||||||
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
|
shape:feature
|
||||||
|
options:nil];
|
||||||
|
MLNHeatmapStyleLayer *layer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"layerID"
|
||||||
|
source:source];
|
||||||
|
|
||||||
auto rawLayer = static_cast<mbgl::style::HeatmapLayer*>(layer.rawLayer);
|
auto rawLayer = static_cast<mbgl::style::HeatmapLayer *>(layer.rawLayer);
|
||||||
|
|
||||||
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
|
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
|
||||||
@"heatmap-color should be unset initially.");
|
@"heatmap-color should be unset initially.");
|
||||||
NSExpression *defaultExpression = layer.heatmapColor;
|
NSExpression *defaultExpression = layer.heatmapColor;
|
||||||
|
|
||||||
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
|
NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
|
||||||
layer.heatmapColor = constantExpression;
|
layer.heatmapColor = constantExpression;
|
||||||
|
|
||||||
|
mbgl::style::PropertyValue<float> propertyValue = {0xff};
|
||||||
|
XCTAssertEqual(
|
||||||
|
rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
|
||||||
|
@"Setting heatmapColor to a constant value expression should update heatmap-color.");
|
||||||
|
XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
|
||||||
|
@"heatmapColor should round-trip constant value expressions.");
|
||||||
|
|
||||||
mbgl::style::PropertyValue<float> propertyValue = { 0xff };
|
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
|
||||||
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
|
NSExpression *constantExpression2 =
|
||||||
@"Setting heatmapColor to a constant value expression should update heatmap-color.");
|
[NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
|
||||||
XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
|
|
||||||
@"heatmapColor should round-trip constant value expressions.");
|
|
||||||
|
|
||||||
constantExpression = [NSExpression expressionWithFormat:@"%@", [MLNColor redColor]];
|
|
||||||
NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MLNColor blueColor]];
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
|
XCTExpectFailure(
|
||||||
|
@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
|
||||||
#endif
|
#endif
|
||||||
NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)", constantExpression, @{@12: constantExpression2}];
|
NSExpression *functionExpression =
|
||||||
layer.heatmapColor = functionExpression;
|
[NSExpression expressionWithFormat:@"mgl_step:from:stops:($heatmapDensity, %@, %@)",
|
||||||
|
constantExpression, @{@12 : constantExpression2}];
|
||||||
|
layer.heatmapColor = functionExpression;
|
||||||
|
|
||||||
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
|
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
|
||||||
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
|
@"Setting heatmapColor to an expression depending on $heatmapDensity should "
|
||||||
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
|
@"update heatmap-color.");
|
||||||
@"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
|
XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
|
||||||
XCTExpectFailure(@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
|
@"Setting heatmapColor to an expression depending on $heatmapDensity should "
|
||||||
XCTAssertEqualObjects(layer.heatmapColor, functionExpression,
|
@"update heatmap-color.");
|
||||||
@"heatmapColor should round-trip expressions depending on $heatmapDensity.");
|
XCTExpectFailure(
|
||||||
|
@"Awaiting unit test refactoring for https://github.com/maplibre/maplibre-native/issues/331");
|
||||||
|
XCTAssertEqualObjects(
|
||||||
|
layer.heatmapColor, functionExpression,
|
||||||
|
@"heatmapColor should round-trip expressions depending on $heatmapDensity.");
|
||||||
|
|
||||||
layer.heatmapColor = nil;
|
layer.heatmapColor = nil;
|
||||||
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
|
XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
|
||||||
@"Unsetting heatmapColor should return heatmap-color to the default value.");
|
@"Unsetting heatmapColor should return heatmap-color to the default value.");
|
||||||
// The contained colors aren’t object equal, even though their descriptions are.
|
// The contained colors aren’t object equal, even though their descriptions are.
|
||||||
XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description,
|
XCTAssertEqualObjects(layer.heatmapColor.description, defaultExpression.description,
|
||||||
@"heatmapColor should return the default value after being unset.");
|
@"heatmapColor should return the default value after being unset.");
|
||||||
|
|
||||||
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}];
|
functionExpression =
|
||||||
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor.");
|
[NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)",
|
||||||
functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
|
constantExpression, @{@18 : constantExpression}];
|
||||||
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a data expression is applied to heatmapColor.");
|
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
|
||||||
functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression, @{@18: constantExpression}];
|
NSInvalidArgumentException,
|
||||||
functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}];
|
@"MLNHeatmapLayer should raise an exception if a camera expression "
|
||||||
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
|
@"is applied to heatmapColor.");
|
||||||
|
functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
|
||||||
|
XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException,
|
||||||
|
NSInvalidArgumentException,
|
||||||
|
@"MLNHeatmapLayer should raise an exception if a data expression is "
|
||||||
|
@"applied to heatmapColor.");
|
||||||
|
functionExpression =
|
||||||
|
[NSExpression expressionWithFormat:@"mgl_step:from:stops:(bogus, %@, %@)", constantExpression,
|
||||||
|
@{@18 : constantExpression}];
|
||||||
|
functionExpression = [NSExpression
|
||||||
|
expressionWithFormat:
|
||||||
|
@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)",
|
||||||
|
@{@10 : functionExpression}];
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException,
|
||||||
|
@"MLNHeatmapLayer should raise an exception if a camera-data expression is applied to a "
|
||||||
|
@"property that does not support key paths to feature attributes.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#import <XCTest/XCTest.h>
|
|
||||||
#import <Mapbox.h>
|
#import <Mapbox.h>
|
||||||
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import "UIImage+MLNAdditions.h"
|
#import "UIImage+MLNAdditions.h"
|
||||||
#define MLNImageResizingModeTile UIImageResizingModeTile
|
#define MLNImageResizingModeTile UIImageResizingModeTile
|
||||||
#define MLNImageResizingModeStretch UIImageResizingModeStretch
|
#define MLNImageResizingModeStretch UIImageResizingModeStretch
|
||||||
#else
|
#else
|
||||||
#import "NSImage+MLNAdditions.h"
|
#import "NSImage+MLNAdditions.h"
|
||||||
#define MLNImageResizingModeTile NSImageResizingModeTile
|
#define MLNImageResizingModeTile NSImageResizingModeTile
|
||||||
#define MLNImageResizingModeStretch NSImageResizingModeStretch
|
#define MLNImageResizingModeStretch NSImageResizingModeStretch
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@interface MLNImageTests : XCTestCase
|
@interface MLNImageTests : XCTestCase
|
||||||
@@ -19,115 +19,122 @@
|
|||||||
|
|
||||||
- (void)testStretching {
|
- (void)testStretching {
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
CGRect rect = CGRectMake(0, 0, 24, 24);
|
CGRect rect = CGRectMake(0, 0, 24, 24);
|
||||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale);
|
UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale);
|
||||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||||
CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor);
|
CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor);
|
||||||
CGContextStrokeRectWithWidth(context, rect, 2);
|
CGContextStrokeRectWithWidth(context, rect, 2);
|
||||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||||
UIGraphicsEndImageContext();
|
UIGraphicsEndImageContext();
|
||||||
#else
|
#else
|
||||||
NSImage *image = [NSImage imageWithSize:NSMakeSize(24, 24) flipped:NO drawingHandler:^BOOL(NSRect dstRect) {
|
NSImage *image = [NSImage
|
||||||
// A little more fanciful than the iOS version, but we aren’t testing the actual contents of the image anyways.
|
imageWithSize:NSMakeSize(24, 24)
|
||||||
NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge, NSMaxXEdge};
|
flipped:NO
|
||||||
|
drawingHandler:^BOOL(NSRect dstRect) {
|
||||||
|
// A little more fanciful than the iOS version, but we aren’t testing the actual contents of
|
||||||
|
// the image anyways.
|
||||||
|
NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge,
|
||||||
|
NSMinXEdge, NSMinYEdge, NSMaxXEdge};
|
||||||
CGFloat grays[] = {NSBlack, NSBlack, NSWhite, NSWhite, NSDarkGray, NSDarkGray};
|
CGFloat grays[] = {NSBlack, NSBlack, NSWhite, NSWhite, NSDarkGray, NSDarkGray};
|
||||||
dstRect = NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0]));
|
dstRect =
|
||||||
|
NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0]));
|
||||||
[NSColor.grayColor set];
|
[NSColor.grayColor set];
|
||||||
NSRectFill(dstRect);
|
NSRectFill(dstRect);
|
||||||
return YES;
|
return YES;
|
||||||
}];
|
}];
|
||||||
image.resizingMode = NSImageResizingModeTile;
|
image.resizingMode = NSImageResizingModeTile;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{
|
{
|
||||||
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
||||||
XCTAssert(styleImage);
|
XCTAssert(styleImage);
|
||||||
if (styleImage) {
|
if (styleImage) {
|
||||||
XCTAssert(!styleImage->getContent());
|
XCTAssert(!styleImage->getContent());
|
||||||
XCTAssertFalse(styleImage->isSdf());
|
XCTAssertFalse(styleImage->isSdf());
|
||||||
XCTAssertTrue(styleImage->getStretchX().empty());
|
XCTAssertTrue(styleImage->getStretchX().empty());
|
||||||
XCTAssertTrue(styleImage->getStretchY().empty());
|
XCTAssertTrue(styleImage->getStretchY().empty());
|
||||||
|
|
||||||
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
||||||
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile);
|
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeTile);
|
||||||
XCTAssertEqual(imageAfter.capInsets.top, 0);
|
XCTAssertEqual(imageAfter.capInsets.top, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.left, 0);
|
XCTAssertEqual(imageAfter.capInsets.left, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
|
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.right, 0);
|
XCTAssertEqual(imageAfter.capInsets.right, 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch];
|
image = [image resizableImageWithCapInsets:UIEdgeInsetsZero
|
||||||
|
resizingMode:UIImageResizingModeStretch];
|
||||||
#else
|
#else
|
||||||
image.resizingMode = NSImageResizingModeStretch;
|
image.resizingMode = NSImageResizingModeStretch;
|
||||||
image.capInsets = NSEdgeInsetsZero;
|
image.capInsets = NSEdgeInsetsZero;
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
||||||
XCTAssert(styleImage);
|
XCTAssert(styleImage);
|
||||||
if (styleImage) {
|
if (styleImage) {
|
||||||
auto scale = styleImage->getPixelRatio();
|
auto scale = styleImage->getPixelRatio();
|
||||||
XCTAssert(!styleImage->getContent());
|
XCTAssert(!styleImage->getContent());
|
||||||
|
|
||||||
auto stretchX = styleImage->getStretchX();
|
auto stretchX = styleImage->getStretchX();
|
||||||
XCTAssertEqual(stretchX.size(), 1UL);
|
XCTAssertEqual(stretchX.size(), 1UL);
|
||||||
if (!stretchX.empty()) {
|
if (!stretchX.empty()) {
|
||||||
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale));
|
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale));
|
||||||
}
|
}
|
||||||
auto stretchY = styleImage->getStretchY();
|
auto stretchY = styleImage->getStretchY();
|
||||||
XCTAssertEqual(stretchY.size(), 1UL);
|
XCTAssertEqual(stretchY.size(), 1UL);
|
||||||
if (!stretchY.empty()) {
|
if (!stretchY.empty()) {
|
||||||
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale));
|
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
||||||
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
|
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
|
||||||
XCTAssertEqual(imageAfter.capInsets.top, 0);
|
XCTAssertEqual(imageAfter.capInsets.top, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.left, 0);
|
XCTAssertEqual(imageAfter.capInsets.left, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
|
XCTAssertEqual(imageAfter.capInsets.bottom, 0);
|
||||||
XCTAssertEqual(imageAfter.capInsets.right, 0);
|
XCTAssertEqual(imageAfter.capInsets.right, 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)];
|
image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)];
|
||||||
#else
|
#else
|
||||||
image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4);
|
image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4);
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
auto styleImage = [image mgl_styleImageWithIdentifier:@"box"];
|
||||||
XCTAssert(styleImage);
|
XCTAssert(styleImage);
|
||||||
if (styleImage) {
|
if (styleImage) {
|
||||||
auto scale = styleImage->getPixelRatio();
|
auto scale = styleImage->getPixelRatio();
|
||||||
auto content = styleImage->getContent();
|
auto content = styleImage->getContent();
|
||||||
XCTAssert(content);
|
XCTAssert(content);
|
||||||
if (content) {
|
if (content) {
|
||||||
XCTAssertEqual(content->top, 1 * scale);
|
XCTAssertEqual(content->top, 1 * scale);
|
||||||
XCTAssertEqual(content->left, 2 * scale);
|
XCTAssertEqual(content->left, 2 * scale);
|
||||||
XCTAssertEqual(content->bottom, 21 * scale);
|
XCTAssertEqual(content->bottom, 21 * scale);
|
||||||
XCTAssertEqual(content->right, 20 * scale);
|
XCTAssertEqual(content->right, 20 * scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stretchX = styleImage->getStretchX();
|
auto stretchX = styleImage->getStretchX();
|
||||||
XCTAssertEqual(stretchX.size(), 1UL);
|
XCTAssertEqual(stretchX.size(), 1UL);
|
||||||
if (!stretchX.empty()) {
|
if (!stretchX.empty()) {
|
||||||
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale));
|
XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale));
|
||||||
}
|
}
|
||||||
auto stretchY = styleImage->getStretchY();
|
auto stretchY = styleImage->getStretchY();
|
||||||
XCTAssertEqual(stretchY.size(), 1UL);
|
XCTAssertEqual(stretchY.size(), 1UL);
|
||||||
if (!stretchY.empty()) {
|
if (!stretchY.empty()) {
|
||||||
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale));
|
XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
MLNImage *imageAfter = [[MLNImage alloc] initWithMLNStyleImage:*styleImage];
|
||||||
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
|
XCTAssertEqual(imageAfter.resizingMode, MLNImageResizingModeStretch);
|
||||||
XCTAssertEqual(imageAfter.capInsets.top, 1);
|
XCTAssertEqual(imageAfter.capInsets.top, 1);
|
||||||
XCTAssertEqual(imageAfter.capInsets.left, 2);
|
XCTAssertEqual(imageAfter.capInsets.left, 2);
|
||||||
XCTAssertEqual(imageAfter.capInsets.bottom, 3);
|
XCTAssertEqual(imageAfter.capInsets.bottom, 3);
|
||||||
XCTAssertEqual(imageAfter.capInsets.right, 4);
|
XCTAssertEqual(imageAfter.capInsets.right, 4);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -11,30 +11,28 @@ using namespace std::chrono_literals;
|
|||||||
@implementation MLNNSDateAdditionsTests
|
@implementation MLNNSDateAdditionsTests
|
||||||
|
|
||||||
- (void)testDurationToNSTimeInterval {
|
- (void)testDurationToNSTimeInterval {
|
||||||
|
NSTimeInterval timeInterval = 5;
|
||||||
|
mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval);
|
||||||
|
NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration);
|
||||||
|
|
||||||
NSTimeInterval timeInterval = 5;
|
mbgl::Duration expectedDuration = 5s;
|
||||||
mbgl::Duration duration = MLNDurationFromTimeInterval(timeInterval);
|
mbgl::Duration expectedDurationMiliSeconds = 5000ms;
|
||||||
NSTimeInterval durationTimeInterval = MLNTimeIntervalFromDuration(duration);
|
mbgl::Duration expectedDurationMicroSeconds = 5000000us;
|
||||||
|
mbgl::Duration expectedDurationNanoSeconds = 5000000000ns;
|
||||||
|
|
||||||
mbgl::Duration expectedDuration = 5s;
|
XCTAssertEqual(timeInterval, durationTimeInterval);
|
||||||
mbgl::Duration expectedDurationMiliSeconds = 5000ms;
|
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration));
|
||||||
mbgl::Duration expectedDurationMicroSeconds = 5000000us;
|
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds));
|
||||||
mbgl::Duration expectedDurationNanoSeconds = 5000000000ns;
|
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds));
|
||||||
|
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds));
|
||||||
|
|
||||||
XCTAssertEqual(timeInterval, durationTimeInterval);
|
mbgl::Duration durationMiliSeconds = 2500ms;
|
||||||
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDuration));
|
mbgl::Duration durationMicroSeconds = 2500000us;
|
||||||
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMiliSeconds));
|
mbgl::Duration durationNanoSeconds = 2500000000ns;
|
||||||
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationMicroSeconds));
|
|
||||||
XCTAssertEqual(timeInterval, MLNTimeIntervalFromDuration(expectedDurationNanoSeconds));
|
|
||||||
|
|
||||||
mbgl::Duration durationMiliSeconds = 2500ms;
|
|
||||||
mbgl::Duration durationMicroSeconds = 2500000us;
|
|
||||||
mbgl::Duration durationNanoSeconds = 2500000000ns;
|
|
||||||
|
|
||||||
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds));
|
|
||||||
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds));
|
|
||||||
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds));
|
|
||||||
|
|
||||||
|
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMiliSeconds));
|
||||||
|
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationMicroSeconds));
|
||||||
|
XCTAssertEqual(NSTimeInterval(2.5), MLNTimeIntervalFromDuration(durationNanoSeconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -10,49 +10,68 @@
|
|||||||
@implementation MLNOfflinePackTests
|
@implementation MLNOfflinePackTests
|
||||||
|
|
||||||
- (void)testInvalidation {
|
- (void)testInvalidation {
|
||||||
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
|
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
|
||||||
|
|
||||||
XCTAssertEqual(invalidPack.state, MLNOfflinePackStateInvalid, @"Offline pack should be invalid when initialized independently of MLNOfflineStorage.");
|
XCTAssertEqual(
|
||||||
|
invalidPack.state, MLNOfflinePackStateInvalid,
|
||||||
|
@"Offline pack should be invalid when initialized independently of MLNOfflineStorage.");
|
||||||
|
|
||||||
XCTAssertThrowsSpecificNamed(invalidPack.region, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its region.");
|
XCTAssertThrowsSpecificNamed(
|
||||||
XCTAssertThrowsSpecificNamed(invalidPack.context, NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when accessing its context.");
|
invalidPack.region, NSException, MLNInvalidOfflinePackException,
|
||||||
XCTAssertThrowsSpecificNamed([invalidPack resume], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being resumed.");
|
@"Invalid offline pack should raise an exception when accessing its region.");
|
||||||
XCTAssertThrowsSpecificNamed([invalidPack suspend], NSException, MLNInvalidOfflinePackException, @"Invalid offline pack should raise an exception when being suspended.");
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
invalidPack.context, NSException, MLNInvalidOfflinePackException,
|
||||||
|
@"Invalid offline pack should raise an exception when accessing its context.");
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[invalidPack resume], NSException, MLNInvalidOfflinePackException,
|
||||||
|
@"Invalid offline pack should raise an exception when being resumed.");
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[invalidPack suspend], NSException, MLNInvalidOfflinePackException,
|
||||||
|
@"Invalid offline pack should raise an exception when being suspended.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testInvalidatingAnInvalidPack {
|
- (void)testInvalidatingAnInvalidPack {
|
||||||
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
|
MLNOfflinePack *invalidPack = [[MLNOfflinePack alloc] init];
|
||||||
|
|
||||||
XCTAssertThrowsSpecificNamed([invalidPack invalidate], NSException, NSInternalInconsistencyException, @"Invalid offline pack should raise an exception when being invalidated.");
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[invalidPack invalidate], NSException, NSInternalInconsistencyException,
|
||||||
|
@"Invalid offline pack should raise an exception when being invalidated.");
|
||||||
|
|
||||||
// Now try again, without asserts
|
// Now try again, without asserts
|
||||||
NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
|
NSAssertionHandler *oldHandler = [NSAssertionHandler currentHandler];
|
||||||
MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self];
|
MLNTestAssertionHandler *newHandler = [[MLNTestAssertionHandler alloc] initWithTestCase:self];
|
||||||
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
|
[[[NSThread currentThread] threadDictionary] setValue:newHandler forKey:NSAssertionHandlerKey];
|
||||||
|
|
||||||
// Make sure this doesn't crash without asserts
|
// Make sure this doesn't crash without asserts
|
||||||
[invalidPack invalidate];
|
[invalidPack invalidate];
|
||||||
|
|
||||||
[[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
|
[[[NSThread currentThread] threadDictionary] setValue:oldHandler forKey:NSAssertionHandlerKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testProgressBoxing {
|
- (void)testProgressBoxing {
|
||||||
MLNOfflinePackProgress progress = {
|
MLNOfflinePackProgress progress = {
|
||||||
.countOfResourcesCompleted = 3,
|
.countOfResourcesCompleted = 3,
|
||||||
.countOfResourcesExpected = 2,
|
.countOfResourcesExpected = 2,
|
||||||
.countOfBytesCompleted = 7,
|
.countOfBytesCompleted = 7,
|
||||||
.countOfTilesCompleted = 1,
|
.countOfTilesCompleted = 1,
|
||||||
.countOfTileBytesCompleted = 6,
|
.countOfTileBytesCompleted = 6,
|
||||||
.maximumResourcesExpected = UINT64_MAX,
|
.maximumResourcesExpected = UINT64_MAX,
|
||||||
};
|
};
|
||||||
MLNOfflinePackProgress roundTrippedProgress = [NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue;
|
MLNOfflinePackProgress roundTrippedProgress =
|
||||||
|
[NSValue valueWithMLNOfflinePackProgress:progress].MLNOfflinePackProgressValue;
|
||||||
|
|
||||||
XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted, @"Completed resources should round-trip.");
|
XCTAssertEqual(progress.countOfResourcesCompleted, roundTrippedProgress.countOfResourcesCompleted,
|
||||||
XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected, @"Expected resources should round-trip.");
|
@"Completed resources should round-trip.");
|
||||||
XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted, @"Completed bytes should round-trip.");
|
XCTAssertEqual(progress.countOfResourcesExpected, roundTrippedProgress.countOfResourcesExpected,
|
||||||
XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted, @"Completed tiles should round-trip.");
|
@"Expected resources should round-trip.");
|
||||||
XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted, @"Completed tile bytes should round-trip.");
|
XCTAssertEqual(progress.countOfBytesCompleted, roundTrippedProgress.countOfBytesCompleted,
|
||||||
XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected, @"Maximum expected resources should round-trip.");
|
@"Completed bytes should round-trip.");
|
||||||
|
XCTAssertEqual(progress.countOfTilesCompleted, roundTrippedProgress.countOfTilesCompleted,
|
||||||
|
@"Completed tiles should round-trip.");
|
||||||
|
XCTAssertEqual(progress.countOfTileBytesCompleted, roundTrippedProgress.countOfTileBytesCompleted,
|
||||||
|
@"Completed tile bytes should round-trip.");
|
||||||
|
XCTAssertEqual(progress.maximumResourcesExpected, roundTrippedProgress.maximumResourcesExpected,
|
||||||
|
@"Maximum expected resources should round-trip.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,9 @@
|
|||||||
#import <mbgl/storage/resource.hpp>
|
#import <mbgl/storage/resource.hpp>
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
extern NSURL *resourceURL(const Resource& resource);
|
extern NSURL *resourceURL(const Resource &resource);
|
||||||
extern BOOL isValidMapboxEndpoint(NSURL *url);
|
extern BOOL isValidMapboxEndpoint(NSURL *url);
|
||||||
}
|
} // namespace mbgl
|
||||||
|
|
||||||
@interface MLNResourceTests : XCTestCase
|
@interface MLNResourceTests : XCTestCase
|
||||||
@end
|
@end
|
||||||
@@ -13,77 +13,78 @@ namespace mbgl {
|
|||||||
@implementation MLNResourceTests
|
@implementation MLNResourceTests
|
||||||
|
|
||||||
- (void)testValidEndpoints {
|
- (void)testValidEndpoints {
|
||||||
using namespace mbgl;
|
using namespace mbgl;
|
||||||
|
|
||||||
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"]));
|
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.com"]));
|
||||||
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"]));
|
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://mapbox.cn"]));
|
||||||
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"]));
|
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.com"]));
|
||||||
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"]));
|
XCTAssertTrue(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.mapbox.cn"]));
|
||||||
|
|
||||||
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"]));
|
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.com"]));
|
||||||
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"]));
|
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://example.cn"]));
|
||||||
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"]));
|
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.com"]));
|
||||||
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"]));
|
XCTAssertFalse(isValidMapboxEndpoint([NSURL URLWithString:@"https://examplemapbox.cn"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL {
|
- (void)internalTestOfflineQueryParameterIsAddedForOfflineResource:(std::string)testURL {
|
||||||
|
using namespace mbgl;
|
||||||
|
|
||||||
using namespace mbgl;
|
// Is our test URL "correct" for subsequent checks?
|
||||||
|
{
|
||||||
|
NSURL *url = [NSURL URLWithString:@(testURL.c_str())];
|
||||||
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url
|
||||||
|
resolvingAgainstBaseURL:NO];
|
||||||
|
NSArray<NSURLQueryItem *> *items = components.queryItems;
|
||||||
|
XCTAssert(items.count == 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Is our test URL "correct" for subsequent checks?
|
Resource resource(Resource::Kind::Unknown, testURL);
|
||||||
{
|
|
||||||
NSURL *url = [NSURL URLWithString:@(testURL.c_str())];
|
|
||||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
|
||||||
NSArray<NSURLQueryItem *> *items = components.queryItems;
|
|
||||||
XCTAssert(items.count == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource resource(Resource::Kind::Unknown, testURL);
|
// Now check offline
|
||||||
|
resource.setUsage(Resource::Usage::Offline);
|
||||||
|
|
||||||
// Now check offline
|
{
|
||||||
resource.setUsage(Resource::Usage::Offline);
|
NSURL *url = resourceURL(resource);
|
||||||
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url
|
||||||
|
resolvingAgainstBaseURL:NO];
|
||||||
|
|
||||||
{
|
// For offline, we expect a single offline query item
|
||||||
NSURL *url = resourceURL(resource);
|
NSInteger foundCount = 0;
|
||||||
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
|
||||||
|
|
||||||
// For offline, we expect a single offline query item
|
|
||||||
NSInteger foundCount = 0;
|
|
||||||
|
|
||||||
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
#if TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
|
||||||
for (NSURLQueryItem *item in components.queryItems) {
|
for (NSURLQueryItem *item in components.queryItems) {
|
||||||
if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) ||
|
if (([item.name isEqualToString:@"offline"] && [item.value isEqualToString:@"true"]) ||
|
||||||
([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
|
([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
|
||||||
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
|
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
|
||||||
foundCount++;
|
foundCount++;
|
||||||
}
|
}
|
||||||
XCTAssertFalse([item.name isEqualToString:@"sku"]);
|
XCTAssertFalse([item.name isEqualToString:@"sku"]);
|
||||||
}
|
|
||||||
|
|
||||||
XCTAssert(foundCount == 3);
|
|
||||||
#else
|
|
||||||
// NOTE: Currently the macOS SDK does not supply the sku or offline query parameters
|
|
||||||
for (NSURLQueryItem *item in components.queryItems) {
|
|
||||||
if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
|
|
||||||
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
|
|
||||||
foundCount++;
|
|
||||||
}
|
|
||||||
XCTAssertFalse([item.name isEqualToString:@"sku"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
XCTAssert(foundCount == 2);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XCTAssert(foundCount == 3);
|
||||||
|
#else
|
||||||
|
// NOTE: Currently the macOS SDK does not supply the sku or offline query parameters
|
||||||
|
for (NSURLQueryItem *item in components.queryItems) {
|
||||||
|
if (([item.name isEqualToString:@"a"] && [item.value isEqualToString:@"one"]) ||
|
||||||
|
([item.name isEqualToString:@"b"] && [item.value isEqualToString:@"two"])) {
|
||||||
|
foundCount++;
|
||||||
|
}
|
||||||
|
XCTAssertFalse([item.name isEqualToString:@"sku"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssert(foundCount == 2);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOfflineQueryParameterIsAddedForOfflineResource {
|
- (void)testOfflineQueryParameterIsAddedForOfflineResource {
|
||||||
std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two";
|
std::string testURL = "test://mapbox.com/testing_offline_query?a=one&b=two";
|
||||||
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
|
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOfflineQueryParameterIsAddedForOfflineResourceForChina {
|
- (void)testOfflineQueryParameterIsAddedForOfflineResourceForChina {
|
||||||
std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two";
|
std::string testURL = "test://mapbox.cn/testing_offline_query?a=one&b=two";
|
||||||
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
|
[self internalTestOfflineQueryParameterIsAddedForOfflineResource:testURL];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -13,322 +13,390 @@
|
|||||||
@implementation MLNShapeSourceTests
|
@implementation MLNShapeSourceTests
|
||||||
|
|
||||||
- (void)testGeoJSONOptionsFromDictionary {
|
- (void)testGeoJSONOptionsFromDictionary {
|
||||||
NSExpression *reduceExpression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForKeyPath:@"featureAccumulated"], [NSExpression expressionForKeyPath:@"sumValue"]]];
|
NSExpression *reduceExpression =
|
||||||
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
|
[NSExpression expressionForFunction:@"sum:"
|
||||||
NSArray *clusterPropertyArray = @[reduceExpression, mapExpression];
|
arguments:@[
|
||||||
NSDictionary *options = @{MLNShapeSourceOptionClustered: @YES,
|
[NSExpression expressionForKeyPath:@"featureAccumulated"],
|
||||||
MLNShapeSourceOptionClusterRadius: @42,
|
[NSExpression expressionForKeyPath:@"sumValue"]
|
||||||
MLNShapeSourceOptionClusterMinPoints: @3,
|
]];
|
||||||
MLNShapeSourceOptionClusterProperties: @{@"sumValue": clusterPropertyArray},
|
NSExpression *mapExpression = [NSExpression expressionForKeyPath:@"mag"];
|
||||||
MLNShapeSourceOptionMaximumZoomLevelForClustering: @98,
|
NSArray *clusterPropertyArray = @[ reduceExpression, mapExpression ];
|
||||||
MLNShapeSourceOptionMaximumZoomLevel: @99,
|
NSDictionary *options = @{
|
||||||
MLNShapeSourceOptionBuffer: @1976,
|
MLNShapeSourceOptionClustered : @YES,
|
||||||
MLNShapeSourceOptionSimplificationTolerance: @0.42,
|
MLNShapeSourceOptionClusterRadius : @42,
|
||||||
MLNShapeSourceOptionLineDistanceMetrics: @YES,
|
MLNShapeSourceOptionClusterMinPoints : @3,
|
||||||
MLNShapeSourceOptionSynchronousUpdate: @YES};
|
MLNShapeSourceOptionClusterProperties : @{@"sumValue" : clusterPropertyArray},
|
||||||
|
MLNShapeSourceOptionMaximumZoomLevelForClustering : @98,
|
||||||
|
MLNShapeSourceOptionMaximumZoomLevel : @99,
|
||||||
|
MLNShapeSourceOptionBuffer : @1976,
|
||||||
|
MLNShapeSourceOptionSimplificationTolerance : @0.42,
|
||||||
|
MLNShapeSourceOptionLineDistanceMetrics : @YES,
|
||||||
|
MLNShapeSourceOptionSynchronousUpdate : @YES
|
||||||
|
};
|
||||||
|
|
||||||
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
|
auto mbglOptions = MLNGeoJSONOptionsFromDictionary(options);
|
||||||
XCTAssertTrue(mbglOptions->cluster);
|
XCTAssertTrue(mbglOptions->cluster);
|
||||||
XCTAssertEqual(mbglOptions->clusterRadius, 42);
|
XCTAssertEqual(mbglOptions->clusterRadius, 42);
|
||||||
XCTAssertEqual(mbglOptions->clusterMaxZoom, 98);
|
XCTAssertEqual(mbglOptions->clusterMaxZoom, 98);
|
||||||
XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL);
|
XCTAssertEqual(mbglOptions->clusterMinPoints, 3UL);
|
||||||
XCTAssertEqual(mbglOptions->maxzoom, 99);
|
XCTAssertEqual(mbglOptions->maxzoom, 99);
|
||||||
XCTAssertEqual(mbglOptions->buffer, 1976);
|
XCTAssertEqual(mbglOptions->buffer, 1976);
|
||||||
XCTAssertEqual(mbglOptions->tolerance, 0.42);
|
XCTAssertEqual(mbglOptions->tolerance, 0.42);
|
||||||
XCTAssertTrue(mbglOptions->lineMetrics);
|
XCTAssertTrue(mbglOptions->lineMetrics);
|
||||||
XCTAssertTrue(!mbglOptions->clusterProperties.empty());
|
XCTAssertTrue(!mbglOptions->clusterProperties.empty());
|
||||||
XCTAssertTrue(mbglOptions->synchronousUpdate);
|
XCTAssertTrue(mbglOptions->synchronousUpdate);
|
||||||
|
|
||||||
options = @{MLNShapeSourceOptionClustered: @"number 1"};
|
options = @{MLNShapeSourceOptionClustered : @"number 1"};
|
||||||
XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options));
|
XCTAssertThrows(MLNGeoJSONOptionsFromDictionary(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNilShape {
|
- (void)testNilShape {
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:nil options:nil];
|
||||||
XCTAssertNil(source.shape);
|
XCTAssertNil(source.shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testUnclusterableShape {
|
- (void)testUnclusterableShape {
|
||||||
NSDictionary *options = @{
|
NSDictionary *options = @{
|
||||||
MLNShapeSourceOptionClustered: @YES,
|
MLNShapeSourceOptionClustered : @YES,
|
||||||
};
|
};
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:[[MLNPointFeature alloc] init] options:options];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"id"
|
||||||
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
|
shape:[[MLNPointFeature alloc] init]
|
||||||
|
options:options];
|
||||||
|
XCTAssertTrue([source.shape isKindOfClass:[MLNPointFeature class]]);
|
||||||
|
|
||||||
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
|
MLNShapeCollectionFeature *feature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[]];
|
||||||
source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options];
|
source = [[MLNShapeSource alloc] initWithIdentifier:@"id" shape:feature options:options];
|
||||||
XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]);
|
XCTAssertTrue([source.shape isKindOfClass:[MLNShapeCollectionFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithDataMultipleFeatures {
|
- (void)testMLNShapeSourceWithDataMultipleFeatures {
|
||||||
|
NSString *geoJSON =
|
||||||
|
@"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": "
|
||||||
|
@"{},\"geometry\": {\"type\": \"LineString\",\"coordinates\": "
|
||||||
|
@"[[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
|
||||||
|
|
||||||
NSString *geoJSON = @"{\"type\": \"FeatureCollection\",\"features\": [{\"type\": \"Feature\",\"properties\": {},\"geometry\": {\"type\": \"LineString\",\"coordinates\": [[-107.75390625,40.329795743702064],[-104.34814453125,37.64903402157866]]}}]}";
|
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
NSError *error;
|
||||||
|
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
||||||
|
XCTAssertNil(error);
|
||||||
|
XCTAssertNotNil(shape);
|
||||||
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:shape
|
||||||
|
options:nil];
|
||||||
|
|
||||||
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
|
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
|
||||||
NSError *error;
|
XCTAssertNotNil(collection);
|
||||||
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
XCTAssertEqual(collection.shapes.count, 1UL);
|
||||||
XCTAssertNil(error);
|
XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
|
||||||
XCTAssertNotNil(shape);
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shape options:nil];
|
|
||||||
|
|
||||||
MLNShapeCollection *collection = (MLNShapeCollection *)source.shape;
|
|
||||||
XCTAssertNotNil(collection);
|
|
||||||
XCTAssertEqual(collection.shapes.count, 1UL);
|
|
||||||
XCTAssertTrue([collection.shapes.firstObject isMemberOfClass:[MLNPolylineFeature class]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithSingleGeometry {
|
- (void)testMLNShapeSourceWithSingleGeometry {
|
||||||
NSData *data = [@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
|
NSData *data =
|
||||||
NSError *error;
|
[@"{\"type\": \"Point\", \"coordinates\": [0, 0]}" dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
NSError *error;
|
||||||
XCTAssertNil(error);
|
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
||||||
XCTAssertNotNil(shape);
|
XCTAssertNil(error);
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil];
|
XCTAssertNotNil(shape);
|
||||||
XCTAssertNotNil(source.shape);
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
|
||||||
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]);
|
shape:shape
|
||||||
|
options:nil];
|
||||||
|
XCTAssertNotNil(source.shape);
|
||||||
|
XCTAssert([source.shape isKindOfClass:[MLNPointAnnotation class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNGeoJSONSourceWithSingleFeature {
|
- (void)testMLNGeoJSONSourceWithSingleFeature {
|
||||||
NSString *geoJSON = @"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}";
|
NSString *geoJSON =
|
||||||
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
|
@"{\"type\": \"Feature\", \"properties\": {\"color\": \"green\"}, \"geometry\": { \"type\": "
|
||||||
NSError *error;
|
@"\"Point\", \"coordinates\": [ -114.06847000122069, 51.050459433092655 ] }}";
|
||||||
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
NSData *data = [geoJSON dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
XCTAssertNil(error);
|
NSError *error;
|
||||||
XCTAssertNotNil(shape);
|
MLNShape *shape = [MLNShape shapeWithData:data encoding:NSUTF8StringEncoding error:&error];
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson" shape:shape options:nil];
|
XCTAssertNil(error);
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(shape);
|
||||||
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"geojson"
|
||||||
MLNPointFeature *feature = (MLNPointFeature *)source.shape;
|
shape:shape
|
||||||
XCTAssert([feature.attributes.allKeys containsObject:@"color"]);
|
options:nil];
|
||||||
|
XCTAssertNotNil(source.shape);
|
||||||
|
XCTAssert([source.shape isKindOfClass:[MLNPointFeature class]]);
|
||||||
|
MLNPointFeature *feature = (MLNPointFeature *)source.shape;
|
||||||
|
XCTAssert([feature.attributes.allKeys containsObject:@"color"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithPolylineFeatures {
|
- (void)testMLNShapeSourceWithPolylineFeatures {
|
||||||
CLLocationCoordinate2D coordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
|
CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(0, 0),
|
||||||
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates count:2];
|
CLLocationCoordinate2DMake(10, 10)};
|
||||||
|
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates
|
||||||
|
count:2];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polylineFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:polylineFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolylineFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithPolygonFeatures {
|
- (void)testMLNShapeSourceWithPolygonFeatures {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5];
|
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
polygonFeature.identifier = @"feature-id";
|
count:5];
|
||||||
NSString *stringAttribute = @"string";
|
polygonFeature.identifier = @"feature-id";
|
||||||
NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
|
NSString *stringAttribute = @"string";
|
||||||
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
|
NSNumber *boolAttribute = [NSNumber numberWithBool:YES];
|
||||||
NSDictionary *nestedDictionaryValue = @{@"nested-key-1": @"nested-string-value"};
|
NSNumber *doubleAttribute = [NSNumber numberWithDouble:1.23];
|
||||||
NSArray *arrayValue = @[@"string-value", @2];
|
NSDictionary *nestedDictionaryValue = @{@"nested-key-1" : @"nested-string-value"};
|
||||||
NSDictionary *dictionaryValue = @{@"key-1": @"string-value",
|
NSArray *arrayValue = @[ @"string-value", @2 ];
|
||||||
@"key-2": @1,
|
NSDictionary *dictionaryValue = @{
|
||||||
@"key-3": nestedDictionaryValue,
|
@"key-1" : @"string-value",
|
||||||
@"key-4": arrayValue};
|
@"key-2" : @1,
|
||||||
NSArray *arrayOfArrays = @[@[@1, @"string-value", @[@"jagged"]]];
|
@"key-3" : nestedDictionaryValue,
|
||||||
NSArray *arrayOfDictionaries = @[@{@"key": @"value"}];
|
@"key-4" : arrayValue
|
||||||
|
};
|
||||||
|
NSArray *arrayOfArrays = @[ @[ @1, @"string-value", @[ @"jagged" ] ] ];
|
||||||
|
NSArray *arrayOfDictionaries = @[ @{@"key" : @"value"} ];
|
||||||
|
|
||||||
polygonFeature.attributes = @{@"name": stringAttribute,
|
polygonFeature.attributes = @{
|
||||||
@"bool": boolAttribute,
|
@"name" : stringAttribute,
|
||||||
@"double": doubleAttribute,
|
@"bool" : boolAttribute,
|
||||||
@"dictionary-attribute": dictionaryValue,
|
@"double" : doubleAttribute,
|
||||||
@"array-attribute": arrayValue,
|
@"dictionary-attribute" : dictionaryValue,
|
||||||
@"array-of-array-attribute": arrayOfArrays,
|
@"array-attribute" : arrayValue,
|
||||||
@"array-of-dictionary-attribute": arrayOfDictionaries};
|
@"array-of-array-attribute" : arrayOfArrays,
|
||||||
|
@"array-of-dictionary-attribute" : arrayOfDictionaries
|
||||||
|
};
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:polygonFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape;
|
MLNPolygonFeature *expectedPolygonFeature = (MLNPolygonFeature *)source.shape;
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
|
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
|
||||||
XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]);
|
XCTAssertTrue([expectedPolygonFeature isMemberOfClass:[MLNPolygonFeature class]]);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
|
XCTAssertEqualObjects(expectedPolygonFeature.identifier, polygonFeature.identifier);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"name"], stringAttribute);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"bool"], boolAttribute);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"double"], doubleAttribute);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"], dictionaryValue);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"dictionary-attribute"],
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
|
dictionaryValue);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"], arrayOfArrays);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-attribute"], arrayValue);
|
||||||
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"], arrayOfDictionaries);
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-array-attribute"],
|
||||||
|
arrayOfArrays);
|
||||||
|
XCTAssertEqualObjects(expectedPolygonFeature.attributes[@"array-of-dictionary-attribute"],
|
||||||
|
arrayOfDictionaries);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons {
|
- (void)testMLNShapeSourceWithPolygonFeaturesInculdingInteriorPolygons {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
CLLocationCoordinate2D interiorCoordinates[] = {
|
CLLocationCoordinate2D interiorCoordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2),
|
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
|
||||||
CLLocationCoordinate2DMake(0.2, 100.8),
|
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
|
||||||
CLLocationCoordinate2DMake(0.8, 100.8),
|
CLLocationCoordinate2DMake(0.2, 100.2)};
|
||||||
CLLocationCoordinate2DMake(0.8, 100.2),
|
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2)};
|
|
||||||
|
|
||||||
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
||||||
|
|
||||||
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
|
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
|
count:5
|
||||||
|
interiorPolygons:@[ polygon ]];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:polygonFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:polygonFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNPolygonFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithMultiPolylineFeatures {
|
- (void)testMLNShapeSourceWithMultiPolylineFeatures {
|
||||||
CLLocationCoordinate2D firstCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
|
CLLocationCoordinate2D firstCoordinates[] = {CLLocationCoordinate2DMake(0, 0),
|
||||||
MLNPolylineFeature *firstPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2];
|
CLLocationCoordinate2DMake(10, 10)};
|
||||||
CLLocationCoordinate2D secondCoordinates[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
|
MLNPolylineFeature *firstPolylineFeature =
|
||||||
MLNPolylineFeature *secondPolylineFeature = [MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2];
|
[MLNPolylineFeature polylineWithCoordinates:firstCoordinates count:2];
|
||||||
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[firstPolylineFeature, secondPolylineFeature]];
|
CLLocationCoordinate2D secondCoordinates[] = {CLLocationCoordinate2DMake(0, 0),
|
||||||
|
CLLocationCoordinate2DMake(10, 10)};
|
||||||
|
MLNPolylineFeature *secondPolylineFeature =
|
||||||
|
[MLNPolylineFeature polylineWithCoordinates:secondCoordinates count:2];
|
||||||
|
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature
|
||||||
|
multiPolylineWithPolylines:@[ firstPolylineFeature, secondPolylineFeature ]];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolylineFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:multiPolylineFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolylineFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithMultiPolygonFeatures {
|
- (void)testMLNShapeSourceWithMultiPolygonFeatures {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
CLLocationCoordinate2D interiorCoordinates[] = {
|
CLLocationCoordinate2D interiorCoordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2),
|
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
|
||||||
CLLocationCoordinate2DMake(0.2, 100.8),
|
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
|
||||||
CLLocationCoordinate2DMake(0.8, 100.8),
|
CLLocationCoordinate2DMake(0.2, 100.2)};
|
||||||
CLLocationCoordinate2DMake(0.8, 100.2),
|
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2)};
|
|
||||||
|
|
||||||
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
||||||
|
|
||||||
MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
|
MLNPolygonFeature *firstPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
|
count:5
|
||||||
|
interiorPolygons:@[ polygon ]];
|
||||||
|
MLNPolygonFeature *secondPolygon = [MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
|
count:5
|
||||||
|
interiorPolygons:@[ polygon ]];
|
||||||
|
|
||||||
MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[firstPolygon, secondPolygon]];
|
MLNMultiPolygonFeature *multiPolygonFeature =
|
||||||
|
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ firstPolygon, secondPolygon ]];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:multiPolygonFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:multiPolygonFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNMultiPolygonFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithPointFeature {
|
- (void)testMLNShapeSourceWithPointFeature {
|
||||||
MLNPointFeature *pointFeature = [MLNPointFeature new];
|
MLNPointFeature *pointFeature = [MLNPointFeature new];
|
||||||
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
|
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
|
||||||
|
shape:pointFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithPointCollectionFeature {
|
- (void)testMLNShapeSourceWithPointCollectionFeature {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
MLNPointCollectionFeature *pointCollectionFeature =
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
[MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
|
||||||
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id"
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"souce-id" shape:pointCollectionFeature options:nil];
|
shape:pointCollectionFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
XCTAssertNotNil(source.shape);
|
XCTAssertNotNil(source.shape);
|
||||||
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
|
XCTAssertTrue([source.shape isMemberOfClass:[MLNPointCollectionFeature class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithShapeCollectionFeatures {
|
- (void)testMLNShapeSourceWithShapeCollectionFeatures {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
CLLocationCoordinate2D interiorCoordinates[] = {
|
CLLocationCoordinate2D interiorCoordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2),
|
CLLocationCoordinate2DMake(0.2, 100.2), CLLocationCoordinate2DMake(0.2, 100.8),
|
||||||
CLLocationCoordinate2DMake(0.2, 100.8),
|
CLLocationCoordinate2DMake(0.8, 100.8), CLLocationCoordinate2DMake(0.8, 100.2),
|
||||||
CLLocationCoordinate2DMake(0.8, 100.8),
|
CLLocationCoordinate2DMake(0.2, 100.2)};
|
||||||
CLLocationCoordinate2DMake(0.8, 100.2),
|
|
||||||
CLLocationCoordinate2DMake(0.2, 100.2)};
|
|
||||||
|
|
||||||
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:interiorCoordinates count:5];
|
||||||
|
|
||||||
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:5 interiorPolygons:@[polygon]];
|
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
|
count:5
|
||||||
|
interiorPolygons:@[ polygon ]];
|
||||||
|
|
||||||
CLLocationCoordinate2D coordinates_2[] = { CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(10, 10)};
|
CLLocationCoordinate2D coordinates_2[] = {CLLocationCoordinate2DMake(0, 0),
|
||||||
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2 count:2];
|
CLLocationCoordinate2DMake(10, 10)};
|
||||||
|
MLNPolylineFeature *polylineFeature = [MLNPolylineFeature polylineWithCoordinates:coordinates_2
|
||||||
|
count:2];
|
||||||
|
|
||||||
MLNMultiPolygonFeature *multiPolygonFeature = [MLNMultiPolygonFeature multiPolygonWithPolygons:@[polygonFeature, polygonFeature]];
|
MLNMultiPolygonFeature *multiPolygonFeature =
|
||||||
|
[MLNMultiPolygonFeature multiPolygonWithPolygons:@[ polygonFeature, polygonFeature ]];
|
||||||
|
|
||||||
MLNMultiPolylineFeature *multiPolylineFeature = [MLNMultiPolylineFeature multiPolylineWithPolylines:@[polylineFeature, polylineFeature]];
|
MLNMultiPolylineFeature *multiPolylineFeature =
|
||||||
|
[MLNMultiPolylineFeature multiPolylineWithPolylines:@[ polylineFeature, polylineFeature ]];
|
||||||
|
|
||||||
MLNPointCollectionFeature *pointCollectionFeature = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
|
MLNPointCollectionFeature *pointCollectionFeature =
|
||||||
|
[MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:5];
|
||||||
|
|
||||||
MLNPointFeature *pointFeature = [MLNPointFeature new];
|
MLNPointFeature *pointFeature = [MLNPointFeature new];
|
||||||
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
|
pointFeature.coordinate = CLLocationCoordinate2DMake(0.2, 100.2);
|
||||||
|
|
||||||
MLNShapeCollectionFeature *shapeCollectionFeature = [MLNShapeCollectionFeature shapeCollectionWithShapes:@[polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature, pointCollectionFeature, pointFeature]];
|
MLNShapeCollectionFeature *shapeCollectionFeature =
|
||||||
|
[MLNShapeCollectionFeature shapeCollectionWithShapes:@[
|
||||||
|
polygonFeature, polylineFeature, multiPolygonFeature, multiPolylineFeature,
|
||||||
|
pointCollectionFeature, pointFeature
|
||||||
|
]];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shape:shapeCollectionFeature options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
|
shape:shapeCollectionFeature
|
||||||
|
options:nil];
|
||||||
|
|
||||||
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
||||||
XCTAssertNotNil(shape);
|
XCTAssertNotNil(shape);
|
||||||
XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes");
|
XCTAssert(shape.shapes.count == 6, @"Shape collection should contain 6 shapes");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithFeaturesConvenienceInitializer {
|
- (void)testMLNShapeSourceWithFeaturesConvenienceInitializer {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
MLNPolygonFeature *polygonFeature = [MLNPolygonFeature polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
|
MLNPolygonFeature *polygonFeature =
|
||||||
|
[MLNPolygonFeature polygonWithCoordinates:coordinates
|
||||||
|
count:sizeof(coordinates) / sizeof(coordinates[0])
|
||||||
|
interiorPolygons:nil];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" features:@[polygonFeature] options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
features:@[ polygonFeature ]
|
||||||
|
options:nil];
|
||||||
|
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
||||||
|
|
||||||
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]);
|
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollectionFeature class]]);
|
||||||
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
|
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
|
||||||
|
|
||||||
// when a shape is included in the features array
|
// when a shape is included in the features array
|
||||||
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
|
MLNPolygon *polygon =
|
||||||
|
[MLNPolygon polygonWithCoordinates:coordinates
|
||||||
|
count:sizeof(coordinates) / sizeof(coordinates[0])
|
||||||
|
interiorPolygons:nil];
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||||
XCTAssertThrowsSpecificNamed([[MLNShapeSource alloc] initWithIdentifier:@"source-id-invalid" features:@[polygon] options:nil], NSException, NSInvalidArgumentException, @"Shape source should raise an exception if a shape is sent to the features initializer");
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[[MLNShapeSource alloc] initWithIdentifier:@"source-id-invalid"
|
||||||
|
features:@[ polygon ]
|
||||||
|
options:nil],
|
||||||
|
NSException, NSInvalidArgumentException,
|
||||||
|
@"Shape source should raise an exception if a shape is sent to the features initializer");
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNShapeSourceWithShapesConvenienceInitializer {
|
- (void)testMLNShapeSourceWithShapesConvenienceInitializer {
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0),
|
CLLocationCoordinate2DMake(0.0, 100.0), CLLocationCoordinate2DMake(0.0, 101.0),
|
||||||
CLLocationCoordinate2DMake(0.0, 101.0),
|
CLLocationCoordinate2DMake(1.0, 101.0), CLLocationCoordinate2DMake(1.0, 100.0),
|
||||||
CLLocationCoordinate2DMake(1.0, 101.0),
|
CLLocationCoordinate2DMake(0.0, 100.0)};
|
||||||
CLLocationCoordinate2DMake(1.0, 100.0),
|
|
||||||
CLLocationCoordinate2DMake(0.0, 100.0)};
|
|
||||||
|
|
||||||
MLNPolygon *polygon = [MLNPolygon polygonWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0]) interiorPolygons:nil];
|
MLNPolygon *polygon =
|
||||||
|
[MLNPolygon polygonWithCoordinates:coordinates
|
||||||
|
count:sizeof(coordinates) / sizeof(coordinates[0])
|
||||||
|
interiorPolygons:nil];
|
||||||
|
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id" shapes:@[polygon] options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"source-id"
|
||||||
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
shapes:@[ polygon ]
|
||||||
|
options:nil];
|
||||||
|
MLNShapeCollectionFeature *shape = (MLNShapeCollectionFeature *)source.shape;
|
||||||
|
|
||||||
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);
|
XCTAssertTrue([shape isKindOfClass:[MLNShapeCollection class]]);
|
||||||
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
|
XCTAssertEqual(shape.shapes.count, 1UL, @"Shape collection should contain 1 shape");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#import <Mapbox.h>
|
#import <Mapbox.h>
|
||||||
|
|
||||||
#import "NSBundle+MLNAdditions.h"
|
|
||||||
#import "MLNVectorTileSource_Private.h"
|
#import "MLNVectorTileSource_Private.h"
|
||||||
|
#import "NSBundle+MLNAdditions.h"
|
||||||
|
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#else
|
#else
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#endif
|
#endif
|
||||||
#import <objc/runtime.h>
|
#import <objc/runtime.h>
|
||||||
|
|
||||||
@@ -19,422 +19,543 @@
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNStyleTests {
|
@implementation MLNStyleTests {
|
||||||
XCTestExpectation *_styleLoadingExpectation;
|
XCTestExpectation *_styleLoadingExpectation;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setUp {
|
- (void)setUp {
|
||||||
[super setUp];
|
[super setUp];
|
||||||
|
|
||||||
[MLNSettings useWellKnownTileServer:MLNMapTiler];
|
[MLNSettings useWellKnownTileServer:MLNMapTiler];
|
||||||
[MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"];
|
[MLNSettings setApiKey:@"pk.feedcafedeadbeefbadebede"];
|
||||||
|
|
||||||
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
|
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
|
||||||
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
|
withExtension:@"json"];
|
||||||
self.mapView.delegate = self;
|
self.mapView = [[MLNMapView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) styleURL:styleURL];
|
||||||
if (!self.mapView.style) {
|
self.mapView.delegate = self;
|
||||||
_styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
|
if (!self.mapView.style) {
|
||||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
_styleLoadingExpectation =
|
||||||
}
|
[self expectationWithDescription:@"Map view should finish loading style."];
|
||||||
|
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style {
|
- (void)mapView:(MLNMapView *)mapView didFinishLoadingStyle:(MLNStyle *)style {
|
||||||
XCTAssertNotNil(mapView.style);
|
XCTAssertNotNil(mapView.style);
|
||||||
XCTAssertEqual(mapView.style, style);
|
XCTAssertEqual(mapView.style, style);
|
||||||
|
|
||||||
[_styleLoadingExpectation fulfill];
|
[_styleLoadingExpectation fulfill];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tearDown {
|
- (void)tearDown {
|
||||||
_styleLoadingExpectation = nil;
|
_styleLoadingExpectation = nil;
|
||||||
self.mapView = nil;
|
self.mapView = nil;
|
||||||
|
|
||||||
[super tearDown];
|
[super tearDown];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove backed property _style
|
// TODO: remove backed property _style
|
||||||
#pragma clang diagnostic push
|
#pragma clang diagnostic push
|
||||||
#pragma clang diagnostic ignored "-Wunused-property-ivar"
|
#pragma clang diagnostic ignored "-Wunused-property-ivar"
|
||||||
- (MLNStyle *)style {
|
- (MLNStyle *)style {
|
||||||
return self.mapView.style;
|
return self.mapView.style;
|
||||||
}
|
}
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
- (void)testName {
|
- (void)testName {
|
||||||
XCTAssertNil(self.style.name);
|
XCTAssertNil(self.style.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSources {
|
- (void)testSources {
|
||||||
NSSet<MLNSource *> *initialSources = self.style.sources;
|
NSSet<MLNSource *> *initialSources = self.style.sources;
|
||||||
if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) {
|
if ([initialSources.anyObject.identifier isEqualToString:@"org.maplibre.annotations"]) {
|
||||||
XCTAssertEqual(self.style.sources.count, 1UL);
|
XCTAssertEqual(self.style.sources.count, 1UL);
|
||||||
} else {
|
} else {
|
||||||
XCTAssertEqual(self.style.sources.count, 0UL);
|
XCTAssertEqual(self.style.sources.count, 0UL);
|
||||||
}
|
}
|
||||||
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
|
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
|
||||||
[self.style addSource:shapeSource];
|
shape:nil
|
||||||
XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
|
options:nil];
|
||||||
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
|
[self.style addSource:shapeSource];
|
||||||
[self.style removeSource:shapeSource];
|
XCTAssertEqual(self.style.sources.count, initialSources.count + 1);
|
||||||
XCTAssertEqual(self.style.sources.count, initialSources.count);
|
XCTAssertEqual(shapeSource, [self.style sourceWithIdentifier:@"shapeSource"]);
|
||||||
|
[self.style removeSource:shapeSource];
|
||||||
|
XCTAssertEqual(self.style.sources.count, initialSources.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingSourcesTwice {
|
- (void)testAddingSourcesTwice {
|
||||||
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
|
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
|
||||||
[self.style addSource:shapeSource];
|
shape:nil
|
||||||
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException, MLNRedundantSourceException);
|
options:nil];
|
||||||
|
[self.style addSource:shapeSource];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addSource:shapeSource], NSException,
|
||||||
|
MLNRedundantSourceException);
|
||||||
|
|
||||||
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource" configurationURL:[NSURL URLWithString:@".json"] tileSize:42];
|
MLNRasterTileSource *rasterTileSource =
|
||||||
[self.style addSource:rasterTileSource];
|
[[MLNRasterTileSource alloc] initWithIdentifier:@"rasterTileSource"
|
||||||
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException, MLNRedundantSourceException);
|
configurationURL:[NSURL URLWithString:@".json"]
|
||||||
|
tileSize:42];
|
||||||
|
[self.style addSource:rasterTileSource];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addSource:rasterTileSource], NSException,
|
||||||
|
MLNRedundantSourceException);
|
||||||
|
|
||||||
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource" configurationURL:[NSURL URLWithString:@".json"]];
|
MLNVectorTileSource *vectorTileSource =
|
||||||
[self.style addSource:vectorTileSource];
|
[[MLNVectorTileSource alloc] initWithIdentifier:@"vectorTileSource"
|
||||||
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException, MLNRedundantSourceException);
|
configurationURL:[NSURL URLWithString:@".json"]];
|
||||||
|
[self.style addSource:vectorTileSource];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addSource:vectorTileSource], NSException,
|
||||||
|
MLNRedundantSourceException);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingSourcesWithDuplicateIdentifiers {
|
- (void)testAddingSourcesWithDuplicateIdentifiers {
|
||||||
MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
MLNVectorTileSource *source1 = [[MLNVectorTileSource alloc]
|
||||||
MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
initWithIdentifier:@"my-source"
|
||||||
|
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
||||||
|
MLNVectorTileSource *source2 = [[MLNVectorTileSource alloc]
|
||||||
|
initWithIdentifier:@"my-source"
|
||||||
|
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
||||||
|
|
||||||
[self.style addSource: source1];
|
[self.style addSource:source1];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addSource: source2], NSException, MLNRedundantSourceIdentifierException);
|
XCTAssertThrowsSpecificNamed([self.style addSource:source2], NSException,
|
||||||
|
MLNRedundantSourceIdentifierException);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testRemovingSourcesBeforeAddingThem {
|
- (void)testRemovingSourcesBeforeAddingThem {
|
||||||
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil];
|
MLNRasterTileSource *rasterTileSource =
|
||||||
[self.style removeSource:rasterTileSource];
|
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
|
||||||
[self.style addSource:rasterTileSource];
|
tileURLTemplates:@[]
|
||||||
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
|
options:nil];
|
||||||
|
[self.style removeSource:rasterTileSource];
|
||||||
|
[self.style addSource:rasterTileSource];
|
||||||
|
XCTAssertNotNil([self.style sourceWithIdentifier:rasterTileSource.identifier]);
|
||||||
|
|
||||||
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source" shape:nil options:nil];
|
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source"
|
||||||
[self.style removeSource:shapeSource];
|
shape:nil
|
||||||
[self.style addSource:shapeSource];
|
options:nil];
|
||||||
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
|
[self.style removeSource:shapeSource];
|
||||||
|
[self.style addSource:shapeSource];
|
||||||
|
XCTAssertNotNil([self.style sourceWithIdentifier:shapeSource.identifier]);
|
||||||
|
|
||||||
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source" tileURLTemplates:@[] options:nil];
|
MLNVectorTileSource *vectorTileSource =
|
||||||
[self.style removeSource:vectorTileSource];
|
[[MLNVectorTileSource alloc] initWithIdentifier:@"vector-tile-source"
|
||||||
[self.style addSource:vectorTileSource];
|
tileURLTemplates:@[]
|
||||||
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
|
options:nil];
|
||||||
|
[self.style removeSource:vectorTileSource];
|
||||||
|
[self.style addSource:vectorTileSource];
|
||||||
|
XCTAssertNotNil([self.style sourceWithIdentifier:vectorTileSource.identifier]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier {
|
- (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier {
|
||||||
// Add a raster tile source
|
// Add a raster tile source
|
||||||
MLNRasterTileSource *rasterTileSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
|
MLNRasterTileSource *rasterTileSource =
|
||||||
[self.style addSource:rasterTileSource];
|
[[MLNRasterTileSource alloc] initWithIdentifier:@"some-identifier"
|
||||||
|
tileURLTemplates:@[]
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:rasterTileSource];
|
||||||
|
|
||||||
// Attempt to remove an image source with the same identifier as the raster tile source
|
// Attempt to remove an image source with the same identifier as the raster tile source
|
||||||
MLNImageSource *imageSource = [[MLNImageSource alloc] initWithIdentifier:@"some-identifier" coordinateQuad: { } URL:[NSURL URLWithString:@"http://host/image.png"]];
|
MLNImageSource *imageSource =
|
||||||
[self.style removeSource:imageSource];
|
[[MLNImageSource alloc] initWithIdentifier:@"some-identifier"
|
||||||
// The raster tile source should still be added
|
coordinateQuad:{}
|
||||||
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier] isMemberOfClass:[MLNRasterTileSource class]]);
|
URL:[NSURL URLWithString:@"http://host/image.png"]];
|
||||||
|
[self.style removeSource:imageSource];
|
||||||
|
// The raster tile source should still be added
|
||||||
|
XCTAssertTrue([[self.style sourceWithIdentifier:rasterTileSource.identifier]
|
||||||
|
isMemberOfClass:[MLNRasterTileSource class]]);
|
||||||
|
|
||||||
// Remove the raster tile source
|
// Remove the raster tile source
|
||||||
[self.style removeSource:rasterTileSource];
|
[self.style removeSource:rasterTileSource];
|
||||||
|
|
||||||
// Add the shape source
|
// Add the shape source
|
||||||
[self.style addSource:imageSource];
|
[self.style addSource:imageSource];
|
||||||
|
|
||||||
// Attempt to remove a vector tile source with the same identifer as the shape source
|
// Attempt to remove a vector tile source with the same identifer as the shape source
|
||||||
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
|
MLNVectorTileSource *vectorTileSource =
|
||||||
[self.style removeSource:vectorTileSource];
|
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
|
||||||
// The image source should still be added
|
tileURLTemplates:@[]
|
||||||
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNImageSource class]]);
|
options:nil];
|
||||||
|
[self.style removeSource:vectorTileSource];
|
||||||
|
// The image source should still be added
|
||||||
|
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
|
||||||
|
isMemberOfClass:[MLNImageSource class]]);
|
||||||
|
|
||||||
// Remove the image source
|
// Remove the image source
|
||||||
[self.style removeSource:imageSource];
|
[self.style removeSource:imageSource];
|
||||||
|
|
||||||
// Add the vector tile source
|
// Add the vector tile source
|
||||||
[self.style addSource:vectorTileSource];
|
[self.style addSource:vectorTileSource];
|
||||||
|
|
||||||
// Attempt to remove the previously created raster tile source that has the same identifer as the shape source
|
// Attempt to remove the previously created raster tile source that has the same identifer as the
|
||||||
[self.style removeSource:rasterTileSource];
|
// shape source
|
||||||
// The vector tile source should still be added
|
[self.style removeSource:rasterTileSource];
|
||||||
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]);
|
// The vector tile source should still be added
|
||||||
|
XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier]
|
||||||
|
isMemberOfClass:[MLNVectorTileSource class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testRemovingSourceInUse {
|
- (void)testRemovingSourceInUse {
|
||||||
// Add a raster tile source
|
// Add a raster tile source
|
||||||
MLNVectorTileSource *vectorTileSource = [[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil];
|
MLNVectorTileSource *vectorTileSource =
|
||||||
[self.style addSource:vectorTileSource];
|
[[MLNVectorTileSource alloc] initWithIdentifier:@"some-identifier"
|
||||||
|
tileURLTemplates:@[]
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:vectorTileSource];
|
||||||
|
|
||||||
// Add a layer using it
|
// Add a layer using it
|
||||||
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:vectorTileSource];
|
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
|
||||||
[self.style addLayer:fillLayer];
|
source:vectorTileSource];
|
||||||
|
[self.style addLayer:fillLayer];
|
||||||
|
|
||||||
// Attempt to remove the raster tile source
|
// Attempt to remove the raster tile source
|
||||||
NSError *error;
|
NSError *error;
|
||||||
BOOL result = [self.style removeSource:vectorTileSource error:&error];
|
BOOL result = [self.style removeSource:vectorTileSource error:&error];
|
||||||
|
|
||||||
XCTAssertFalse(result);
|
XCTAssertFalse(result);
|
||||||
XCTAssertEqualObjects(error.domain, MLNErrorDomain);
|
XCTAssertEqualObjects(error.domain, MLNErrorDomain);
|
||||||
XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove);
|
XCTAssertEqual(error.code, MLNErrorCodeSourceIsInUseCannotRemove);
|
||||||
|
|
||||||
// Ensure it is still there
|
// Ensure it is still there
|
||||||
XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier] isMemberOfClass:[MLNVectorTileSource class]]);
|
XCTAssertTrue([[self.style sourceWithIdentifier:vectorTileSource.identifier]
|
||||||
|
isMemberOfClass:[MLNVectorTileSource class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testLayers {
|
- (void)testLayers {
|
||||||
NSArray<MLNStyleLayer *> *initialLayers = self.style.layers;
|
NSArray<MLNStyleLayer *> *initialLayers = self.style.layers;
|
||||||
if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
|
if ([initialLayers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
|
||||||
XCTAssertEqual(self.style.layers.count, 1UL);
|
XCTAssertEqual(self.style.layers.count, 1UL);
|
||||||
} else {
|
} else {
|
||||||
XCTAssertEqual(self.style.layers.count, 0UL);
|
XCTAssertEqual(self.style.layers.count, 0UL);
|
||||||
}
|
}
|
||||||
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
|
MLNShapeSource *shapeSource = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
|
||||||
[self.style addSource:shapeSource];
|
shape:nil
|
||||||
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:shapeSource];
|
options:nil];
|
||||||
[self.style addLayer:fillLayer];
|
[self.style addSource:shapeSource];
|
||||||
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
|
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
|
||||||
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
|
source:shapeSource];
|
||||||
[self.style removeLayer:fillLayer];
|
[self.style addLayer:fillLayer];
|
||||||
XCTAssertEqual(self.style.layers.count, initialLayers.count);
|
XCTAssertEqual(self.style.layers.count, initialLayers.count + 1);
|
||||||
|
XCTAssertEqual(fillLayer, [self.style layerWithIdentifier:@"fillLayer"]);
|
||||||
|
[self.style removeLayer:fillLayer];
|
||||||
|
XCTAssertEqual(self.style.layers.count, initialLayers.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingLayersTwice {
|
- (void)testAddingLayersTwice {
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource" shape:nil options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shapeSource"
|
||||||
|
shape:nil
|
||||||
|
options:nil];
|
||||||
|
|
||||||
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
|
MLNBackgroundStyleLayer *backgroundLayer =
|
||||||
[self.style addLayer:backgroundLayer];
|
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"backgroundLayer"];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:backgroundLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:backgroundLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
|
|
||||||
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer" source:source];
|
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circleLayer"
|
||||||
[self.style addLayer:circleLayer];
|
source:source];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:circleLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:circleLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
|
|
||||||
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer" source:source];
|
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fillLayer"
|
||||||
[self.style addLayer:fillLayer];
|
source:source];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:fillLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:fillLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
|
|
||||||
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source];
|
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"lineLayer"
|
||||||
[self.style addLayer:lineLayer];
|
source:source];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:lineLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:lineLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
|
|
||||||
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer" source:source];
|
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"rasterLayer"
|
||||||
[self.style addLayer:rasterLayer];
|
source:source];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:rasterLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:rasterLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
|
|
||||||
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer" source:source];
|
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbolLayer"
|
||||||
[self.style addLayer:symbolLayer];
|
source:source];
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException, MLNRedundantLayerException);
|
[self.style addLayer:symbolLayer];
|
||||||
|
XCTAssertThrowsSpecificNamed([self.style addLayer:symbolLayer], NSException,
|
||||||
|
MLNRedundantLayerException);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingLayersWithDuplicateIdentifiers {
|
- (void)testAddingLayersWithDuplicateIdentifiers {
|
||||||
// Just some source
|
// Just some source
|
||||||
MLNVectorTileSource *source = [[MLNVectorTileSource alloc] initWithIdentifier:@"my-source" configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
MLNVectorTileSource *source = [[MLNVectorTileSource alloc]
|
||||||
[self.style addSource: source];
|
initWithIdentifier:@"my-source"
|
||||||
|
configurationURL:[NSURL URLWithString:@"maptiler://sources/hillshades"]];
|
||||||
|
[self.style addSource:source];
|
||||||
|
|
||||||
// Add initial layer
|
// Add initial layer
|
||||||
MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source];
|
MLNFillStyleLayer *initial = [[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
|
||||||
[self.style addLayer:initial];
|
source:source];
|
||||||
|
[self.style addLayer:initial];
|
||||||
|
|
||||||
// Try to add the duplicate
|
// Try to add the duplicate
|
||||||
XCTAssertThrowsSpecificNamed([self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source]], NSException, @"MLNRedundantLayerIdentifierException");
|
XCTAssertThrowsSpecificNamed(
|
||||||
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] belowLayer:initial],NSException, @"MLNRedundantLayerIdentifierException");
|
[self.style addLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
|
||||||
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] aboveLayer:initial], NSException, @"MLNRedundantLayerIdentifierException");
|
source:source]],
|
||||||
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer" source:source] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException");
|
NSException, @"MLNRedundantLayerIdentifierException");
|
||||||
XCTAssertThrowsSpecificNamed([self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"] atIndex:0], NSException, @"MLNRedundantLayerIdentifierException");
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
|
||||||
|
source:source]
|
||||||
|
belowLayer:initial],
|
||||||
|
NSException, @"MLNRedundantLayerIdentifierException");
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
|
||||||
|
source:source]
|
||||||
|
aboveLayer:initial],
|
||||||
|
NSException, @"MLNRedundantLayerIdentifierException");
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[self.style insertLayer:[[MLNFillStyleLayer alloc] initWithIdentifier:@"my-layer"
|
||||||
|
source:source]
|
||||||
|
atIndex:0],
|
||||||
|
NSException, @"MLNRedundantLayerIdentifierException");
|
||||||
|
XCTAssertThrowsSpecificNamed(
|
||||||
|
[self.style insertLayer:[[MLNCustomStyleLayer alloc] initWithIdentifier:@"my-layer"]
|
||||||
|
atIndex:0],
|
||||||
|
NSException, @"MLNRedundantLayerIdentifierException");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testRemovingLayerBeforeAddingSameLayer {
|
- (void)testRemovingLayerBeforeAddingSameLayer {
|
||||||
{
|
{
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding" shape:nil options:nil];
|
MLNShapeSource *source =
|
||||||
|
[[MLNShapeSource alloc] initWithIdentifier:@"shape-source-removing-before-adding"
|
||||||
|
shape:nil
|
||||||
|
options:nil];
|
||||||
|
|
||||||
// Attempting to find a layer with identifier will trigger an exception if the source associated with the layer is not added
|
// Attempting to find a layer with identifier will trigger an exception if the source associated
|
||||||
[self.style addSource:source];
|
// with the layer is not added
|
||||||
|
[self.style addSource:source];
|
||||||
|
|
||||||
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer" source:source];
|
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"fill-layer"
|
||||||
[self.style removeLayer:fillLayer];
|
source:source];
|
||||||
[self.style addLayer:fillLayer];
|
[self.style removeLayer:fillLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
|
[self.style addLayer:fillLayer];
|
||||||
|
XCTAssertNotNil([self.style layerWithIdentifier:fillLayer.identifier]);
|
||||||
|
|
||||||
MLNSymbolStyleLayer *symbolLayer = [[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
|
MLNSymbolStyleLayer *symbolLayer =
|
||||||
[self.style removeLayer:symbolLayer];
|
[[MLNSymbolStyleLayer alloc] initWithIdentifier:@"symbol-layer" source:source];
|
||||||
[self.style addLayer:symbolLayer];
|
[self.style removeLayer:symbolLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
|
[self.style addLayer:symbolLayer];
|
||||||
|
XCTAssertNotNil([self.style layerWithIdentifier:symbolLayer.identifier]);
|
||||||
|
|
||||||
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source];
|
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:@"line-layer"
|
||||||
[self.style removeLayer:lineLayer];
|
source:source];
|
||||||
[self.style addLayer:lineLayer];
|
[self.style removeLayer:lineLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
|
[self.style addLayer:lineLayer];
|
||||||
|
XCTAssertNotNil([self.style layerWithIdentifier:lineLayer.identifier]);
|
||||||
|
|
||||||
MLNCircleStyleLayer *circleLayer = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
|
MLNCircleStyleLayer *circleLayer =
|
||||||
[self.style removeLayer:circleLayer];
|
[[MLNCircleStyleLayer alloc] initWithIdentifier:@"circle-layer" source:source];
|
||||||
[self.style addLayer:circleLayer];
|
[self.style removeLayer:circleLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
|
[self.style addLayer:circleLayer];
|
||||||
|
XCTAssertNotNil([self.style layerWithIdentifier:circleLayer.identifier]);
|
||||||
|
|
||||||
MLNBackgroundStyleLayer *backgroundLayer = [[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
|
MLNBackgroundStyleLayer *backgroundLayer =
|
||||||
[self.style removeLayer:backgroundLayer];
|
[[MLNBackgroundStyleLayer alloc] initWithIdentifier:@"background-layer"];
|
||||||
[self.style addLayer:backgroundLayer];
|
[self.style removeLayer:backgroundLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
|
[self.style addLayer:backgroundLayer];
|
||||||
}
|
XCTAssertNotNil([self.style layerWithIdentifier:backgroundLayer.identifier]);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
MLNRasterTileSource *rasterSource = [[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source" tileURLTemplates:@[] options:nil];
|
MLNRasterTileSource *rasterSource =
|
||||||
[self.style addSource:rasterSource];
|
[[MLNRasterTileSource alloc] initWithIdentifier:@"raster-tile-source"
|
||||||
|
tileURLTemplates:@[]
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:rasterSource];
|
||||||
|
|
||||||
MLNRasterStyleLayer *rasterLayer = [[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
|
MLNRasterStyleLayer *rasterLayer =
|
||||||
[self.style removeLayer:rasterLayer];
|
[[MLNRasterStyleLayer alloc] initWithIdentifier:@"raster-layer" source:rasterSource];
|
||||||
[self.style addLayer:rasterLayer];
|
[self.style removeLayer:rasterLayer];
|
||||||
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
|
[self.style addLayer:rasterLayer];
|
||||||
}
|
XCTAssertNotNil([self.style layerWithIdentifier:rasterLayer.identifier]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier {
|
- (void)testAddingLayerOfTypeABeforeRemovingLayerOfTypeBWithSameIdentifier {
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier" shape:nil options:nil];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"shape-source-identifier"
|
||||||
[self.style addSource:source];
|
shape:nil
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:source];
|
||||||
|
|
||||||
// Add a fill layer
|
// Add a fill layer
|
||||||
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier" source:source];
|
MLNFillStyleLayer *fillLayer = [[MLNFillStyleLayer alloc] initWithIdentifier:@"some-identifier"
|
||||||
[self.style addLayer:fillLayer];
|
source:source];
|
||||||
|
[self.style addLayer:fillLayer];
|
||||||
|
|
||||||
// Attempt to remove a line layer with the same identifier as the fill layer
|
// Attempt to remove a line layer with the same identifier as the fill layer
|
||||||
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier source:source];
|
MLNLineStyleLayer *lineLayer = [[MLNLineStyleLayer alloc] initWithIdentifier:fillLayer.identifier
|
||||||
[self.style removeLayer:lineLayer];
|
source:source];
|
||||||
|
[self.style removeLayer:lineLayer];
|
||||||
|
|
||||||
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier] isMemberOfClass:[MLNFillStyleLayer class]]);
|
XCTAssertTrue([[self.style layerWithIdentifier:fillLayer.identifier]
|
||||||
|
isMemberOfClass:[MLNFillStyleLayer class]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)stringWithContentsOfStyleHeader {
|
- (NSString *)stringWithContentsOfStyleHeader {
|
||||||
NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL
|
NSURL *styleHeaderURL = [[[NSBundle mgl_frameworkBundle].bundleURL
|
||||||
URLByAppendingPathComponent:@"Headers" isDirectory:YES]
|
URLByAppendingPathComponent:@"Headers"
|
||||||
URLByAppendingPathComponent:@"MLNStyle.h"];
|
isDirectory:YES] URLByAppendingPathComponent:@"MLNStyle.h"];
|
||||||
NSError *styleHeaderError;
|
NSError *styleHeaderError;
|
||||||
NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL usedEncoding:nil error:&styleHeaderError];
|
NSString *styleHeader = [NSString stringWithContentsOfURL:styleHeaderURL
|
||||||
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
|
usedEncoding:nil
|
||||||
return styleHeader;
|
error:&styleHeaderError];
|
||||||
|
XCTAssertNil(styleHeaderError, @"Error getting contents of MLNStyle.h.");
|
||||||
|
return styleHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testImages {
|
- (void)testImages {
|
||||||
NSString *imageName = @"TrackingLocationMask";
|
NSString *imageName = @"TrackingLocationMask";
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
MLNImage *image = [MLNImage imageNamed:imageName
|
MLNImage *image = [MLNImage imageNamed:imageName
|
||||||
inBundle:[NSBundle bundleForClass:[self class]]
|
inBundle:[NSBundle bundleForClass:[self class]]
|
||||||
compatibleWithTraitCollection:nil];
|
compatibleWithTraitCollection:nil];
|
||||||
#else
|
#else
|
||||||
MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
|
MLNImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
|
||||||
#endif
|
#endif
|
||||||
XCTAssertNotNil(image);
|
XCTAssertNotNil(image);
|
||||||
|
|
||||||
[self.style setImage:image forName:imageName];
|
[self.style setImage:image forName:imageName];
|
||||||
MLNImage *styleImage = [self.style imageForName:imageName];
|
MLNImage *styleImage = [self.style imageForName:imageName];
|
||||||
|
|
||||||
XCTAssertNotNil(styleImage);
|
XCTAssertNotNil(styleImage);
|
||||||
XCTAssertEqual(image.size.width, styleImage.size.width);
|
XCTAssertEqual(image.size.width, styleImage.size.width);
|
||||||
XCTAssertEqual(image.size.height, styleImage.size.height);
|
XCTAssertEqual(image.size.height, styleImage.size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testLayersOrder {
|
- (void)testLayersOrder {
|
||||||
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"];
|
NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam"
|
||||||
NSURL *url = [NSURL fileURLWithPath:filePath];
|
ofType:@"geojson"];
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" URL:url options:nil];
|
NSURL *url = [NSURL fileURLWithPath:filePath];
|
||||||
[self.style addSource:source];
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID"
|
||||||
|
URL:url
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:source];
|
||||||
|
|
||||||
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1" source:source];
|
MLNCircleStyleLayer *layer1 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer1"
|
||||||
[self.style addLayer:layer1];
|
source:source];
|
||||||
|
[self.style addLayer:layer1];
|
||||||
|
|
||||||
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3" source:source];
|
MLNCircleStyleLayer *layer3 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer3"
|
||||||
[self.style addLayer:layer3];
|
source:source];
|
||||||
|
[self.style addLayer:layer3];
|
||||||
|
|
||||||
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2" source:source];
|
MLNCircleStyleLayer *layer2 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer2"
|
||||||
[self.style insertLayer:layer2 aboveLayer:layer1];
|
source:source];
|
||||||
|
[self.style insertLayer:layer2 aboveLayer:layer1];
|
||||||
|
|
||||||
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4" source:source];
|
MLNCircleStyleLayer *layer4 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer4"
|
||||||
[self.style insertLayer:layer4 aboveLayer:layer3];
|
source:source];
|
||||||
|
[self.style insertLayer:layer4 aboveLayer:layer3];
|
||||||
|
|
||||||
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0" source:source];
|
MLNCircleStyleLayer *layer0 = [[MLNCircleStyleLayer alloc] initWithIdentifier:@"layer0"
|
||||||
[self.style insertLayer:layer0 belowLayer:layer1];
|
source:source];
|
||||||
|
[self.style insertLayer:layer0 belowLayer:layer1];
|
||||||
|
|
||||||
NSArray<MLNStyleLayer *> *layers = [self.style layers];
|
NSArray<MLNStyleLayer *> *layers = [self.style layers];
|
||||||
NSUInteger startIndex = 0;
|
NSUInteger startIndex = 0;
|
||||||
if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
|
if ([layers.firstObject.identifier isEqualToString:@"org.maplibre.annotations.points"]) {
|
||||||
startIndex++;
|
startIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier);
|
XCTAssertEqualObjects(layers[startIndex++].identifier, layer0.identifier);
|
||||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier);
|
XCTAssertEqualObjects(layers[startIndex++].identifier, layer1.identifier);
|
||||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier);
|
XCTAssertEqualObjects(layers[startIndex++].identifier, layer2.identifier);
|
||||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier);
|
XCTAssertEqualObjects(layers[startIndex++].identifier, layer3.identifier);
|
||||||
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
|
XCTAssertEqualObjects(layers[startIndex++].identifier, layer4.identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Localization tests
|
// MARK: Localization tests
|
||||||
|
|
||||||
- (void)testLanguageMatching {
|
- (void)testLanguageMatching {
|
||||||
{
|
{
|
||||||
NSArray *preferences = @[@"en"];
|
NSArray *preferences = @[ @"en" ];
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
XCTAssertEqualObjects(
|
||||||
}
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||||
{
|
}
|
||||||
NSArray *preferences = @[@"en-US"];
|
{
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
NSArray *preferences = @[ @"en-US" ];
|
||||||
}
|
XCTAssertEqualObjects(
|
||||||
{
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||||
NSArray *preferences = @[@"fr"];
|
}
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
|
{
|
||||||
}
|
NSArray *preferences = @[ @"fr" ];
|
||||||
{
|
XCTAssertEqualObjects(
|
||||||
NSArray *preferences = @[@"zh-Hans"];
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"fr");
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
}
|
||||||
}
|
{
|
||||||
{
|
NSArray *preferences = @[ @"zh-Hans" ];
|
||||||
NSArray *preferences = @[@"zh-Hans", @"en"];
|
XCTAssertEqualObjects(
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
NSArray *preferences = @[@"zh-Hant"];
|
NSArray *preferences = @[ @"zh-Hans", @"en" ];
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
|
XCTAssertEqualObjects(
|
||||||
}
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hans");
|
||||||
{
|
}
|
||||||
NSArray *preferences = @[@"en", @"fr", @"el"];
|
{
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
NSArray *preferences = @[ @"zh-Hant" ];
|
||||||
}
|
XCTAssertEqualObjects(
|
||||||
{
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"zh-Hant");
|
||||||
NSArray *preferences = @[@"tlh"];
|
}
|
||||||
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
{
|
||||||
}
|
NSArray *preferences = @[ @"en", @"fr", @"el" ];
|
||||||
{
|
XCTAssertEqualObjects(
|
||||||
NSArray *preferences = @[@"tlh", @"en"];
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||||
XCTAssertEqualObjects([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
}
|
||||||
}
|
{
|
||||||
{
|
NSArray *preferences = @[ @"tlh" ];
|
||||||
NSArray *preferences = @[@"mul"];
|
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
||||||
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
}
|
||||||
}
|
{
|
||||||
|
NSArray *preferences = @[ @"tlh", @"en" ];
|
||||||
|
XCTAssertEqualObjects(
|
||||||
|
[MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences], @"en");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
NSArray *preferences = @[ @"mul" ];
|
||||||
|
XCTAssertNil([MLNVectorTileSource preferredMapboxStreetsLanguageForPreferences:preferences]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Transition tests
|
// MARK: Transition tests
|
||||||
|
|
||||||
- (void)testTransition
|
- (void)testTransition {
|
||||||
{
|
MLNTransition transitionTest = MLNTransitionMake(5, 4);
|
||||||
MLNTransition transitionTest = MLNTransitionMake(5, 4);
|
|
||||||
|
|
||||||
self.style.transition = transitionTest;
|
self.style.transition = transitionTest;
|
||||||
|
|
||||||
XCTAssert(self.style.transition.delay == transitionTest.delay);
|
XCTAssert(self.style.transition.delay == transitionTest.delay);
|
||||||
XCTAssert(self.style.transition.duration == transitionTest.duration);
|
XCTAssert(self.style.transition.duration == transitionTest.duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testPerformsPlacementTransitions
|
- (void)testPerformsPlacementTransitions {
|
||||||
{
|
XCTAssertTrue(self.style.performsPlacementTransitions,
|
||||||
XCTAssertTrue(self.style.performsPlacementTransitions, @"The default value for enabling placement transitions should be YES.");
|
@"The default value for enabling placement transitions should be YES.");
|
||||||
|
|
||||||
self.style.performsPlacementTransitions = NO;
|
self.style.performsPlacementTransitions = NO;
|
||||||
XCTAssertFalse(self.style.performsPlacementTransitions, @"Enabling placement transitions should be NO.");
|
XCTAssertFalse(self.style.performsPlacementTransitions,
|
||||||
|
@"Enabling placement transitions should be NO.");
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testStyleJSON {
|
- (void)testStyleJSON {
|
||||||
// Test getting style JSON
|
// Test getting style JSON
|
||||||
NSString *styleJSON = self.style.styleJSON;
|
NSString *styleJSON = self.style.styleJSON;
|
||||||
XCTAssertNotNil(styleJSON, @"Style JSON should not be nil");
|
XCTAssertNotNil(styleJSON, @"Style JSON should not be nil");
|
||||||
|
|
||||||
// Verify the JSON is valid
|
// Verify the JSON is valid
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
id jsonObject = [NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
|
id jsonObject =
|
||||||
options:0
|
[NSJSONSerialization JSONObjectWithData:[styleJSON dataUsingEncoding:NSUTF8StringEncoding]
|
||||||
error:&error];
|
options:0
|
||||||
XCTAssertNil(error, @"Style JSON should be valid JSON");
|
error:&error];
|
||||||
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
|
XCTAssertNil(error, @"Style JSON should be valid JSON");
|
||||||
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]], @"Style JSON should represent a dictionary");
|
XCTAssertNotNil(jsonObject, @"Style JSON should parse to a valid object");
|
||||||
|
XCTAssertTrue([jsonObject isKindOfClass:[NSDictionary class]],
|
||||||
|
@"Style JSON should represent a dictionary");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
|
|
||||||
#import <Mapbox.h>
|
#import <Mapbox.h>
|
||||||
#import "MLNVectorTileSource.h"
|
|
||||||
#import "MLNTileSource_Private.h"
|
|
||||||
#import "MLNGeometry_Private.h"
|
#import "MLNGeometry_Private.h"
|
||||||
|
#import "MLNTileSource_Private.h"
|
||||||
|
#import "MLNVectorTileSource.h"
|
||||||
|
|
||||||
#include <mbgl/util/tileset.hpp>
|
#include <mbgl/util/tileset.hpp>
|
||||||
|
|
||||||
@@ -14,180 +14,194 @@
|
|||||||
@implementation MLNTileSetTests
|
@implementation MLNTileSetTests
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// a tile set that provides an mbgl tile set
|
// a tile set that provides an mbgl tile set
|
||||||
NSArray *tileURLTemplates = @[@"tile.1", @"tile.2", @"tile.3"];
|
NSArray *tileURLTemplates = @[ @"tile.1", @"tile.2", @"tile.3" ];
|
||||||
|
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
- (void)testTileSetFromTileURLTemplates {
|
- (void)testTileSetFromTileURLTemplates {
|
||||||
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil);
|
mbgl::Tileset tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, nil);
|
||||||
|
|
||||||
// has the correct URL templates
|
// has the correct URL templates
|
||||||
XCTAssertEqual(tileSet.tiles.size(), 3UL);
|
XCTAssertEqual(tileSet.tiles.size(), 3UL);
|
||||||
XCTAssertEqual(tileSet.tiles[0], "tile.1");
|
XCTAssertEqual(tileSet.tiles[0], "tile.1");
|
||||||
XCTAssertEqual(tileSet.tiles[1], "tile.2");
|
XCTAssertEqual(tileSet.tiles[1], "tile.2");
|
||||||
XCTAssertEqual(tileSet.tiles[2], "tile.3");
|
XCTAssertEqual(tileSet.tiles[2], "tile.3");
|
||||||
|
|
||||||
// has the default scheme
|
// has the default scheme
|
||||||
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ);
|
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::XYZ);
|
||||||
|
|
||||||
// when the tile set has no min or max zoom level set
|
// when the tile set has no min or max zoom level set
|
||||||
// the mbgl object has default values for min and max zoom level
|
// the mbgl object has default values for min and max zoom level
|
||||||
XCTAssertEqual(tileSet.zoomRange.min, 0);
|
XCTAssertEqual(tileSet.zoomRange.min, 0);
|
||||||
XCTAssertEqual(tileSet.zoomRange.max, 22);
|
XCTAssertEqual(tileSet.zoomRange.max, 22);
|
||||||
|
|
||||||
// when the tile set has min and/or max zoom level set
|
// when the tile set has min and/or max zoom level set
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
|
tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
|
||||||
MLNTileSourceOptionMinimumZoomLevel: @1,
|
MLNTileSourceOptionMinimumZoomLevel : @1,
|
||||||
MLNTileSourceOptionMaximumZoomLevel: @2,
|
MLNTileSourceOptionMaximumZoomLevel : @2,
|
||||||
});
|
});
|
||||||
|
|
||||||
// the mbgl object reflects the set values for min and max zoom level
|
// the mbgl object reflects the set values for min and max zoom level
|
||||||
XCTAssertEqual(tileSet.zoomRange.min, 1);
|
XCTAssertEqual(tileSet.zoomRange.min, 1);
|
||||||
XCTAssertEqual(tileSet.zoomRange.max, 2);
|
XCTAssertEqual(tileSet.zoomRange.max, 2);
|
||||||
|
|
||||||
// when the tile set has a bounds set
|
// when the tile set has a bounds set
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78));
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34),
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
|
CLLocationCoordinate2DMake(56, 78));
|
||||||
MLNTileSourceOptionCoordinateBounds: @(bounds),
|
tileSet = MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
|
||||||
});
|
MLNTileSourceOptionCoordinateBounds : @(bounds),
|
||||||
|
});
|
||||||
|
|
||||||
// the mbgl object reflects the set values for the bounds
|
// the mbgl object reflects the set values for the bounds
|
||||||
XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
|
XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
|
||||||
if (tileSet.bounds) {
|
if (tileSet.bounds) {
|
||||||
MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
|
MLNCoordinateBounds actual = MLNCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
|
||||||
XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
|
XCTAssert(MLNCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the tile set has an attribution
|
// when the tile set has an attribution
|
||||||
NSString *attribution = @"my tileset © ©️🎈";
|
NSString *attribution = @"my tileset © ©️🎈";
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNTileSourceOptionAttributionHTMLString: attribution,
|
MLNTileSourceOptionAttributionHTMLString : attribution,
|
||||||
});
|
});
|
||||||
|
|
||||||
// the attribution is reflected by the mbgl tileset
|
// the attribution is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.attribution, attribution.UTF8String);
|
XCTAssertEqual(tileSet.attribution, attribution.UTF8String);
|
||||||
|
|
||||||
// when the tile set has attribution infos
|
// when the tile set has attribution infos
|
||||||
MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc] initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
|
MLNAttributionInfo *mapboxInfo = [[MLNAttributionInfo alloc]
|
||||||
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
|
initWithTitle:[[NSAttributedString alloc] initWithString:@"Mapbox"]
|
||||||
|
URL:[NSURL URLWithString:@"https://www.mapbox.com/"]];
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
UIColor *redColor = [UIColor redColor];
|
UIColor *redColor = [UIColor redColor];
|
||||||
#else
|
#else
|
||||||
// CSS uses the sRGB color space.
|
// CSS uses the sRGB color space.
|
||||||
// AppKit incorrectly uses calibrated RGB when exporting HTML, so input
|
// AppKit incorrectly uses calibrated RGB when exporting HTML, so input
|
||||||
// calibrated RGB to ensure round-tripping.
|
// calibrated RGB to ensure round-tripping.
|
||||||
// <rdar://problem/46115233> <http://www.openradar.me/46115233>
|
// <rdar://problem/46115233> <http://www.openradar.me/46115233>
|
||||||
NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
|
NSColor *redColor = [NSColor colorWithCalibratedRed:1 green:0 blue:0 alpha:1];
|
||||||
#endif
|
#endif
|
||||||
NSAttributedString *gl = [[NSAttributedString alloc] initWithString:@"GL" attributes:@{
|
NSAttributedString *gl =
|
||||||
NSBackgroundColorAttributeName: redColor,
|
[[NSAttributedString alloc] initWithString:@"GL"
|
||||||
}];
|
attributes:@{
|
||||||
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
|
NSBackgroundColorAttributeName : redColor,
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
}];
|
||||||
MLNTileSourceOptionAttributionInfos: @[mapboxInfo, glInfo],
|
MLNAttributionInfo *glInfo = [[MLNAttributionInfo alloc] initWithTitle:gl URL:nil];
|
||||||
});
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
|
MLNTileSourceOptionAttributionInfos : @[ mapboxInfo, glInfo ],
|
||||||
|
});
|
||||||
|
|
||||||
// the attribution is reflected by the mbgl tileset
|
// the attribution is reflected by the mbgl tileset
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
NSString *html;
|
NSString *html;
|
||||||
if (@available(iOS 13.0, *)) {
|
if (@available(iOS 13.0, *)) {
|
||||||
// TODO: investigate visual impact
|
// TODO: investigate visual impact
|
||||||
// iOS 13 evidently changes font size from points to pixels
|
// iOS 13 evidently changes font size from points to pixels
|
||||||
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px\">"
|
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
|
||||||
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
@"font-size: 12.00px\">"
|
||||||
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
|
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
||||||
} else {
|
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
|
||||||
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt\">"
|
@"font-size: 12.00px; background-color: #ff0000\">GL</font>\n");
|
||||||
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
} else {
|
||||||
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 12.00pt; background-color: #ff0000\">GL</font>\n");
|
html = (@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
|
||||||
}
|
@"font-size: 12.00pt\">"
|
||||||
|
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
||||||
|
@"<font style=\"font-family: 'Helvetica'; font-weight: normal; font-style: normal; "
|
||||||
|
@"font-size: 12.00pt; background-color: #ff0000\">GL</font>\n");
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">"
|
NSString *html = (@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica\">"
|
||||||
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
@"<a href=\"https://www.mapbox.com/\">Mapbox</a> </font>"
|
||||||
@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; background-color: #ff0000\">GL</font>\n");
|
@"<font face=\"Helvetica\" size=\"3\" style=\"font: 12.0px Helvetica; "
|
||||||
|
@"background-color: #ff0000\">GL</font>\n");
|
||||||
#endif
|
#endif
|
||||||
XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html);
|
XCTAssertEqualObjects(@(tileSet.attribution.c_str()), html);
|
||||||
|
|
||||||
// when the tile coordinate system is changed using an NSNumber
|
// when the tile coordinate system is changed using an NSNumber
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNTileSourceOptionTileCoordinateSystem: @(MLNTileCoordinateSystemTMS),
|
MLNTileSourceOptionTileCoordinateSystem : @(MLNTileCoordinateSystemTMS),
|
||||||
});
|
});
|
||||||
|
|
||||||
// the scheme is reflected by the mbgl tileset
|
// the scheme is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
|
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
|
||||||
|
|
||||||
// when the tile coordinate system is changed using an NSValue
|
// when the tile coordinate system is changed using an NSValue
|
||||||
MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS;
|
MLNTileCoordinateSystem tms = MLNTileCoordinateSystemTMS;
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNTileSourceOptionTileCoordinateSystem: [NSValue value:&tms withObjCType:@encode(MLNTileCoordinateSystem)],
|
MLNTileSourceOptionTileCoordinateSystem : [NSValue value:&tms
|
||||||
});
|
withObjCType:@encode(MLNTileCoordinateSystem)],
|
||||||
|
});
|
||||||
|
|
||||||
// the scheme is reflected by the mbgl tileset
|
// the scheme is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
|
XCTAssertEqual(tileSet.scheme, mbgl::Tileset::Scheme::TMS);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testTileSetFromTileURLTemplatesRasterEncodings {
|
- (void)testTileSetFromTileURLTemplatesRasterEncodings {
|
||||||
mbgl::Tileset tileSet;
|
mbgl::Tileset tileSet;
|
||||||
|
|
||||||
// when the DEM encoding is changed using an NSNumber
|
// when the DEM encoding is changed using an NSNumber
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNTileSourceOptionDEMEncoding: @(MLNDEMEncodingTerrarium),
|
MLNTileSourceOptionDEMEncoding : @(MLNDEMEncodingTerrarium),
|
||||||
});
|
});
|
||||||
|
|
||||||
// the encoding is reflected by the mbgl tileset
|
// the encoding is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
|
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
|
||||||
XCTAssertFalse(tileSet.vectorEncoding);
|
XCTAssertFalse(tileSet.vectorEncoding);
|
||||||
|
|
||||||
// when the raster encoding is changed using an NSValue
|
// when the raster encoding is changed using an NSValue
|
||||||
MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium;
|
MLNDEMEncoding terrarium = MLNDEMEncodingTerrarium;
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNTileSourceOptionDEMEncoding: [NSValue value:&terrarium withObjCType:@encode(MLNDEMEncoding)],
|
MLNTileSourceOptionDEMEncoding : [NSValue value:&terrarium
|
||||||
});
|
withObjCType:@encode(MLNDEMEncoding)],
|
||||||
|
});
|
||||||
|
|
||||||
// the encoding is reflected by the mbgl tileset
|
// the encoding is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
|
XCTAssertEqual(tileSet.rasterEncoding, mbgl::Tileset::RasterEncoding::Terrarium);
|
||||||
XCTAssertFalse(tileSet.vectorEncoding);
|
XCTAssertFalse(tileSet.vectorEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testTileSetFromTileURLTemplatesVectorEncodings {
|
- (void)testTileSetFromTileURLTemplatesVectorEncodings {
|
||||||
mbgl::Tileset tileSet;
|
mbgl::Tileset tileSet;
|
||||||
|
|
||||||
// when the raster encoding is changed using an NSNumber
|
// when the raster encoding is changed using an NSNumber
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMapbox),
|
MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMapbox),
|
||||||
});
|
});
|
||||||
|
|
||||||
// the encoding is reflected by the mbgl tileset
|
// the encoding is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox);
|
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::Mapbox);
|
||||||
XCTAssertFalse(tileSet.rasterEncoding);
|
XCTAssertFalse(tileSet.rasterEncoding);
|
||||||
|
|
||||||
// when the raster encoding is changed using an NSNumber
|
// when the raster encoding is changed using an NSNumber
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNVectorTileSourceOptionEncoding: @(MLNVectorTileSourceEncodingMLT),
|
MLNVectorTileSourceOptionEncoding : @(MLNVectorTileSourceEncodingMLT),
|
||||||
});
|
});
|
||||||
|
|
||||||
// the encoding is reflected by the mbgl tileset
|
// the encoding is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
|
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
|
||||||
XCTAssertFalse(tileSet.rasterEncoding);
|
XCTAssertFalse(tileSet.rasterEncoding);
|
||||||
|
|
||||||
// when the raster encoding is changed using an NSValue
|
// when the raster encoding is changed using an NSValue
|
||||||
MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT;
|
MLNVectorTileSourceEncoding mlt = MLNVectorTileSourceEncodingMLT;
|
||||||
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
tileSet = MLNTileSetFromTileURLTemplates(tileURLTemplates, @{
|
||||||
MLNVectorTileSourceOptionEncoding: [NSValue value:&mlt withObjCType:@encode(MLNVectorTileSourceEncoding)],
|
MLNVectorTileSourceOptionEncoding : [NSValue value:&mlt
|
||||||
});
|
withObjCType:@encode(MLNVectorTileSourceEncoding)],
|
||||||
|
});
|
||||||
|
|
||||||
// the encoding is reflected by the mbgl tileset
|
// the encoding is reflected by the mbgl tileset
|
||||||
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
|
XCTAssertEqual(tileSet.vectorEncoding, mbgl::Tileset::VectorEncoding::MLT);
|
||||||
XCTAssertFalse(tileSet.rasterEncoding);
|
XCTAssertFalse(tileSet.rasterEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testInvalidTileSet {
|
- (void)testInvalidTileSet {
|
||||||
// a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels throws an exception
|
// a tile set that provides an mbgl tile set and invalid (crossed) minimum and maximum zoom levels
|
||||||
XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[@"tile.1"], @{
|
// throws an exception
|
||||||
MLNTileSourceOptionMinimumZoomLevel: @10,
|
XCTAssertThrowsSpecificNamed(MLNTileSetFromTileURLTemplates(@[ @"tile.1" ], @{
|
||||||
MLNTileSourceOptionMaximumZoomLevel: @9,
|
MLNTileSourceOptionMinimumZoomLevel : @10,
|
||||||
}), NSException, NSInvalidArgumentException);
|
MLNTileSourceOptionMaximumZoomLevel : @9,
|
||||||
|
}),
|
||||||
|
NSException, NSInvalidArgumentException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
// Called early in GLFW initialization so that the local glyph rasterizer
|
// Called early in GLFW initialization so that the local glyph rasterizer
|
||||||
// can find a suitable CJK font via NSUserDefaults.
|
// can find a suitable CJK font via NSUserDefaults.
|
||||||
__attribute__((constructor))
|
__attribute__((constructor)) static void setDefaultIdeographicFont() {
|
||||||
static void setDefaultIdeographicFont() {
|
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
||||||
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) {
|
||||||
if (![defaults objectForKey:@"MLNIdeographicFontFamilyName"]) {
|
[defaults setObject:@[ @"PingFang TC" ] forKey:@"MLNIdeographicFontFamilyName"];
|
||||||
[defaults setObject:@[@"PingFang TC"] forKey:@"MLNIdeographicFontFamilyName"];
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ struct GLFWwindow;
|
|||||||
@class NSWindow;
|
@class NSWindow;
|
||||||
|
|
||||||
class GLFWMetalBackend final : public GLFWBackend {
|
class GLFWMetalBackend final : public GLFWBackend {
|
||||||
public:
|
public:
|
||||||
GLFWMetalBackend(GLFWwindow *window_, const bool capFrameRate);
|
GLFWMetalBackend(GLFWwindow *window_, const bool capFrameRate);
|
||||||
~GLFWMetalBackend() = default;
|
~GLFWMetalBackend() = default;
|
||||||
mbgl::gfx::RendererBackend &getRendererBackend() override;
|
mbgl::gfx::RendererBackend &getRendererBackend() override;
|
||||||
void setSize(mbgl::Size) override;
|
void setSize(mbgl::Size) override;
|
||||||
mbgl::Size getSize() const override;
|
mbgl::Size getSize() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NSWindow *window;
|
NSWindow *window;
|
||||||
MetalBackend rendererBackend;
|
MetalBackend rendererBackend;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,20 +7,10 @@
|
|||||||
#include <GLFW/glfw3native.h>
|
#include <GLFW/glfw3native.h>
|
||||||
|
|
||||||
GLFWMetalBackend::GLFWMetalBackend(GLFWwindow* window_, [[maybe_unused]] const bool capFrameRate)
|
GLFWMetalBackend::GLFWMetalBackend(GLFWwindow* window_, [[maybe_unused]] const bool capFrameRate)
|
||||||
:
|
: window(glfwGetCocoaWindow(window_)), rendererBackend(window) {}
|
||||||
window(glfwGetCocoaWindow(window_)),
|
|
||||||
rendererBackend(window)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() {
|
mbgl::gfx::RendererBackend& GLFWMetalBackend::getRendererBackend() { return rendererBackend; }
|
||||||
return rendererBackend;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFWMetalBackend::setSize(mbgl::Size size) {
|
void GLFWMetalBackend::setSize(mbgl::Size size) { rendererBackend.setSize(size); }
|
||||||
rendererBackend.setSize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
mbgl::Size GLFWMetalBackend::getSize() const {
|
mbgl::Size GLFWMetalBackend::getSize() const { return rendererBackend.getSize(); }
|
||||||
return rendererBackend.getSize();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,129 +10,130 @@ namespace mbgl {
|
|||||||
|
|
||||||
using namespace mtl;
|
using namespace mtl;
|
||||||
|
|
||||||
class MetalRenderableResource final: public mtl::RenderableResource {
|
class MetalRenderableResource final : public mtl::RenderableResource {
|
||||||
public:
|
public:
|
||||||
MetalRenderableResource(MetalBackend &backend):
|
MetalRenderableResource(MetalBackend& backend)
|
||||||
rendererBackend(backend),
|
: rendererBackend(backend),
|
||||||
commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())),
|
commandQueue(NS::TransferPtr(backend.getDevice()->newCommandQueue())),
|
||||||
swapchain(NS::TransferPtr(CA::MetalLayer::layer()))
|
swapchain(NS::TransferPtr(CA::MetalLayer::layer())) {
|
||||||
{
|
swapchain->setDevice(backend.getDevice().get());
|
||||||
swapchain->setDevice(backend.getDevice().get());
|
}
|
||||||
|
|
||||||
|
void setBackendSize(mbgl::Size size_) {
|
||||||
|
size = size_;
|
||||||
|
swapchain->setDrawableSize(
|
||||||
|
{static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)});
|
||||||
|
buffersInvalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbgl::Size getSize() const { return size; }
|
||||||
|
|
||||||
|
void bind() override {
|
||||||
|
surface = NS::TransferPtr(swapchain->nextDrawable());
|
||||||
|
auto texSize = mbgl::Size{static_cast<uint32_t>(swapchain->drawableSize().width),
|
||||||
|
static_cast<uint32_t>(swapchain->drawableSize().height)};
|
||||||
|
|
||||||
|
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer());
|
||||||
|
renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor());
|
||||||
|
renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture());
|
||||||
|
|
||||||
|
if (buffersInvalid || !depthTexture || !stencilTexture) {
|
||||||
|
buffersInvalid = false;
|
||||||
|
depthTexture = rendererBackend.getContext().createTexture2D();
|
||||||
|
depthTexture->setSize(texSize);
|
||||||
|
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
|
||||||
|
depthTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
|
||||||
|
gfx::TextureWrapType::Clamp,
|
||||||
|
gfx::TextureWrapType::Clamp});
|
||||||
|
static_cast<mtl::Texture2D*>(depthTexture.get())
|
||||||
|
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
|
||||||
|
MTL::TextureUsageRenderTarget);
|
||||||
|
|
||||||
|
stencilTexture = rendererBackend.getContext().createTexture2D();
|
||||||
|
stencilTexture->setSize(texSize);
|
||||||
|
stencilTexture->setFormat(gfx::TexturePixelType::Stencil,
|
||||||
|
gfx::TextureChannelDataType::UnsignedByte);
|
||||||
|
stencilTexture->setSamplerConfiguration({gfx::TextureFilterType::Linear,
|
||||||
|
gfx::TextureWrapType::Clamp,
|
||||||
|
gfx::TextureWrapType::Clamp});
|
||||||
|
static_cast<mtl::Texture2D*>(stencilTexture.get())
|
||||||
|
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite |
|
||||||
|
MTL::TextureUsageRenderTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBackendSize(mbgl::Size size_) {
|
if (depthTexture) {
|
||||||
size = size_;
|
depthTexture->create();
|
||||||
swapchain->setDrawableSize({static_cast<CGFloat>(size.width), static_cast<CGFloat>(size.height)});
|
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
|
||||||
buffersInvalid = true;
|
depthTarget->setTexture(
|
||||||
|
static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (stencilTexture) {
|
||||||
mbgl::Size getSize() const {
|
stencilTexture->create();
|
||||||
return size;
|
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
|
||||||
|
stencilTarget->setTexture(
|
||||||
|
static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void bind() override {
|
void swap() override {
|
||||||
surface = NS::TransferPtr(swapchain->nextDrawable());
|
commandBuffer->presentDrawable(surface.get());
|
||||||
auto texSize = mbgl::Size{
|
commandBuffer->commit();
|
||||||
static_cast<uint32_t>(swapchain->drawableSize().width),
|
commandBuffer.reset();
|
||||||
static_cast<uint32_t>(swapchain->drawableSize().height)};
|
renderPassDescriptor.reset();
|
||||||
|
}
|
||||||
|
|
||||||
commandBuffer = NS::TransferPtr(commandQueue->commandBuffer());
|
const mtl::RendererBackend& getBackend() const override { return rendererBackend; }
|
||||||
renderPassDescriptor = NS::TransferPtr(MTL::RenderPassDescriptor::renderPassDescriptor());
|
|
||||||
renderPassDescriptor->colorAttachments()->object(0)->setTexture(surface->texture());
|
|
||||||
|
|
||||||
if (buffersInvalid || !depthTexture || !stencilTexture) {
|
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override { return commandBuffer; }
|
||||||
buffersInvalid = false;
|
|
||||||
depthTexture = rendererBackend.getContext().createTexture2D();
|
|
||||||
depthTexture->setSize(texSize);
|
|
||||||
depthTexture->setFormat(gfx::TexturePixelType::Depth, gfx::TextureChannelDataType::Float);
|
|
||||||
depthTexture->setSamplerConfiguration(
|
|
||||||
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
|
|
||||||
static_cast<mtl::Texture2D*>(depthTexture.get())->setUsage(
|
|
||||||
MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
|
|
||||||
|
|
||||||
stencilTexture = rendererBackend.getContext().createTexture2D();
|
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
|
||||||
stencilTexture->setSize(texSize);
|
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
|
||||||
stencilTexture->setFormat(gfx::TexturePixelType::Stencil, gfx::TextureChannelDataType::UnsignedByte);
|
}
|
||||||
stencilTexture->setSamplerConfiguration(
|
|
||||||
{gfx::TextureFilterType::Linear, gfx::TextureWrapType::Clamp, gfx::TextureWrapType::Clamp});
|
|
||||||
static_cast<mtl::Texture2D*>(stencilTexture.get())
|
|
||||||
->setUsage(MTL::TextureUsageShaderRead | MTL::TextureUsageShaderWrite | MTL::TextureUsageRenderTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (depthTexture) {
|
const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
|
||||||
depthTexture->create();
|
return renderPassDescriptor;
|
||||||
if (auto* depthTarget = renderPassDescriptor->depthAttachment()) {
|
}
|
||||||
depthTarget->setTexture(static_cast<mtl::Texture2D*>(depthTexture.get())->getMetalTexture());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stencilTexture) {
|
|
||||||
stencilTexture->create();
|
|
||||||
if (auto* stencilTarget = renderPassDescriptor->stencilAttachment()) {
|
|
||||||
stencilTarget->setTexture(static_cast<mtl::Texture2D*>(stencilTexture.get())->getMetalTexture());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap() override {
|
const CAMetalLayerPtr& getSwapchain() const { return swapchain; }
|
||||||
commandBuffer->presentDrawable(surface.get());
|
|
||||||
commandBuffer->commit();
|
|
||||||
commandBuffer.reset();
|
|
||||||
renderPassDescriptor.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtl::RendererBackend& getBackend() const override {
|
|
||||||
return rendererBackend;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtl::MTLCommandBufferPtr& getCommandBuffer() const override {
|
|
||||||
return commandBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
mtl::MTLBlitPassDescriptorPtr getUploadPassDescriptor() const override {
|
|
||||||
return NS::TransferPtr(MTL::BlitPassDescriptor::alloc()->init());
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtl::MTLRenderPassDescriptorPtr& getRenderPassDescriptor() const override {
|
|
||||||
return renderPassDescriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CAMetalLayerPtr& getSwapchain() const {
|
|
||||||
return swapchain;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MetalBackend &rendererBackend;
|
MetalBackend& rendererBackend;
|
||||||
MTLCommandQueuePtr commandQueue;
|
MTLCommandQueuePtr commandQueue;
|
||||||
MTLCommandBufferPtr commandBuffer;
|
MTLCommandBufferPtr commandBuffer;
|
||||||
MTLRenderPassDescriptorPtr renderPassDescriptor;
|
MTLRenderPassDescriptorPtr renderPassDescriptor;
|
||||||
CAMetalDrawablePtr surface;
|
CAMetalDrawablePtr surface;
|
||||||
CAMetalLayerPtr swapchain;
|
CAMetalLayerPtr swapchain;
|
||||||
gfx::Texture2DPtr depthTexture;
|
gfx::Texture2DPtr depthTexture;
|
||||||
gfx::Texture2DPtr stencilTexture;
|
gfx::Texture2DPtr stencilTexture;
|
||||||
mbgl::Size size;
|
mbgl::Size size;
|
||||||
bool buffersInvalid = true;
|
bool buffersInvalid = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mbgl
|
} // namespace mbgl
|
||||||
|
|
||||||
MetalBackend::MetalBackend(NSWindow *window):
|
MetalBackend::MetalBackend(NSWindow* window)
|
||||||
mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique),
|
: mbgl::mtl::RendererBackend(mbgl::gfx::ContextMode::Unique),
|
||||||
mbgl::gfx::Renderable(mbgl::Size{0, 0}, std::make_unique<mbgl::MetalRenderableResource>(*this))
|
mbgl::gfx::Renderable(mbgl::Size{0, 0},
|
||||||
{
|
std::make_unique<mbgl::MetalRenderableResource>(*this)) {
|
||||||
window.contentView.layer = (__bridge CALayer *)getDefaultRenderable().getResource<mbgl::MetalRenderableResource>().getSwapchain().get();
|
window.contentView.layer = (__bridge CALayer*)getDefaultRenderable()
|
||||||
window.contentView.wantsLayer = YES;
|
.getResource<mbgl::MetalRenderableResource>()
|
||||||
|
.getSwapchain()
|
||||||
|
.get();
|
||||||
|
window.contentView.wantsLayer = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
mbgl::gfx::Renderable &MetalBackend::getDefaultRenderable() {
|
mbgl::gfx::Renderable& MetalBackend::getDefaultRenderable() { return *this; }
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MetalBackend::activate() {}
|
void MetalBackend::activate() {}
|
||||||
void MetalBackend::deactivate() {}
|
void MetalBackend::deactivate() {}
|
||||||
void MetalBackend::updateAssumedState() {}
|
void MetalBackend::updateAssumedState() {}
|
||||||
|
|
||||||
void MetalBackend::setSize(mbgl::Size size_) {
|
void MetalBackend::setSize(mbgl::Size size_) {
|
||||||
getResource<mbgl::MetalRenderableResource>().setBackendSize(size_);
|
getResource<mbgl::MetalRenderableResource>().setBackendSize(size_);
|
||||||
}
|
}
|
||||||
|
|
||||||
mbgl::Size MetalBackend::getSize() const { return getResource<mbgl::MetalRenderableResource>().getSize(); }
|
mbgl::Size MetalBackend::getSize() const {
|
||||||
|
return getResource<mbgl::MetalRenderableResource>().getSize();
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
|
#import "../../../darwin/src/MLNGeometry_Private.h"
|
||||||
#import "MLNMapViewIntegrationTest.h"
|
#import "MLNMapViewIntegrationTest.h"
|
||||||
#import "MLNTestUtility.h"
|
#import "MLNTestUtility.h"
|
||||||
#import "../../../darwin/src/MLNGeometry_Private.h"
|
|
||||||
|
|
||||||
#include <mbgl/map/camera.hpp>
|
#include <mbgl/map/camera.hpp>
|
||||||
|
|
||||||
@@ -10,90 +10,93 @@
|
|||||||
@implementation MLNCameraTransitionFinishTests
|
@implementation MLNCameraTransitionFinishTests
|
||||||
|
|
||||||
- (void)testEaseToCompletionHandler {
|
- (void)testEaseToCompletionHandler {
|
||||||
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
||||||
|
CLLocationCoordinate2DMake(1.0, 1.0));
|
||||||
|
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
||||||
|
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
XCTestExpectation *expectation =
|
||||||
CLLocationCoordinate2DMake(1.0, 1.0));
|
[self expectationWithDescription:@"Completion block should be called"];
|
||||||
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
|
[self.mapView setCamera:camera
|
||||||
|
withDuration:0.0
|
||||||
[self.mapView setCamera:camera
|
animationTimingFunction:nil
|
||||||
withDuration:0.0
|
completionHandler:^{
|
||||||
animationTimingFunction:nil
|
|
||||||
completionHandler:^{
|
|
||||||
[expectation fulfill];
|
[expectation fulfill];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:0.5];
|
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testEaseToCompletionHandlerAnimated {
|
- (void)testEaseToCompletionHandlerAnimated {
|
||||||
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
||||||
|
CLLocationCoordinate2DMake(1.0, 1.0));
|
||||||
|
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
||||||
|
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
XCTestExpectation *expectation =
|
||||||
CLLocationCoordinate2DMake(1.0, 1.0));
|
[self expectationWithDescription:@"Completion block should be called"];
|
||||||
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
|
[self.mapView setCamera:camera
|
||||||
|
withDuration:0.3
|
||||||
[self.mapView setCamera:camera
|
animationTimingFunction:nil
|
||||||
withDuration:0.3
|
completionHandler:^{
|
||||||
animationTimingFunction:nil
|
|
||||||
completionHandler:^{
|
|
||||||
[expectation fulfill];
|
[expectation fulfill];
|
||||||
}];
|
}];
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:0.5];
|
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testFlyToCompletionHandler {
|
- (void)testFlyToCompletionHandler {
|
||||||
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
||||||
|
CLLocationCoordinate2DMake(1.0, 1.0));
|
||||||
|
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
||||||
|
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
XCTestExpectation *expectation =
|
||||||
CLLocationCoordinate2DMake(1.0, 1.0));
|
[self expectationWithDescription:@"Completion block should be called"];
|
||||||
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
|
[self.mapView flyToCamera:camera
|
||||||
|
withDuration:0.0
|
||||||
|
completionHandler:^{
|
||||||
|
[expectation fulfill];
|
||||||
|
}];
|
||||||
|
|
||||||
[self.mapView flyToCamera:camera
|
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||||
withDuration:0.0
|
|
||||||
completionHandler:^{
|
|
||||||
[expectation fulfill];
|
|
||||||
}];
|
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:0.5];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testFlyToCompletionHandlerAnimated {
|
- (void)testFlyToCompletionHandlerAnimated {
|
||||||
|
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
||||||
|
CLLocationCoordinate2DMake(1.0, 1.0));
|
||||||
|
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
||||||
|
|
||||||
MLNCoordinateBounds bounds = MLNCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0),
|
XCTestExpectation *expectation =
|
||||||
CLLocationCoordinate2DMake(1.0, 1.0));
|
[self expectationWithDescription:@"Completion block should be called"];
|
||||||
MLNMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds];
|
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"];
|
[self.mapView flyToCamera:camera
|
||||||
|
withDuration:0.3
|
||||||
|
completionHandler:^{
|
||||||
|
[expectation fulfill];
|
||||||
|
}];
|
||||||
|
|
||||||
[self.mapView flyToCamera:camera
|
[self waitForExpectations:@[ expectation ] timeout:0.5];
|
||||||
withDuration:0.3
|
|
||||||
completionHandler:^{
|
|
||||||
[expectation fulfill];
|
|
||||||
}];
|
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:0.5];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// MARK: - camera transitions with NaN values
|
// MARK: - camera transitions with NaN values
|
||||||
|
|
||||||
@interface MLNMapView (MLNCameraTransitionFinishNaNTests)
|
@interface MLNMapView (MLNCameraTransitionFinishNaNTests)
|
||||||
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets;
|
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
|
||||||
|
edgePadding:(UIEdgeInsets)insets;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MLNCameraTransitionNaNZoomMapView: MLNMapView
|
@interface MLNCameraTransitionNaNZoomMapView : MLNMapView
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation MLNCameraTransitionNaNZoomMapView
|
@implementation MLNCameraTransitionNaNZoomMapView
|
||||||
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets {
|
- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MLNMapCamera *)camera
|
||||||
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets];
|
edgePadding:(UIEdgeInsets)insets {
|
||||||
options.zoom = NAN;
|
mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera
|
||||||
return options;
|
edgePadding:insets];
|
||||||
|
options.zoom = NAN;
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -103,6 +106,6 @@
|
|||||||
|
|
||||||
@implementation MLNCameraTransitionFinishNaNTests
|
@implementation MLNCameraTransitionFinishNaNTests
|
||||||
- (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL {
|
- (MLNMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL {
|
||||||
return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL];
|
return [[MLNCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
#import "../../../darwin/src/MLNGeometry_Private.h"
|
||||||
#import "MLNMapViewIntegrationTest.h"
|
#import "MLNMapViewIntegrationTest.h"
|
||||||
#import "MLNTestUtility.h"
|
#import "MLNTestUtility.h"
|
||||||
#import "../../../darwin/src/MLNGeometry_Private.h"
|
|
||||||
|
|
||||||
@interface MLNCameraTransitionTests : MLNMapViewIntegrationTest
|
@interface MLNCameraTransitionTests : MLNMapViewIntegrationTest
|
||||||
@end
|
@end
|
||||||
@@ -8,387 +8,350 @@
|
|||||||
@implementation MLNCameraTransitionTests
|
@implementation MLNCameraTransitionTests
|
||||||
|
|
||||||
- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
|
- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
|
expectation.expectedFulfillmentCount = 2;
|
||||||
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
expectation.expectedFulfillmentCount = 2;
|
|
||||||
expectation.assertForOverFulfill = YES;
|
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
if (!strongSelf) return;
|
||||||
|
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
[expectation fulfill];
|
||||||
|
|
||||||
if (!strongSelf) return;
|
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
||||||
|
if (mapView.direction != 0.0) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[mapView resetNorth];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
[expectation fulfill];
|
[self.mapView setDirection:90 animated:YES];
|
||||||
|
|
||||||
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
// loop, render, and wait
|
||||||
if (mapView.direction != 0.0) {
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[mapView resetNorth];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[self.mapView setDirection:90 animated:YES];
|
|
||||||
|
|
||||||
// loop, render, and wait
|
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)testSetAndResetNorthInDelegateMethod {
|
- (void)testSetAndResetNorthInDelegateMethod {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
|
expectation.expectedFulfillmentCount = 2;
|
||||||
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
expectation.expectedFulfillmentCount = 2;
|
|
||||||
expectation.assertForOverFulfill = YES;
|
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
if (!strongSelf) return;
|
||||||
|
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
[expectation fulfill];
|
||||||
|
|
||||||
if (!strongSelf) return;
|
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
||||||
|
if (mapView.direction != 0.0) {
|
||||||
|
NSLog(@"Reset to north");
|
||||||
|
[mapView resetNorth];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
[expectation fulfill];
|
[self.mapView setDirection:90 animated:YES];
|
||||||
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
|
||||||
if (mapView.direction != 0.0) {
|
|
||||||
NSLog(@"Reset to north");
|
|
||||||
[mapView resetNorth];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[self.mapView setDirection:90 animated:YES];
|
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
|
- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
|
||||||
|
// Reset to non-zero, prior to testing
|
||||||
|
[self.mapView setDirection:45 animated:NO];
|
||||||
|
|
||||||
// Reset to non-zero, prior to testing
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
[self.mapView setDirection:45 animated:NO];
|
expectation.expectedFulfillmentCount = 1;
|
||||||
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
expectation.expectedFulfillmentCount = 1;
|
__block BOOL startedReset = NO;
|
||||||
expectation.assertForOverFulfill = YES;
|
__block BOOL finishedReset = NO;
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
self.regionIsChanging = ^(MLNMapView *mapView) {
|
||||||
__block BOOL startedReset = NO;
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
__block BOOL finishedReset = NO;
|
if (!strongSelf) return;
|
||||||
|
|
||||||
self.regionIsChanging = ^(MLNMapView *mapView) {
|
if (!startedReset) {
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
NSLog(@"Reset to north, interrupting the previous transition");
|
||||||
if (!strongSelf) return;
|
startedReset = YES;
|
||||||
|
[mapView resetNorth];
|
||||||
|
finishedReset = YES;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!startedReset) {
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
NSLog(@"Reset to north, interrupting the previous transition");
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
startedReset = YES;
|
if (!strongSelf) return;
|
||||||
[mapView resetNorth];
|
|
||||||
finishedReset = YES;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
MLNTestAssert(strongSelf, startedReset);
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
|
||||||
if (!strongSelf) return;
|
|
||||||
|
|
||||||
MLNTestAssert(strongSelf, startedReset);
|
if (finishedReset) {
|
||||||
|
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
|
||||||
|
[expectation fulfill];
|
||||||
|
} else {
|
||||||
|
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (finishedReset) {
|
[self.mapView setDirection:90 animated:YES];
|
||||||
MLNTestAssert(strongSelf, !(reason & MLNCameraChangeReasonTransitionCancelled));
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
[expectation fulfill];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
MLNTestAssert(strongSelf, reason & MLNCameraChangeReasonTransitionCancelled);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
[self.mapView setDirection:90 animated:YES];
|
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
@"Camera should have reset to north. %0.3f", self.mapView.direction);
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSetCenterCancelsTransitions {
|
- (void)testSetCenterCancelsTransitions {
|
||||||
XCTestExpectation *cameraIsInDCExpectation = [self expectationWithDescription:@"camera reset to DC"];
|
XCTestExpectation *cameraIsInDCExpectation =
|
||||||
|
[self expectationWithDescription:@"camera reset to DC"];
|
||||||
|
|
||||||
CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487);
|
CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487);
|
||||||
CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
|
CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
|
||||||
|
|
||||||
double zoomLevel = 15.0;
|
double zoomLevel = 15.0;
|
||||||
|
|
||||||
[self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
|
[self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
|
||||||
[self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
|
[self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC),
|
dispatch_after(
|
||||||
dispatch_get_main_queue(),
|
dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
|
||||||
^{
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
|
||||||
|
|
||||||
[strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
|
[strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
MLNTestAssertEqualWithAccuracy(strongSelf, dc.latitude,
|
||||||
dc.latitude,
|
strongSelf.mapView.centerCoordinate.latitude, 0.0005,
|
||||||
strongSelf.mapView.centerCoordinate.latitude,
|
@"setting center coordinate should cancel transitions");
|
||||||
0.0005,
|
MLNTestAssertEqualWithAccuracy(strongSelf, dc.longitude,
|
||||||
@"setting center coordinate should cancel transitions");
|
strongSelf.mapView.centerCoordinate.longitude, 0.0005,
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
@"setting center coordinate should cancel transitions");
|
||||||
dc.longitude,
|
[cameraIsInDCExpectation fulfill];
|
||||||
strongSelf.mapView.centerCoordinate.longitude,
|
});
|
||||||
0.0005,
|
|
||||||
@"setting center coordinate should cancel transitions");
|
|
||||||
[cameraIsInDCExpectation fulfill];
|
|
||||||
});
|
|
||||||
|
|
||||||
[self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0];
|
[self waitForExpectations:@[ cameraIsInDCExpectation ] timeout:10.0];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testSetCenterCoordinateInDelegateMethod {
|
- (void)testSetCenterCoordinateInDelegateMethod {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
|
expectation.expectedFulfillmentCount = 2;
|
||||||
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
expectation.expectedFulfillmentCount = 2;
|
__block NSInteger delegateCallCount = 0;
|
||||||
expectation.assertForOverFulfill = YES;
|
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
|
||||||
__block NSInteger delegateCallCount = 0;
|
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
|
||||||
|
|
||||||
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
if (!strongSelf) return;
|
||||||
|
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
||||||
|
|
||||||
if (!strongSelf) return;
|
CLLocationCoordinate2D center = mapView.centerCoordinate;
|
||||||
|
|
||||||
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
switch (delegateCallCount) {
|
||||||
|
case 0: {
|
||||||
|
// Our center coordinate should match our target (assuming we're not
|
||||||
|
// constrained by zoom level)
|
||||||
|
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
|
||||||
|
@"center coordinate longitude should be at target");
|
||||||
|
|
||||||
CLLocationCoordinate2D center = mapView.centerCoordinate;
|
MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
|
||||||
|
@"center coordinate latitude should be at target");
|
||||||
|
|
||||||
switch(delegateCallCount) {
|
// Now set another coordinate.
|
||||||
case 0:
|
// Should take MLNAnimationDuration seconds (0.3s)
|
||||||
{
|
[mapView setCenterCoordinate:target2 animated:YES];
|
||||||
// Our center coordinate should match our target (assuming we're not
|
break;
|
||||||
// constrained by zoom level)
|
}
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
|
||||||
target.longitude,
|
|
||||||
center.longitude,
|
|
||||||
0.0005,
|
|
||||||
@"center coordinate longitude should be at target");
|
|
||||||
|
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
case 1: {
|
||||||
target.latitude,
|
// Our center coordinate should match our target (assuming we're not
|
||||||
center.latitude,
|
// constrained by zoom level)
|
||||||
0.0005,
|
MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
|
||||||
@"center coordinate latitude should be at target");
|
@"center coordinate longitude should be at target2");
|
||||||
|
|
||||||
// Now set another coordinate.
|
MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
|
||||||
// Should take MLNAnimationDuration seconds (0.3s)
|
@"center coordinate latitude should be at target2");
|
||||||
[mapView setCenterCoordinate:target2 animated:YES];
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
case 1:
|
default:
|
||||||
{
|
MLNTestFail(strongSelf);
|
||||||
// Our center coordinate should match our target (assuming we're not
|
break;
|
||||||
// constrained by zoom level)
|
}
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
|
||||||
target2.longitude,
|
|
||||||
center.longitude,
|
|
||||||
0.0005,
|
|
||||||
@"center coordinate longitude should be at target2");
|
|
||||||
|
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
delegateCallCount++;
|
||||||
target2.latitude,
|
|
||||||
center.latitude,
|
|
||||||
0.0005,
|
|
||||||
@"center coordinate latitude should be at target2");
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
[expectation fulfill];
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
// Should take MLNAnimationDuration seconds (0.3)
|
||||||
MLNTestFail(strongSelf);
|
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
|
||||||
break;
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
}
|
|
||||||
|
|
||||||
delegateCallCount++;
|
|
||||||
|
|
||||||
[expectation fulfill];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Should take MLNAnimationDuration seconds (0.3)
|
|
||||||
[self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
|
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testFlyToCameraInDelegateMethod {
|
- (void)testFlyToCameraInDelegateMethod {
|
||||||
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
|
__block NSInteger delegateCallCount = 0;
|
||||||
|
expectation.expectedFulfillmentCount = 3;
|
||||||
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
|
||||||
__block NSInteger delegateCallCount = 0;
|
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
|
||||||
expectation.expectedFulfillmentCount = 3;
|
|
||||||
expectation.assertForOverFulfill = YES;
|
|
||||||
|
|
||||||
CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
|
__block BOOL runloop = YES;
|
||||||
CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
|
|
||||||
|
|
||||||
__block BOOL runloop = YES;
|
NSTimeInterval stop0 = CACurrentMediaTime();
|
||||||
|
__block NSTimeInterval stop1 = 0.0;
|
||||||
|
__block NSTimeInterval stop2 = 0.0;
|
||||||
|
|
||||||
NSTimeInterval stop0 = CACurrentMediaTime();
|
double zoomLevel = 5.0;
|
||||||
__block NSTimeInterval stop1 = 0.0;
|
double altitude =
|
||||||
__block NSTimeInterval stop2 = 0.0;
|
MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
|
||||||
|
|
||||||
double zoomLevel = 5.0;
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
double altitude = MLNAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
|
MLNCameraTransitionTests *strongSelf = weakself;
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
if (!strongSelf) return;
|
||||||
|
|
||||||
MLNCameraTransitionTests *strongSelf = weakself;
|
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
||||||
|
|
||||||
if (!strongSelf) return;
|
CLLocationCoordinate2D center = mapView.centerCoordinate;
|
||||||
|
|
||||||
MLNTestAssert(strongSelf, mapView.userTrackingMode != MLNUserTrackingModeFollowWithHeading);
|
switch (delegateCallCount) {
|
||||||
|
case 0: {
|
||||||
|
stop1 = CACurrentMediaTime();
|
||||||
|
|
||||||
CLLocationCoordinate2D center = mapView.centerCoordinate;
|
// Our center coordinate should match our target (assuming we're not
|
||||||
|
// constrained by zoom level)
|
||||||
|
MLNTestAssertEqualWithAccuracy(strongSelf, target.longitude, center.longitude, 0.0005,
|
||||||
|
@"center coordinate longitude should be at target");
|
||||||
|
|
||||||
switch(delegateCallCount) {
|
MLNTestAssertEqualWithAccuracy(strongSelf, target.latitude, center.latitude, 0.0005,
|
||||||
case 0:
|
@"center coordinate latitude should be at target");
|
||||||
{
|
|
||||||
stop1 = CACurrentMediaTime();
|
|
||||||
|
|
||||||
// Our center coordinate should match our target (assuming we're not
|
// Now set another coordinate.
|
||||||
// constrained by zoom level)
|
MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
altitude:altitude
|
||||||
target.longitude,
|
pitch:0.0
|
||||||
center.longitude,
|
heading:0.0];
|
||||||
0.0005,
|
|
||||||
@"center coordinate longitude should be at target");
|
|
||||||
|
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
// flyToCamera can take a while...
|
||||||
target.latitude,
|
[mapView flyToCamera:camera
|
||||||
center.latitude,
|
completionHandler:^{
|
||||||
0.0005,
|
MLNTestAssert(strongSelf, !runloop,
|
||||||
@"center coordinate latitude should be at target");
|
@"Completion block should be called after delegate method");
|
||||||
|
[expectation fulfill];
|
||||||
|
stop2 = CACurrentMediaTime();
|
||||||
|
}];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Now set another coordinate.
|
case 1: {
|
||||||
MLNMapCamera *camera = [MLNMapCamera cameraLookingAtCenterCoordinate:target2
|
// Our center coordinate should match our target (assuming we're not
|
||||||
altitude:altitude
|
// constrained by zoom level)
|
||||||
pitch:0.0
|
MLNTestAssertEqualWithAccuracy(strongSelf, target2.longitude, center.longitude, 0.0005,
|
||||||
heading:0.0];
|
@"center coordinate longitude should be at target2");
|
||||||
|
|
||||||
// flyToCamera can take a while...
|
MLNTestAssertEqualWithAccuracy(strongSelf, target2.latitude, center.latitude, 0.0005,
|
||||||
[mapView flyToCamera:camera completionHandler:^{
|
@"center coordinate latitude should be at target2");
|
||||||
MLNTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method");
|
|
||||||
[expectation fulfill];
|
|
||||||
stop2 = CACurrentMediaTime();
|
|
||||||
}];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 1:
|
runloop = NO;
|
||||||
{
|
break;
|
||||||
// Our center coordinate should match our target (assuming we're not
|
}
|
||||||
// constrained by zoom level)
|
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
|
||||||
target2.longitude,
|
|
||||||
center.longitude,
|
|
||||||
0.0005,
|
|
||||||
@"center coordinate longitude should be at target2");
|
|
||||||
|
|
||||||
MLNTestAssertEqualWithAccuracy(strongSelf,
|
default:
|
||||||
target2.latitude,
|
MLNTestFail(strongSelf);
|
||||||
center.latitude,
|
break;
|
||||||
0.0005,
|
}
|
||||||
@"center coordinate latitude should be at target2");
|
|
||||||
|
|
||||||
runloop = NO;
|
delegateCallCount++;
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
[expectation fulfill];
|
||||||
MLNTestFail(strongSelf);
|
};
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
delegateCallCount++;
|
// Should take MLNAnimationDuration
|
||||||
|
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
|
||||||
|
|
||||||
[expectation fulfill];
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
};
|
|
||||||
|
|
||||||
// Should take MLNAnimationDuration
|
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
|
||||||
[self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
|
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld",
|
||||||
|
(long)delegateCallCount); // Once for the setDirection and once for the reset north
|
||||||
NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
|
|
||||||
NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
|
|
||||||
|
|
||||||
XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", (long)delegateCallCount); // Once for the setDirection and once for the reset north
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Pending tests
|
// MARK: - Pending tests
|
||||||
|
|
||||||
- (void)testContinuallyResettingNorthInIsChangingPENDING{
|
- (void)testContinuallyResettingNorthInIsChangingPENDING {
|
||||||
// See https://github.com/mapbox/mapbox-gl-native/pull/11614
|
// See https://github.com/mapbox/mapbox-gl-native/pull/11614
|
||||||
// This test currently fails, unsurprisingly, since we're continually
|
// This test currently fails, unsurprisingly, since we're continually
|
||||||
// setting the camera to the same parameters during its update.
|
// setting the camera to the same parameters during its update.
|
||||||
//
|
//
|
||||||
// Possible solutions/expectations:
|
// Possible solutions/expectations:
|
||||||
// - If you set camera parameters that match the *current* target parameters
|
// - If you set camera parameters that match the *current* target parameters
|
||||||
// then the transition could be a no-op. We'd need to consider any completion
|
// then the transition could be a no-op. We'd need to consider any completion
|
||||||
// block
|
// block
|
||||||
// - Ideally we would detect this case and disallow it.
|
// - Ideally we would detect this case and disallow it.
|
||||||
|
|
||||||
// Reset to non-zero, prior to testing
|
// Reset to non-zero, prior to testing
|
||||||
[self.mapView setDirection:45 animated:NO];
|
[self.mapView setDirection:45 animated:NO];
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
expectation.expectedFulfillmentCount = 2;
|
expectation.expectedFulfillmentCount = 2;
|
||||||
expectation.assertForOverFulfill = YES;
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
self.regionIsChanging = ^(MLNMapView *mapView) {
|
self.regionIsChanging = ^(MLNMapView *mapView) {
|
||||||
[mapView resetNorth];
|
[mapView resetNorth];
|
||||||
};
|
};
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
[expectation fulfill];
|
[expectation fulfill];
|
||||||
};
|
};
|
||||||
|
|
||||||
[self.mapView setDirection:90 animated:YES];
|
[self.mapView setDirection:90 animated:YES];
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
|
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
|
||||||
|
@"Camera should have reset to north. %0.3f", self.mapView.direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testContinuallySettingCoordinateInIsChangingPENDING {
|
- (void)testContinuallySettingCoordinateInIsChangingPENDING {
|
||||||
// See above comment in `-testContinuallyResettingNorthInIsChangingPENDING`
|
// See above comment in `-testContinuallyResettingNorthInIsChangingPENDING`
|
||||||
|
|
||||||
// Reset to non-zero, prior to testing
|
// Reset to non-zero, prior to testing
|
||||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
|
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
|
||||||
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
|
||||||
expectation.expectedFulfillmentCount = 2;
|
expectation.expectedFulfillmentCount = 2;
|
||||||
expectation.assertForOverFulfill = YES;
|
expectation.assertForOverFulfill = YES;
|
||||||
|
|
||||||
__weak MLNCameraTransitionTests *weakself = self;
|
__weak MLNCameraTransitionTests *weakself = self;
|
||||||
|
|
||||||
self.regionIsChanging = ^(MLNMapView *mapView) {
|
self.regionIsChanging = ^(MLNMapView *mapView) {
|
||||||
[weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
|
[weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
|
||||||
};
|
};
|
||||||
|
|
||||||
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
self.regionDidChange = ^(MLNMapView *mapView, MLNCameraChangeReason reason, BOOL animated) {
|
||||||
[expectation fulfill];
|
[expectation fulfill];
|
||||||
};
|
};
|
||||||
|
|
||||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
|
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
|
||||||
[self waitForExpectations:@[expectation] timeout:10];
|
[self waitForExpectations:@[ expectation ] timeout:10];
|
||||||
|
|
||||||
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
|
XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001,
|
||||||
|
@"Camera should have reset to north. %0.3f", self.mapView.direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
#import "MLNMapView+Impl.h"
|
||||||
#import "MLNMapViewIntegrationTest.h"
|
#import "MLNMapViewIntegrationTest.h"
|
||||||
#import "MLNMapView_Private.h"
|
#import "MLNMapView_Private.h"
|
||||||
#import "MLNMapView+Impl.h"
|
|
||||||
|
|
||||||
#include <mbgl/gfx/renderable.hpp>
|
#include <mbgl/gfx/renderable.hpp>
|
||||||
|
|
||||||
@@ -12,240 +12,247 @@
|
|||||||
// MARK: - Tests
|
// MARK: - Tests
|
||||||
|
|
||||||
- (void)waitForMapViewToBeRendered {
|
- (void)waitForMapViewToBeRendered {
|
||||||
[self waitForMapViewToBeRenderedWithTimeout:10];
|
[self waitForMapViewToBeRenderedWithTimeout:10];
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test does not strictly need to be in this test file/target. Including here for convenience.
|
// This test does not strictly need to be in this test file/target. Including here for convenience.
|
||||||
- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
|
- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle {
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
__weak id weakLayer = layer;
|
__weak id weakLayer = layer;
|
||||||
layer = nil;
|
layer = nil;
|
||||||
|
|
||||||
XCTAssertNil(weakLayer);
|
XCTAssertNil(weakLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingRemovingOpenGLLayerWithoutRendering {
|
- (void)testAddingRemovingOpenGLLayerWithoutRendering {
|
||||||
XCTAssertNotNil(self.style);
|
XCTAssertNotNil(self.style);
|
||||||
|
|
||||||
void(^addRemoveGLLayer)(void) = ^{
|
void (^addRemoveGLLayer)(void) = ^{
|
||||||
__weak id weakLayer = nil;
|
__weak id weakLayer = nil;
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
[self.style insertLayer:layer atIndex:0];
|
[self.style insertLayer:layer atIndex:0];
|
||||||
weakLayer = layer;
|
weakLayer = layer;
|
||||||
|
|
||||||
// Nil the layer prior to remove to ensure it's being retained
|
// Nil the layer prior to remove to ensure it's being retained
|
||||||
layer = nil;
|
layer = nil;
|
||||||
[self.style removeLayer:weakLayer];
|
[self.style removeLayer:weakLayer];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertNil(weakLayer);
|
XCTAssertNil(weakLayer);
|
||||||
};
|
};
|
||||||
|
|
||||||
addRemoveGLLayer();
|
addRemoveGLLayer();
|
||||||
addRemoveGLLayer();
|
addRemoveGLLayer();
|
||||||
addRemoveGLLayer();
|
addRemoveGLLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testReusingOpenGLLayerIdentifier {
|
- (void)testReusingOpenGLLayerIdentifier {
|
||||||
__weak MLNCustomStyleLayer *weakLayer2;
|
__weak MLNCustomStyleLayer *weakLayer2;
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer1 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
[self.style insertLayer:layer1 atIndex:0];
|
[self.style insertLayer:layer1 atIndex:0];
|
||||||
[self waitForMapViewToBeRendered];
|
|
||||||
[self.style removeLayer:layer1];
|
|
||||||
|
|
||||||
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
|
||||||
weakLayer2 = layer2;
|
|
||||||
|
|
||||||
XCTAssertNotNil(layer2);
|
|
||||||
XCTAssert(layer1 != layer2);
|
|
||||||
|
|
||||||
[self.style insertLayer:layer2 atIndex:0];
|
|
||||||
[self waitForMapViewToBeRendered];
|
|
||||||
[self.style removeLayer:layer2];
|
|
||||||
|
|
||||||
XCTAssertNil([layer1 style]);
|
|
||||||
XCTAssertNil([layer2 style]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, layer2 (and layer1) should still be around,
|
|
||||||
// since the render process needs to keep a reference to them.
|
|
||||||
XCTAssertNotNil(weakLayer2);
|
|
||||||
|
|
||||||
// Let render loop run enough to release the layers
|
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
XCTAssertNil(weakLayer2);
|
[self.style removeLayer:layer1];
|
||||||
|
|
||||||
|
MLNCustomStyleLayer *layer2 = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
|
weakLayer2 = layer2;
|
||||||
|
|
||||||
|
XCTAssertNotNil(layer2);
|
||||||
|
XCTAssert(layer1 != layer2);
|
||||||
|
|
||||||
|
[self.style insertLayer:layer2 atIndex:0];
|
||||||
|
[self waitForMapViewToBeRendered];
|
||||||
|
[self.style removeLayer:layer2];
|
||||||
|
|
||||||
|
XCTAssertNil([layer1 style]);
|
||||||
|
XCTAssertNil([layer2 style]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, layer2 (and layer1) should still be around,
|
||||||
|
// since the render process needs to keep a reference to them.
|
||||||
|
XCTAssertNotNil(weakLayer2);
|
||||||
|
|
||||||
|
// Let render loop run enough to release the layers
|
||||||
|
[self waitForMapViewToBeRendered];
|
||||||
|
XCTAssertNil(weakLayer2);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testAddingRemovingOpenGLLayer {
|
- (void)testAddingRemovingOpenGLLayer {
|
||||||
XCTAssertNotNil(self.style);
|
XCTAssertNotNil(self.style);
|
||||||
|
|
||||||
void(^addRemoveGLLayer)(void) = ^{
|
void (^addRemoveGLLayer)(void) = ^{
|
||||||
|
__weak id retrievedLayer = nil;
|
||||||
|
|
||||||
__weak id retrievedLayer = nil;
|
@autoreleasepool {
|
||||||
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
|
[self.style insertLayer:layer atIndex:0];
|
||||||
|
layer = nil;
|
||||||
|
|
||||||
@autoreleasepool {
|
[self waitForMapViewToBeRendered];
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
|
||||||
[self.style insertLayer:layer atIndex:0];
|
|
||||||
layer = nil;
|
|
||||||
|
|
||||||
[self waitForMapViewToBeRendered];
|
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
|
||||||
|
XCTAssertNotNil(retrievedLayer);
|
||||||
|
|
||||||
retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"];
|
[self.style removeLayer:retrievedLayer];
|
||||||
XCTAssertNotNil(retrievedLayer);
|
[self waitForMapViewToBeRendered];
|
||||||
|
}
|
||||||
|
|
||||||
[self.style removeLayer:retrievedLayer];
|
XCTAssertNil(retrievedLayer);
|
||||||
[self waitForMapViewToBeRendered];
|
};
|
||||||
}
|
|
||||||
|
|
||||||
XCTAssertNil(retrievedLayer);
|
addRemoveGLLayer();
|
||||||
};
|
addRemoveGLLayer();
|
||||||
|
addRemoveGLLayer();
|
||||||
addRemoveGLLayer();
|
|
||||||
addRemoveGLLayer();
|
|
||||||
addRemoveGLLayer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testReusingOpenGLLayer {
|
- (void)testReusingOpenGLLayer {
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
[self.style insertLayer:layer atIndex:0];
|
[self.style insertLayer:layer atIndex:0];
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
[self.style removeLayer:layer];
|
[self.style removeLayer:layer];
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
[self.style insertLayer:layer atIndex:0];
|
[self.style insertLayer:layer atIndex:0];
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
[self.style removeLayer:layer];
|
[self.style removeLayer:layer];
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle {
|
- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle {
|
||||||
__weak id weakLayer;
|
__weak id weakLayer;
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
weakLayer = layer;
|
weakLayer = layer;
|
||||||
[self.style insertLayer:layer atIndex:0];
|
[self.style insertLayer:layer atIndex:0];
|
||||||
layer = nil;
|
layer = nil;
|
||||||
|
|
||||||
[self waitForMapViewToBeRendered];
|
|
||||||
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
|
|
||||||
}
|
|
||||||
|
|
||||||
MLNStyleLayer *layer2 = weakLayer;
|
|
||||||
|
|
||||||
XCTAssertNotNil(weakLayer);
|
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
[self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]];
|
||||||
|
}
|
||||||
|
|
||||||
layer2 = nil;
|
MLNStyleLayer *layer2 = weakLayer;
|
||||||
XCTAssertNil(weakLayer);
|
|
||||||
|
XCTAssertNotNil(weakLayer);
|
||||||
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
|
layer2 = nil;
|
||||||
|
XCTAssertNil(weakLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged {
|
- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged {
|
||||||
__weak MLNCustomStyleLayer *weakLayer;
|
__weak MLNCustomStyleLayer *weakLayer;
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
{
|
{
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
weakLayer = layer;
|
weakLayer = layer;
|
||||||
[self.style insertLayer:layer atIndex:0];
|
[self.style insertLayer:layer atIndex:0];
|
||||||
layer = nil;
|
layer = nil;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
XCTAssertNotNil(weakLayer);
|
XCTAssertNotNil(weakLayer);
|
||||||
|
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
|
MLNStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
|
||||||
|
|
||||||
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
|
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
|
||||||
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
|
withExtension:@"json"];
|
||||||
[self.mapView setStyleURL:styleURL];
|
self.styleLoadingExpectation =
|
||||||
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:10];
|
[self expectationWithDescription:@"Map view should finish loading style."];
|
||||||
|
[self.mapView setStyleURL:styleURL];
|
||||||
|
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:10];
|
||||||
|
|
||||||
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been NULLed
|
// At this point the C++ CustomLayer will have been destroyed, and the rawLayer pointer has been
|
||||||
XCTAssert(weakLayer == layer2);
|
// NULLed
|
||||||
XCTAssertNotNil(weakLayer);
|
XCTAssert(weakLayer == layer2);
|
||||||
|
XCTAssertNotNil(weakLayer);
|
||||||
|
|
||||||
// Asking the style for the layer should return nil
|
// Asking the style for the layer should return nil
|
||||||
MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
|
MLNStyleLayer *layer3 = [self.mapView.style layerWithIdentifier:@"gl-layer"];
|
||||||
XCTAssertNil(layer3);
|
XCTAssertNil(layer3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs {
|
- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs {
|
||||||
__weak id weakLayer;
|
__weak id weakLayer;
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
|
||||||
|
withExtension:@"json"];
|
||||||
|
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds
|
||||||
|
styleURL:styleURL];
|
||||||
|
mapView2.delegate = self;
|
||||||
|
|
||||||
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
|
XCTAssertNil(mapView2.style);
|
||||||
MLNMapView *mapView2 = [[MLNMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL];
|
|
||||||
mapView2.delegate = self;
|
|
||||||
|
|
||||||
XCTAssertNil(mapView2.style);
|
self.styleLoadingExpectation =
|
||||||
|
[self expectationWithDescription:@"Map view should finish loading style."];
|
||||||
|
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||||
|
|
||||||
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
|
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
||||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
weakLayer = layer;
|
||||||
|
[mapView2.style insertLayer:layer atIndex:0];
|
||||||
|
layer = nil;
|
||||||
|
|
||||||
MLNCustomStyleLayer *layer = [[MLNCustomStyleLayer alloc] initWithIdentifier:@"gl-layer"];
|
[self waitForMapViewToBeRendered];
|
||||||
weakLayer = layer;
|
}
|
||||||
[mapView2.style insertLayer:layer atIndex:0];
|
XCTAssertNil(weakLayer);
|
||||||
layer = nil;
|
|
||||||
|
|
||||||
[self waitForMapViewToBeRendered];
|
|
||||||
}
|
|
||||||
XCTAssertNil(weakLayer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testMLNMapViewImplHasCorrectSize {
|
- (void)testMLNMapViewImplHasCorrectSize {
|
||||||
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"];
|
NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner"
|
||||||
self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."];
|
withExtension:@"json"];
|
||||||
[self.mapView setStyleURL:styleURL];
|
self.styleLoadingExpectation =
|
||||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
|
[self expectationWithDescription:@"Map view should finish loading style."];
|
||||||
[self waitForExpectations:@[self.styleLoadingExpectation] timeout:1];
|
[self.mapView setStyleURL:styleURL];
|
||||||
|
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(9.6315313, 52.4133574) animated:NO];
|
||||||
|
[self waitForExpectations:@[ self.styleLoadingExpectation ] timeout:1];
|
||||||
|
|
||||||
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl];
|
MLNMapViewImpl *mapViewImpl = [self.mapView viewImpl];
|
||||||
CGFloat scaleFactor = [UIScreen mainScreen].scale;
|
CGFloat scaleFactor = [UIScreen mainScreen].scale;
|
||||||
mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
|
mbgl::Size renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
|
||||||
mbgl::Size viewSize = {
|
mbgl::Size viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
|
||||||
static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
|
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
|
||||||
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Test that mapView and default renderable have the same size.
|
// Test that mapView and default renderable have the same size.
|
||||||
XCTAssertTrue(renderableSize == viewSize);
|
XCTAssertTrue(renderableSize == viewSize);
|
||||||
|
|
||||||
CLLocationCoordinate2D coordinates[] = {
|
CLLocationCoordinate2D coordinates[] = {CLLocationCoordinate2DMake(9.6315313, 52.4133574),
|
||||||
CLLocationCoordinate2DMake(9.6315313, 52.4133574),
|
CLLocationCoordinate2DMake(24.9410248, 60.1733244)};
|
||||||
CLLocationCoordinate2DMake(24.9410248, 60.1733244)};
|
|
||||||
|
|
||||||
MLNPointCollectionFeature *points = [MLNPointCollectionFeature pointCollectionWithCoordinates:coordinates count:sizeof(coordinates)/sizeof(coordinates[0])];
|
MLNPointCollectionFeature *points = [MLNPointCollectionFeature
|
||||||
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap" shape:points options:nil];
|
pointCollectionWithCoordinates:coordinates
|
||||||
[self.style addSource:source];
|
count:sizeof(coordinates) / sizeof(coordinates[0])];
|
||||||
|
MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"heatmap"
|
||||||
|
shape:points
|
||||||
|
options:nil];
|
||||||
|
[self.style addSource:source];
|
||||||
|
|
||||||
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer" source:source];
|
MLNHeatmapStyleLayer *heatmapLayer = [[MLNHeatmapStyleLayer alloc] initWithIdentifier:@"lineLayer"
|
||||||
[self.style addLayer:heatmapLayer];
|
source:source];
|
||||||
|
[self.style addLayer:heatmapLayer];
|
||||||
|
|
||||||
// Test that heatmap layer can create a texture and be successfully rendered.
|
// Test that heatmap layer can create a texture and be successfully rendered.
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
|
|
||||||
// Resize frame of the view.
|
// Resize frame of the view.
|
||||||
[self.mapView setFrame: CGRect{self.mapView.bounds.origin, {256, 256}}];
|
[self.mapView setFrame:CGRect{self.mapView.bounds.origin, {256, 256}}];
|
||||||
|
|
||||||
// Force sync re-layout.
|
// Force sync re-layout.
|
||||||
[self.mapView layoutIfNeeded];
|
[self.mapView layoutIfNeeded];
|
||||||
|
|
||||||
// Test that mapView and default renderable have the same size after re-layout.
|
// Test that mapView and default renderable have the same size after re-layout.
|
||||||
renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
|
renderableSize = mapViewImpl->getRendererBackend().getDefaultRenderable().getSize();
|
||||||
viewSize = { static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
|
viewSize = {static_cast<uint32_t>(self.mapView.bounds.size.width * scaleFactor),
|
||||||
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor) };
|
static_cast<uint32_t>(self.mapView.bounds.size.height * scaleFactor)};
|
||||||
XCTAssertTrue(renderableSize == viewSize);
|
XCTAssertTrue(renderableSize == viewSize);
|
||||||
[self waitForMapViewToBeRendered];
|
[self waitForMapViewToBeRendered];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,314 +1,327 @@
|
|||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
@import Mapbox;
|
@import Mapbox;
|
||||||
|
#import "MLNFoundation_Private.h"
|
||||||
#import "MLNMapViewIntegrationTest.h"
|
#import "MLNMapViewIntegrationTest.h"
|
||||||
#import "MLNNetworkConfiguration_Private.h"
|
#import "MLNNetworkConfiguration_Private.h"
|
||||||
#import "MLNOfflineStorage_Private.h"
|
#import "MLNOfflineStorage_Private.h"
|
||||||
#import "MLNFoundation_Private.h"
|
|
||||||
|
|
||||||
@interface MLNNetworkConfiguration (Testing)
|
@interface MLNNetworkConfiguration (Testing)
|
||||||
+ (void)testing_clearNativeNetworkManagerDelegate;
|
+ (void)testing_clearNativeNetworkManagerDelegate;
|
||||||
+ (id)testing_nativeNetworkManagerDelegate;
|
+ (id)testing_nativeNetworkManagerDelegate;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MLNNetworkConfigurationTestDelegate: NSObject <MLNNetworkConfigurationDelegate>
|
@interface MLNNetworkConfigurationTestDelegate : NSObject <MLNNetworkConfigurationDelegate>
|
||||||
@property (nonatomic) NSURLSession *(^handler)();
|
@property (nonatomic) NSURLSession * (^handler)();
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MLNNetworkConfigurationSessionDelegate: NSObject <NSURLSessionDelegate>
|
@interface MLNNetworkConfigurationSessionDelegate : NSObject <NSURLSessionDelegate>
|
||||||
@property (nonatomic) dispatch_block_t authHandler;
|
@property (nonatomic) dispatch_block_t authHandler;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface MLNNetworkConfigurationSessionDataDelegate: NSObject <NSURLSessionDataDelegate>
|
@interface MLNNetworkConfigurationSessionDataDelegate : NSObject <NSURLSessionDataDelegate>
|
||||||
@property (nonatomic) void (^dataHandler)(NSURLSessionDataTask *, NSData *);
|
@property (nonatomic) void (^dataHandler)(NSURLSessionDataTask *, NSData *);
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
@interface MLNNetworkConfigurationIntegrationTests : MLNIntegrationTestCase
|
@interface MLNNetworkConfigurationIntegrationTests : MLNIntegrationTestCase
|
||||||
@end
|
@end
|
||||||
|
|
||||||
#define ASSERT_NATIVE_DELEGATE_IS_NIL() \
|
#define ASSERT_NATIVE_DELEGATE_IS_NIL() \
|
||||||
XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
|
XCTAssertNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
|
||||||
|
|
||||||
#define ASSERT_NATIVE_DELEGATE_IS_NOT_NIL() \
|
#define ASSERT_NATIVE_DELEGATE_IS_NOT_NIL() \
|
||||||
XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
|
XCTAssertNotNil([MLNNetworkConfiguration testing_nativeNetworkManagerDelegate])
|
||||||
|
|
||||||
// NOTE: These tests are currently assumed to run in this specific order.
|
// NOTE: These tests are currently assumed to run in this specific order.
|
||||||
@implementation MLNNetworkConfigurationIntegrationTests
|
@implementation MLNNetworkConfigurationIntegrationTests
|
||||||
|
|
||||||
- (void)setUp {
|
- (void)setUp {
|
||||||
[super setUp];
|
[super setUp];
|
||||||
|
|
||||||
// Reset before each test
|
// Reset before each test
|
||||||
[MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate];
|
[MLNNetworkConfiguration testing_clearNativeNetworkManagerDelegate];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test0_NativeNetworkManagerDelegateIsSet
|
- (void)test0_NativeNetworkManagerDelegateIsSet {
|
||||||
{
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init];
|
||||||
MLNNetworkConfiguration *config = [[MLNNetworkConfiguration alloc] init];
|
[config resetNativeNetworkManagerDelegate];
|
||||||
[config resetNativeNetworkManagerDelegate];
|
|
||||||
|
|
||||||
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
||||||
|
|
||||||
id<MLNNativeNetworkDelegate> manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
|
id<MLNNativeNetworkDelegate> manager =
|
||||||
XCTAssertNotNil(manager);
|
MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
|
||||||
|
XCTAssertNotNil(manager);
|
||||||
|
|
||||||
// Expected properties
|
// Expected properties
|
||||||
XCTAssertNotNil([manager sessionConfiguration]);
|
XCTAssertNotNil([manager sessionConfiguration]);
|
||||||
|
|
||||||
[MLNNetworkConfiguration sharedManager];
|
[MLNNetworkConfiguration sharedManager];
|
||||||
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
||||||
XCTAssert(delegate != delegate2);
|
XCTAssert(delegate != delegate2);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager
|
- (void)test1_NativeNetworkManagerDelegateIsSetBySharedManager {
|
||||||
{
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
|
||||||
|
|
||||||
// Just calling the shared manager is also sufficient (even though, it's a
|
// Just calling the shared manager is also sufficient (even though, it's a
|
||||||
// singleton and created with a dispatch_once, the delegate is re-set for
|
// singleton and created with a dispatch_once, the delegate is re-set for
|
||||||
// each call.
|
// each call.
|
||||||
[MLNNetworkConfiguration sharedManager];
|
[MLNNetworkConfiguration sharedManager];
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
||||||
|
|
||||||
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
||||||
id<MLNNativeNetworkDelegate> manager = MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
|
id<MLNNativeNetworkDelegate> manager =
|
||||||
XCTAssertNotNil(manager);
|
MLN_OBJC_DYNAMIC_CAST_AS_PROTOCOL(delegate, MLNNativeNetworkDelegate);
|
||||||
|
XCTAssertNotNil(manager);
|
||||||
|
|
||||||
// Expected properties
|
// Expected properties
|
||||||
XCTAssertNotNil([manager sessionConfiguration]);
|
XCTAssertNotNil([manager sessionConfiguration]);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test2_NativeNetworkManagerDelegateIsSet
|
- (void)test2_NativeNetworkManagerDelegateIsSet {
|
||||||
{
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
[MLNNetworkConfiguration sharedManager];
|
||||||
[MLNNetworkConfiguration sharedManager];
|
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
||||||
id delegate = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
|
||||||
|
|
||||||
[[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate];
|
[[MLNNetworkConfiguration sharedManager] resetNativeNetworkManagerDelegate];
|
||||||
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
id delegate2 = [MLNNetworkConfiguration testing_nativeNetworkManagerDelegate];
|
||||||
XCTAssert(delegate == delegate2);
|
XCTAssert(delegate == delegate2);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage
|
- (void)test3_NativeNetworkManagerDelegateIsSetBySharedOfflineStorage {
|
||||||
{
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
|
||||||
|
|
||||||
// Similar to `[MLNNetworkConfiguration sharedManager]`,
|
// Similar to `[MLNNetworkConfiguration sharedManager]`,
|
||||||
// `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate.
|
// `[MLNOfflineStorage sharedOfflineStorage]` also sets the delegate.
|
||||||
[MLNOfflineStorage sharedOfflineStorage];
|
[MLNOfflineStorage sharedOfflineStorage];
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime
|
- (void)test4_NativeNetworkManagerDelegateIsSetBySharedOfflineStorageASecondTime {
|
||||||
{
|
// Testing a second time...
|
||||||
// Testing a second time...
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
[MLNOfflineStorage sharedOfflineStorage];
|
||||||
[MLNOfflineStorage sharedOfflineStorage];
|
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit
|
- (void)test5_NativeNetworkManagerDelegateIsSetByMapViewInit {
|
||||||
{
|
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NIL();
|
(void)[[MLNMapView alloc] init];
|
||||||
(void)[[MLNMapView alloc] init];
|
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
||||||
ASSERT_NATIVE_DELEGATE_IS_NOT_NIL();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationDelegateIsNil
|
- (void)testNetworkConfigurationDelegateIsNil {
|
||||||
{
|
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
|
||||||
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
|
XCTAssertNil(manager.delegate);
|
||||||
XCTAssertNil(manager.delegate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)internalTestNetworkConfigurationWithSession:(NSURLSession*)session shouldDownload:(BOOL)shouldDownload {
|
- (void)internalTestNetworkConfigurationWithSession:(NSURLSession *)session
|
||||||
|
shouldDownload:(BOOL)shouldDownload {
|
||||||
|
__block BOOL didCallSessionDelegate = NO;
|
||||||
|
__block BOOL isMainThread = YES;
|
||||||
|
|
||||||
__block BOOL didCallSessionDelegate = NO;
|
// Setup delegate object that provides a NSURLSession
|
||||||
__block BOOL isMainThread = YES;
|
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
|
||||||
|
MLNNetworkConfigurationTestDelegate *delegate =
|
||||||
// Setup delegate object that provides a NSURLSession
|
[[MLNNetworkConfigurationTestDelegate alloc] init];
|
||||||
MLNNetworkConfiguration *manager = [MLNNetworkConfiguration sharedManager];
|
delegate.handler = ^{
|
||||||
MLNNetworkConfigurationTestDelegate *delegate = [[MLNNetworkConfigurationTestDelegate alloc] init];
|
NSURLSession *internalSession;
|
||||||
delegate.handler = ^{
|
@synchronized(self) {
|
||||||
NSURLSession *internalSession;
|
didCallSessionDelegate = YES;
|
||||||
@synchronized (self) {
|
isMainThread = [NSThread isMainThread];
|
||||||
didCallSessionDelegate = YES;
|
internalSession = session;
|
||||||
isMainThread = [NSThread isMainThread];
|
|
||||||
internalSession = session;
|
|
||||||
}
|
|
||||||
return internalSession;
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.delegate = delegate;
|
|
||||||
|
|
||||||
// The following is modified/taken from MLNOfflineStorageTests as we do not yet have a
|
|
||||||
// good mechanism to test FileSource (in this SDK)
|
|
||||||
//
|
|
||||||
// Want to ensure we download from the network; nuclear option
|
|
||||||
{
|
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Expect database to be reset without an error."];
|
|
||||||
[[MLNOfflineStorage sharedOfflineStorage] resetDatabaseWithCompletionHandler:^(NSError * _Nullable error) {
|
|
||||||
XCTAssertNil(error);
|
|
||||||
[expectation fulfill];
|
|
||||||
}];
|
|
||||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
|
||||||
}
|
}
|
||||||
|
return internalSession;
|
||||||
|
};
|
||||||
|
|
||||||
// Boston
|
manager.delegate = delegate;
|
||||||
MLNCoordinateBounds bounds = {
|
|
||||||
{ .latitude = 42.360, .longitude = -71.056 },
|
|
||||||
{ .latitude = 42.358, .longitude = -71.053 },
|
|
||||||
};
|
|
||||||
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
|
|
||||||
MLNTilePyramidOfflineRegion *region = [[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
|
|
||||||
bounds:bounds
|
|
||||||
fromZoomLevel:20
|
|
||||||
toZoomLevel:20];
|
|
||||||
|
|
||||||
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{
|
// The following is modified/taken from MLNOfflineStorageTests as we do not yet have a
|
||||||
@"Name": @"Faneuil Hall"
|
// good mechanism to test FileSource (in this SDK)
|
||||||
}];
|
//
|
||||||
|
// Want to ensure we download from the network; nuclear option
|
||||||
__block MLNOfflinePack *pack = nil;
|
{
|
||||||
|
XCTestExpectation *expectation =
|
||||||
// Add pack
|
[self expectationWithDescription:@"Expect database to be reset without an error."];
|
||||||
{
|
[[MLNOfflineStorage sharedOfflineStorage]
|
||||||
XCTestExpectation *additionCompletionHandlerExpectation = [self expectationWithDescription:@"add pack completion handler"];
|
resetDatabaseWithCompletionHandler:^(NSError *_Nullable error) {
|
||||||
|
XCTAssertNil(error);
|
||||||
[[MLNOfflineStorage sharedOfflineStorage] addPackForRegion:region
|
[expectation fulfill];
|
||||||
withContext:context
|
|
||||||
completionHandler:^(MLNOfflinePack * _Nullable completionHandlerPack, NSError * _Nullable error) {
|
|
||||||
pack = completionHandlerPack;
|
|
||||||
[additionCompletionHandlerExpectation fulfill];
|
|
||||||
}];
|
}];
|
||||||
[self waitForExpectationsWithTimeout:5 handler:nil];
|
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssert(pack.state == MLNOfflinePackStateInactive);
|
// Boston
|
||||||
|
MLNCoordinateBounds bounds = {
|
||||||
|
{.latitude = 42.360, .longitude = -71.056},
|
||||||
|
{.latitude = 42.358, .longitude = -71.053},
|
||||||
|
};
|
||||||
|
NSURL *styleURL = [[MLNStyle predefinedStyle:@"Bright"] url];
|
||||||
|
MLNTilePyramidOfflineRegion *region =
|
||||||
|
[[MLNTilePyramidOfflineRegion alloc] initWithStyleURL:styleURL
|
||||||
|
bounds:bounds
|
||||||
|
fromZoomLevel:20
|
||||||
|
toZoomLevel:20];
|
||||||
|
|
||||||
// Download
|
NSData *context = [NSKeyedArchiver archivedDataWithRootObject:@{@"Name" : @"Faneuil Hall"}];
|
||||||
{
|
|
||||||
XCTestExpectation *expectation = [self expectationForNotification:MLNOfflinePackProgressChangedNotification object:pack handler:^BOOL(NSNotification * _Nonnull notification) {
|
__block MLNOfflinePack *pack = nil;
|
||||||
return pack.state == MLNOfflinePackStateComplete;
|
|
||||||
|
// Add pack
|
||||||
|
{
|
||||||
|
XCTestExpectation *additionCompletionHandlerExpectation =
|
||||||
|
[self expectationWithDescription:@"add pack completion handler"];
|
||||||
|
|
||||||
|
[[MLNOfflineStorage sharedOfflineStorage]
|
||||||
|
addPackForRegion:region
|
||||||
|
withContext:context
|
||||||
|
completionHandler:^(MLNOfflinePack *_Nullable completionHandlerPack,
|
||||||
|
NSError *_Nullable error) {
|
||||||
|
pack = completionHandlerPack;
|
||||||
|
[additionCompletionHandlerExpectation fulfill];
|
||||||
}];
|
}];
|
||||||
|
[self waitForExpectationsWithTimeout:5 handler:nil];
|
||||||
|
}
|
||||||
|
|
||||||
expectation.inverted = !shouldDownload;
|
XCTAssert(pack.state == MLNOfflinePackStateInactive);
|
||||||
[pack resume];
|
|
||||||
|
|
||||||
[self waitForExpectations:@[expectation] timeout:15];
|
// Download
|
||||||
}
|
{
|
||||||
|
XCTestExpectation *expectation =
|
||||||
|
[self expectationForNotification:MLNOfflinePackProgressChangedNotification
|
||||||
|
object:pack
|
||||||
|
handler:^BOOL(NSNotification *_Nonnull notification) {
|
||||||
|
return pack.state == MLNOfflinePackStateComplete;
|
||||||
|
}];
|
||||||
|
|
||||||
XCTAssert(didCallSessionDelegate);
|
expectation.inverted = !shouldDownload;
|
||||||
XCTAssertFalse(isMainThread);
|
[pack resume];
|
||||||
|
|
||||||
// Remove pack, so we don't affect other tests
|
[self waitForExpectations:@[ expectation ] timeout:15];
|
||||||
{
|
}
|
||||||
XCTestExpectation *expectation = [self expectationWithDescription:@"remove pack completion handler"];
|
|
||||||
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack withCompletionHandler:^(NSError * _Nullable error) {
|
XCTAssert(didCallSessionDelegate);
|
||||||
[expectation fulfill];
|
XCTAssertFalse(isMainThread);
|
||||||
}];
|
|
||||||
[self waitForExpectationsWithTimeout:5 handler:nil];
|
// Remove pack, so we don't affect other tests
|
||||||
}
|
{
|
||||||
|
XCTestExpectation *expectation =
|
||||||
|
[self expectationWithDescription:@"remove pack completion handler"];
|
||||||
|
[[MLNOfflineStorage sharedOfflineStorage] removePack:pack
|
||||||
|
withCompletionHandler:^(NSError *_Nullable error) {
|
||||||
|
[expectation fulfill];
|
||||||
|
}];
|
||||||
|
[self waitForExpectationsWithTimeout:5 handler:nil];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithSharedSessionLOCKED {
|
- (void)testNetworkConfigurationWithSharedSessionLOCKED {
|
||||||
NSURLSession *session = [NSURLSession sharedSession];
|
NSURLSession *session = [NSURLSession sharedSession];
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithBackgroundSessionConfiguration {
|
- (void)testNetworkConfigurationWithBackgroundSessionConfiguration {
|
||||||
// Background session configurations are NOT supported, we expect this test
|
// Background session configurations are NOT supported, we expect this test
|
||||||
// trigger an exception
|
// trigger an exception
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)];
|
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
backgroundSessionConfigurationWithIdentifier:NSStringFromSelector(_cmd)];
|
||||||
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||||
|
|
||||||
XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]);
|
XCTAssert([session isKindOfClass:NSClassFromString(@"__NSURLBackgroundSession")]);
|
||||||
|
|
||||||
// We cannot do this yet, as it requires intecepting the exception in gl-native
|
// We cannot do this yet, as it requires intecepting the exception in gl-native
|
||||||
// It makes more sense to support background configs (requiring delegation
|
// It makes more sense to support background configs (requiring delegation
|
||||||
// rather than blocks in gl-native)
|
// rather than blocks in gl-native)
|
||||||
// [self internalTestNetworkConfigurationWithSession:session], NSException, NSInvalidArgumentException);
|
// [self internalTestNetworkConfigurationWithSession:session], NSException,
|
||||||
|
// NSInvalidArgumentException);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithDefaultSessionConfigurationLOCKED {
|
- (void)testNetworkConfigurationWithDefaultSessionConfigurationLOCKED {
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||||
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED {
|
- (void)testNetworkConfigurationWithEmphemeralSessionConfigurationLOCKED {
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
[NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||||
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithSessionConfigurationWithDelegateLOCKED {
|
- (void)testNetworkConfigurationWithSessionConfigurationWithDelegateLOCKED {
|
||||||
__block BOOL didCallAuthChallenge = NO;
|
__block BOOL didCallAuthChallenge = NO;
|
||||||
__block BOOL isMainThread = YES;
|
__block BOOL isMainThread = YES;
|
||||||
|
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
MLNNetworkConfigurationSessionDelegate *delegate = [[MLNNetworkConfigurationSessionDelegate alloc] init];
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
delegate.authHandler = ^{
|
MLNNetworkConfigurationSessionDelegate *delegate =
|
||||||
@synchronized (self) {
|
[[MLNNetworkConfigurationSessionDelegate alloc] init];
|
||||||
didCallAuthChallenge = YES;
|
delegate.authHandler = ^{
|
||||||
isMainThread = [NSThread isMainThread];
|
@synchronized(self) {
|
||||||
}
|
didCallAuthChallenge = YES;
|
||||||
};
|
isMainThread = [NSThread isMainThread];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
|
||||||
delegate:delegate
|
delegate:delegate
|
||||||
delegateQueue:nil];
|
delegateQueue:nil];
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:NO];
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:NO];
|
||||||
|
|
||||||
[session finishTasksAndInvalidate];
|
[session finishTasksAndInvalidate];
|
||||||
|
|
||||||
XCTAssertFalse(isMainThread);
|
XCTAssertFalse(isMainThread);
|
||||||
XCTAssert(didCallAuthChallenge);
|
XCTAssert(didCallAuthChallenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testFailureForNetworkConfigurationWithSessionWithDataDelegateLOCKED {
|
- (void)testFailureForNetworkConfigurationWithSessionWithDataDelegateLOCKED {
|
||||||
__block BOOL didCallReceiveData = NO;
|
__block BOOL didCallReceiveData = NO;
|
||||||
|
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
MLNNetworkConfigurationSessionDataDelegate *delegate = [[MLNNetworkConfigurationSessionDataDelegate alloc] init];
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) {
|
MLNNetworkConfigurationSessionDataDelegate *delegate =
|
||||||
@synchronized (self) {
|
[[MLNNetworkConfigurationSessionDataDelegate alloc] init];
|
||||||
didCallReceiveData = YES;
|
delegate.dataHandler = ^(NSURLSessionDataTask *task, NSData *data) {
|
||||||
}
|
@synchronized(self) {
|
||||||
};
|
didCallReceiveData = YES;
|
||||||
|
|
||||||
// NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate
|
|
||||||
// are NOT supported.
|
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
|
|
||||||
delegate:delegate
|
|
||||||
delegateQueue:nil];
|
|
||||||
|
|
||||||
BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)];
|
|
||||||
XCTAssert(conforms);
|
|
||||||
#ifdef DEBUG
|
|
||||||
if (conforms) {
|
|
||||||
NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported");
|
|
||||||
}
|
}
|
||||||
#else
|
};
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
|
||||||
#endif
|
|
||||||
[session finishTasksAndInvalidate];
|
|
||||||
|
|
||||||
XCTAssertFalse(didCallReceiveData);
|
// NOTE: Sessions with a delegate that conforms to NSURLSessionDataDelegate
|
||||||
|
// are NOT supported.
|
||||||
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig
|
||||||
|
delegate:delegate
|
||||||
|
delegateQueue:nil];
|
||||||
|
|
||||||
|
BOOL conforms = [session.delegate conformsToProtocol:@protocol(NSURLSessionDataDelegate)];
|
||||||
|
XCTAssert(conforms);
|
||||||
|
#ifdef DEBUG
|
||||||
|
if (conforms) {
|
||||||
|
NSLog(@"Session delegates conforming to NSURLSessionDataDelegate are not supported");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
||||||
|
#endif
|
||||||
|
[session finishTasksAndInvalidate];
|
||||||
|
|
||||||
|
XCTAssertFalse(didCallReceiveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED {
|
- (void)testNetworkConfigurationWithSessionConfigurationWithCustomHeadersLOCKED {
|
||||||
// Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration``
|
// Custom session configuration, based on ``MLNNetworkConfiguration/defaultSessionConfiguration``
|
||||||
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
|
NSURLSessionConfiguration *sessionConfig =
|
||||||
sessionConfig.HTTPAdditionalHeaders = @{ @"testing" : @YES };
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
||||||
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
|
sessionConfig.HTTPAdditionalHeaders = @{@"testing" : @YES};
|
||||||
sessionConfig.timeoutIntervalForResource = 30;
|
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
|
||||||
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
sessionConfig.timeoutIntervalForResource = 30;
|
||||||
sessionConfig.URLCache = nil;
|
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
||||||
|
sessionConfig.URLCache = nil;
|
||||||
|
|
||||||
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
||||||
|
|
||||||
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
[self internalTestNetworkConfigurationWithSession:session shouldDownload:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -317,33 +330,38 @@
|
|||||||
|
|
||||||
@implementation MLNNetworkConfigurationTestDelegate
|
@implementation MLNNetworkConfigurationTestDelegate
|
||||||
- (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration {
|
- (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration {
|
||||||
if (self.handler) {
|
if (self.handler) {
|
||||||
return self.handler();
|
return self.handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// MARK: - NSURLSession delegate
|
// MARK: - NSURLSession delegate
|
||||||
|
|
||||||
@implementation MLNNetworkConfigurationSessionDelegate
|
@implementation MLNNetworkConfigurationSessionDelegate
|
||||||
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
|
- (void)URLSession:(NSURLSession *)session
|
||||||
if (self.authHandler) {
|
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||||
self.authHandler();
|
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||||
}
|
NSURLCredential *_Nullable credential))completionHandler {
|
||||||
|
if (self.authHandler) {
|
||||||
|
self.authHandler();
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel the challenge
|
// Cancel the challenge
|
||||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// MARK: - NSURLSession data delegate
|
// MARK: - NSURLSession data delegate
|
||||||
|
|
||||||
@implementation MLNNetworkConfigurationSessionDataDelegate
|
@implementation MLNNetworkConfigurationSessionDataDelegate
|
||||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
|
- (void)URLSession:(NSURLSession *)session
|
||||||
if (self.dataHandler) {
|
dataTask:(NSURLSessionDataTask *)dataTask
|
||||||
self.dataHandler(dataTask, data);
|
didReceiveData:(NSData *)data {
|
||||||
}
|
if (self.dataHandler) {
|
||||||
|
self.dataHandler(dataTask, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,18 +7,17 @@
|
|||||||
|
|
||||||
- (BOOL)application:(UIApplication*)application
|
- (BOOL)application:(UIApplication*)application
|
||||||
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
||||||
|
|
||||||
#ifndef MLN_LOGGING_DISABLED
|
#ifndef MLN_LOGGING_DISABLED
|
||||||
[MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault;
|
[MLNLoggingConfiguration sharedConfiguration].loggingLevel = MLNLoggingLevelFault;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
[MLNSettings useWellKnownTileServer:MLNMapTiler];
|
[MLNSettings useWellKnownTileServer:MLNMapTiler];
|
||||||
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||||
self.window.rootViewController = [MBXBenchViewController new];
|
self.window.rootViewController = [MBXBenchViewController new];
|
||||||
[self.window makeKeyAndVisible];
|
[self.window makeKeyAndVisible];
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -19,31 +19,30 @@
|
|||||||
frameRenderingTime:(double)frameRenderingTime;
|
frameRenderingTime:(double)frameRenderingTime;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
class BenchMapObserver : public mbgl::MapObserver {
|
class BenchMapObserver : public mbgl::MapObserver {
|
||||||
public:
|
public:
|
||||||
BenchMapObserver() = delete;
|
BenchMapObserver() = delete;
|
||||||
BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {}
|
BenchMapObserver(id<BenchMapDelegate> mapDelegate_) : mapDelegate(mapDelegate_) {}
|
||||||
virtual ~BenchMapObserver() = default;
|
virtual ~BenchMapObserver() = default;
|
||||||
|
|
||||||
void onDidFinishRenderingFrame(const RenderFrameStatus& status) override final {
|
void onDidFinishRenderingFrame(const RenderFrameStatus &status) override final {
|
||||||
//NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3);
|
// NSLog(@"Frame encoding time: %4.1f ms", status.renderingStats.encodingTime * 1e3);
|
||||||
//NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3);
|
// NSLog(@"Frame rendering time: %4.1f ms", status.renderingStats.renderingTime * 1e3);
|
||||||
|
|
||||||
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
|
bool fullyRendered = status.mode == mbgl::MapObserver::RenderMode::Full;
|
||||||
[mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered
|
[mapDelegate mapDidFinishRenderingFrameFullyRendered:fullyRendered
|
||||||
frameEncodingTime:status.renderingStats.encodingTime
|
frameEncodingTime:status.renderingStats.encodingTime
|
||||||
frameRenderingTime:status.renderingStats.renderingTime];
|
frameRenderingTime:status.renderingStats.renderingTime];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
__weak id<BenchMapDelegate> mapDelegate = nullptr;
|
__weak id<BenchMapDelegate> mapDelegate = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@interface MBXBenchViewController () <BenchMapDelegate> {
|
@interface MBXBenchViewController () <BenchMapDelegate> {
|
||||||
std::unique_ptr<BenchMapObserver> observer;
|
std::unique_ptr<BenchMapObserver> observer;
|
||||||
std::unique_ptr<mbgl::HeadlessFrontend> frontend;
|
std::unique_ptr<mbgl::HeadlessFrontend> frontend;
|
||||||
std::unique_ptr<mbgl::Map> map;
|
std::unique_ptr<mbgl::Map> map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@property (nonatomic) UIImageView *imageView;
|
@property (nonatomic) UIImageView *imageView;
|
||||||
@@ -54,125 +53,129 @@ protected:
|
|||||||
|
|
||||||
// MARK: - Setup
|
// MARK: - Setup
|
||||||
|
|
||||||
+ (void)initialize
|
+ (void)initialize {
|
||||||
{
|
if (self == [MBXBenchViewController class]) {
|
||||||
if (self == [MBXBenchViewController class])
|
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
||||||
{
|
@"MBXUserTrackingMode" : @(MLNUserTrackingModeNone),
|
||||||
[[NSUserDefaults standardUserDefaults] registerDefaults:@{
|
@"MBXShowsUserLocation" : @NO,
|
||||||
@"MBXUserTrackingMode": @(MLNUserTrackingModeNone),
|
@"MBXDebug" : @NO,
|
||||||
@"MBXShowsUserLocation": @NO,
|
}];
|
||||||
@"MBXDebug": @NO,
|
}
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidLoad
|
- (void)viewDidLoad {
|
||||||
{
|
[super viewDidLoad];
|
||||||
[super viewDidLoad];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)viewDidAppear:(BOOL)animated
|
- (void)viewDidAppear:(BOOL)animated {
|
||||||
{
|
[super viewDidAppear:animated];
|
||||||
[super viewDidAppear:animated];
|
|
||||||
|
|
||||||
#ifdef LOG_TO_DOCUMENTS_DIR
|
#ifdef LOG_TO_DOCUMENTS_DIR
|
||||||
[self setAndRedirectLogFileToDocuments];
|
[self setAndRedirectLogFileToDocuments];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Use a local style and local assets if they’ve been downloaded.
|
// Use a local style and local assets if they’ve been downloaded.
|
||||||
NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11" withExtension:@"pbf" subdirectory:@"tiles/tiles/v3/5/7"];
|
NSURL *tile = [[NSBundle mainBundle] URLForResource:@"11"
|
||||||
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles" withExtension:@"json" subdirectory:@"tiles"];
|
withExtension:@"pbf"
|
||||||
const std::vector<std::string> styles = {
|
subdirectory:@"tiles/tiles/v3/5/7"];
|
||||||
"maptiler://maps/streets",
|
NSURL *tileSourceURL = [[NSBundle mainBundle] URLForResource:@"openmaptiles"
|
||||||
};
|
withExtension:@"json"
|
||||||
constexpr auto styleIndex = 0;
|
subdirectory:@"tiles"];
|
||||||
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json" : [NSString stringWithCString:styles[styleIndex].c_str() encoding:NSUTF8StringEncoding]];
|
const std::vector<std::string> styles = {
|
||||||
NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
|
"maptiler://maps/streets",
|
||||||
|
};
|
||||||
|
constexpr auto styleIndex = 0;
|
||||||
|
NSURL *url = [NSURL URLWithString:tile ? @"asset://styles/streets.json"
|
||||||
|
: [NSString stringWithCString:styles[styleIndex].c_str()
|
||||||
|
encoding:NSUTF8StringEncoding]];
|
||||||
|
NSLog(@"Using style URL: \"%@\"", [url absoluteString]);
|
||||||
|
|
||||||
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
|
self.imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
|
||||||
self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
self.imageView.autoresizingMask =
|
||||||
[self.view addSubview:self.imageView];
|
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
[self.view addSubview:self.imageView];
|
||||||
|
|
||||||
mbgl::Size viewSize = { static_cast<uint32_t>(self.view.bounds.size.width),
|
mbgl::Size viewSize = {static_cast<uint32_t>(self.view.bounds.size.width),
|
||||||
static_cast<uint32_t>(self.view.bounds.size.height) };
|
static_cast<uint32_t>(self.view.bounds.size.height)};
|
||||||
auto pixelRatio = [[UIScreen mainScreen] scale];
|
auto pixelRatio = [[UIScreen mainScreen] scale];
|
||||||
|
|
||||||
observer = std::make_unique<BenchMapObserver>(self);
|
observer = std::make_unique<BenchMapObserver>(self);
|
||||||
frontend = std::make_unique<mbgl::HeadlessFrontend>(
|
frontend = std::make_unique<mbgl::HeadlessFrontend>(
|
||||||
viewSize,
|
viewSize, pixelRatio, mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
|
||||||
pixelRatio,
|
mbgl::gfx::ContextMode::Unique,
|
||||||
mbgl::gfx::HeadlessBackend::SwapBehaviour::Flush,
|
/* localFontFamily */ std::nullopt,
|
||||||
mbgl::gfx::ContextMode::Unique,
|
/* invalidateOnUpdate */ false);
|
||||||
/* localFontFamily */ std::nullopt,
|
|
||||||
/* invalidateOnUpdate */ false
|
|
||||||
);
|
|
||||||
|
|
||||||
mbgl::MapOptions mapOptions;
|
mbgl::MapOptions mapOptions;
|
||||||
mapOptions.withMapMode(mbgl::MapMode::Continuous)
|
mapOptions.withMapMode(mbgl::MapMode::Continuous)
|
||||||
.withSize(viewSize)
|
.withSize(viewSize)
|
||||||
.withPixelRatio(pixelRatio)
|
.withPixelRatio(pixelRatio)
|
||||||
.withConstrainMode(mbgl::ConstrainMode::None)
|
.withConstrainMode(mbgl::ConstrainMode::None)
|
||||||
.withViewportMode(mbgl::ViewportMode::Default)
|
.withViewportMode(mbgl::ViewportMode::Default)
|
||||||
.withCrossSourceCollisions(true);
|
.withCrossSourceCollisions(true);
|
||||||
|
|
||||||
mbgl::TileServerOptions* tileServerOptions = [[MLNSettings sharedSettings] tileServerOptionsInternal];
|
mbgl::TileServerOptions *tileServerOptions =
|
||||||
mbgl::ResourceOptions resourceOptions;
|
[[MLNSettings sharedSettings] tileServerOptionsInternal];
|
||||||
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
|
mbgl::ResourceOptions resourceOptions;
|
||||||
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
|
resourceOptions.withCachePath(MLNOfflineStorage.sharedOfflineStorage.databasePath.UTF8String)
|
||||||
.withTileServerOptions(*tileServerOptions);
|
.withAssetPath([NSBundle mainBundle].resourceURL.path.UTF8String)
|
||||||
mbgl::ClientOptions clientOptions;
|
.withTileServerOptions(*tileServerOptions);
|
||||||
|
mbgl::ClientOptions clientOptions;
|
||||||
|
|
||||||
auto apiKey = [[MLNSettings sharedSettings] apiKey];
|
auto apiKey = [[MLNSettings sharedSettings] apiKey];
|
||||||
if (apiKey) {
|
if (apiKey) {
|
||||||
resourceOptions.withApiKey([apiKey UTF8String]);
|
resourceOptions.withApiKey([apiKey UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
map = std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
|
map =
|
||||||
map->setSize(viewSize);
|
std::make_unique<mbgl::Map>(*frontend, *observer, mapOptions, resourceOptions, clientOptions);
|
||||||
map->setDebug(mbgl::MapDebugOptions::NoDebug);
|
map->setSize(viewSize);
|
||||||
map->getStyle().loadURL([url.absoluteString UTF8String]);
|
map->setDebug(mbgl::MapDebugOptions::NoDebug);
|
||||||
|
map->getStyle().loadURL([url.absoluteString UTF8String]);
|
||||||
|
|
||||||
[self startBenchmarkIteration];
|
[self startBenchmarkIteration];
|
||||||
}
|
}
|
||||||
|
|
||||||
// For setting the filename
|
// For setting the filename
|
||||||
NSDate* const currentDate = [NSDate date];
|
NSDate *const currentDate = [NSDate date];
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\description
|
\description
|
||||||
Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings > Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR`
|
Write the Benchmark log to the Documents Directory. For the device or simulator, fetch the
|
||||||
|
Documents Directory, and make a friendly name for the benchmarking output file. In Build Settings
|
||||||
|
> Other C Flags add the compiler flag. `-DLOG_TO_DOCUMENTS_DIR`
|
||||||
*/
|
*/
|
||||||
- (void)setAndRedirectLogFileToDocuments
|
- (void)setAndRedirectLogFileToDocuments {
|
||||||
{
|
NSString *const name = UIDevice.currentDevice.name;
|
||||||
NSString* const name = UIDevice.currentDevice.name;
|
#if TARGET_IPHONE_SIMULATOR
|
||||||
#if TARGET_IPHONE_SIMULATOR
|
NSString *const DeviceMode = @"Simulator";
|
||||||
NSString* const DeviceMode = @"Simulator";
|
#else
|
||||||
#else
|
NSString *const DeviceMode = @"Device";
|
||||||
NSString* const DeviceMode = @"Device";
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set log file name date
|
// Set log file name date
|
||||||
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
|
||||||
[formatter setDateFormat:@"yyyy-MM-dd-HHmm"];
|
[formatter setDateFormat:@"yyyy-MM-dd-HHmm"];
|
||||||
NSString *dateString = [formatter stringFromDate:currentDate];
|
NSString *dateString = [formatter stringFromDate:currentDate];
|
||||||
|
|
||||||
// Set the log file name
|
// Set the log file name
|
||||||
NSString* filename = [NSString stringWithFormat: @"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString];
|
NSString *filename =
|
||||||
|
[NSString stringWithFormat:@"MapLibre-bench-%@-%@-%@.log", name, DeviceMode, dateString];
|
||||||
|
|
||||||
// Set log file path
|
// Set log file path
|
||||||
NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
NSArray *allPaths =
|
||||||
NSString *documentsDirectory = [allPaths objectAtIndex:0];
|
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||||
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent: filename];
|
NSString *documentsDirectory = [allPaths objectAtIndex:0];
|
||||||
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
|
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:filename];
|
||||||
NSLog(@" open \"%@\"", pathForLog);
|
NSLog(@"Writing MapLibre Bench log. To open in Console, use the CLI command");
|
||||||
|
NSLog(@" open \"%@\"", pathForLog);
|
||||||
|
|
||||||
[self redirectLogToDocuments :pathForLog];
|
[self redirectLogToDocuments:pathForLog];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)redirectLogToDocuments: (NSString*) fileName
|
- (void)redirectLogToDocuments:(NSString *)fileName {
|
||||||
{
|
// write to file in append "a+" mode
|
||||||
// write to file in append "a+" mode
|
freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
|
||||||
freopen([fileName cStringUsingEncoding:NSASCIIStringEncoding], "a+", stderr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t idx = 0;
|
size_t idx = 0;
|
||||||
@@ -181,163 +184,153 @@ int frames = 0;
|
|||||||
double totalFrameEncodingTime = 0;
|
double totalFrameEncodingTime = 0;
|
||||||
double totalFrameRenderingTime = 0;
|
double totalFrameRenderingTime = 0;
|
||||||
std::chrono::steady_clock::time_point started;
|
std::chrono::steady_clock::time_point started;
|
||||||
std::vector<std::pair<std::string, std::pair<double, double>> > result;
|
std::vector<std::pair<std::string, std::pair<double, double>>> result;
|
||||||
|
|
||||||
static const int benchmarkDuration = 5; // seconds
|
static const int benchmarkDuration = 5; // seconds
|
||||||
|
|
||||||
namespace mbgl {
|
namespace mbgl {
|
||||||
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments;
|
extern std::size_t uploadCount, uploadBuildCount, uploadVertextAttrsDirty, uploadInvalidSegments;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)renderFrame
|
- (void)renderFrame {
|
||||||
{
|
mbgl::gfx::BackendScope guard{*(frontend->getBackend())};
|
||||||
mbgl::gfx::BackendScope guard{*(frontend->getBackend())};
|
|
||||||
|
|
||||||
frontend->renderFrame();
|
frontend->renderFrame();
|
||||||
|
|
||||||
auto image = frontend->readStillImage();
|
auto image = frontend->readStillImage();
|
||||||
|
|
||||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||||
CGContextRef bitmapContext = CGBitmapContextCreate(
|
CGContextRef bitmapContext =
|
||||||
image.data.get(),
|
CGBitmapContextCreate(image.data.get(), image.size.width, image.size.height, 8,
|
||||||
image.size.width,
|
4 * image.size.width, colorSpace, kCGImageAlphaPremultipliedLast);
|
||||||
image.size.height,
|
CFRelease(colorSpace);
|
||||||
8,
|
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
|
||||||
4 * image.size.width,
|
CGContextRelease(bitmapContext);
|
||||||
colorSpace,
|
|
||||||
kCGImageAlphaPremultipliedLast
|
|
||||||
);
|
|
||||||
CFRelease(colorSpace);
|
|
||||||
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
|
|
||||||
CGContextRelease(bitmapContext);
|
|
||||||
|
|
||||||
self.imageView.image = [UIImage imageWithCGImage:cgImage];
|
self.imageView.image = [UIImage imageWithCGImage:cgImage];
|
||||||
CGImageRelease(cgImage);
|
CGImageRelease(cgImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)startBenchmarkIteration
|
- (void)startBenchmarkIteration {
|
||||||
{
|
if (mbgl::bench::locations.size() > idx) {
|
||||||
if (mbgl::bench::locations.size() > idx) {
|
const auto &location = mbgl::bench::locations[idx];
|
||||||
const auto& location = mbgl::bench::locations[idx];
|
|
||||||
|
|
||||||
mbgl::CameraOptions cameraOptions;
|
mbgl::CameraOptions cameraOptions;
|
||||||
cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude);
|
cameraOptions.center = mbgl::LatLng(location.latitude, location.longitude);
|
||||||
cameraOptions.zoom = location.zoom;
|
cameraOptions.zoom = location.zoom;
|
||||||
cameraOptions.bearing = location.bearing;
|
cameraOptions.bearing = location.bearing;
|
||||||
mbgl::AnimationOptions animationOptions;
|
mbgl::AnimationOptions animationOptions;
|
||||||
animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
|
animationOptions.duration.emplace(std::chrono::duration_cast<mbgl::Duration>(
|
||||||
map->easeTo(cameraOptions, animationOptions);
|
std::chrono::duration<NSTimeInterval>(benchmarkDuration)));
|
||||||
|
map->easeTo(cameraOptions, animationOptions);
|
||||||
|
|
||||||
state = State::WaitingForAssets;
|
state = State::WaitingForAssets;
|
||||||
NSLog(@"Benchmarking \"%s\"", location.name.c_str());
|
NSLog(@"Benchmarking \"%s\"", location.name.c_str());
|
||||||
NSLog(@"- Loading assets...");
|
NSLog(@"- Loading assets...");
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self renderFrame];
|
[self renderFrame];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Do nothing. The benchmark is completed.
|
// Do nothing. The benchmark is completed.
|
||||||
NSLog(@"Benchmark completed.");
|
NSLog(@"Benchmark completed.");
|
||||||
|
|
||||||
NSLog(@"Result:");
|
NSLog(@"Result:");
|
||||||
size_t colWidth = 0;
|
size_t colWidth = 0;
|
||||||
for (const auto& row : result) {
|
for (const auto &row : result) {
|
||||||
colWidth = std::max(row.first.size(), colWidth);
|
colWidth = std::max(row.first.size(), colWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
double totalFrameEncodingTime = 0;
|
double totalFrameEncodingTime = 0;
|
||||||
double totalFrameRenderingTime = 0;
|
double totalFrameRenderingTime = 0;
|
||||||
for (const auto& row : result) {
|
for (const auto &row : result) {
|
||||||
NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(), 1e3 * row.second.first, 1e3 * row.second.second);
|
NSLog(@"| %-*s | %4.2f ms | %4.2f ms |", int(colWidth), row.first.c_str(),
|
||||||
totalFrameEncodingTime += row.second.first;
|
1e3 * row.second.first, 1e3 * row.second.second);
|
||||||
totalFrameRenderingTime += row.second.second;
|
totalFrameEncodingTime += row.second.first;
|
||||||
}
|
totalFrameRenderingTime += row.second.second;
|
||||||
|
}
|
||||||
|
|
||||||
NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size());
|
NSLog(@"Average frame encoding time: %4.2f ms", totalFrameEncodingTime * 1e3 / result.size());
|
||||||
NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size());
|
NSLog(@"Average frame rendering time: %4.2f ms", totalFrameRenderingTime * 1e3 / result.size());
|
||||||
|
|
||||||
// NSLog(@"Total uploads: %zu", mbgl::uploadCount);
|
// NSLog(@"Total uploads: %zu", mbgl::uploadCount);
|
||||||
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
|
// NSLog(@"Total uploads with dirty vattr: %zu", mbgl::uploadVertextAttrsDirty);
|
||||||
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
|
// NSLog(@"Total uploads with invalid segs: %zu", mbgl::uploadInvalidSegments);
|
||||||
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
|
// NSLog(@"Total uploads with build: %zu", mbgl::uploadBuildCount);
|
||||||
|
|
||||||
#if !defined(NDEBUG)
|
#if !defined(NDEBUG)
|
||||||
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
|
// Clean up and show rendering stats, as in `destroyCoreObjects` from tests.
|
||||||
// TODO: This doesn't clean up everything, what are we missing?
|
// TODO: This doesn't clean up everything, what are we missing?
|
||||||
map.reset();
|
map.reset();
|
||||||
observer.reset();
|
observer.reset();
|
||||||
frontend.reset();
|
frontend.reset();
|
||||||
#endif // !defined(NDEBUG)
|
#endif // !defined(NDEBUG)
|
||||||
|
|
||||||
// this does not shut the application down correctly,
|
// this does not shut the application down correctly,
|
||||||
// and results in an assertion failure in thread-local code
|
// and results in an assertion failure in thread-local code
|
||||||
//exit(0);
|
// exit(0);
|
||||||
|
|
||||||
// Use the UIApplication lifecycle instead.
|
// Use the UIApplication lifecycle instead.
|
||||||
// Terminating an app programmatically is strongly discouraged by Apple.
|
// Terminating an app programmatically is strongly discouraged by Apple.
|
||||||
// Combined with the plist setting "Application does not run in background" suspend allows
|
// Combined with the plist setting "Application does not run in background" suspend allows
|
||||||
// the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait.
|
// the XCUITest to wake up, so it doesn't need to guess how long we'll take and wait.
|
||||||
UIApplication *app = [UIApplication sharedApplication];
|
UIApplication *app = [UIApplication sharedApplication];
|
||||||
if ([app respondsToSelector:@selector(suspend)])
|
if ([app respondsToSelector:@selector(suspend)]) {
|
||||||
{
|
[app performSelector:@selector(suspend)];
|
||||||
[app performSelector:@selector(suspend)];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered
|
- (void)mapDidFinishRenderingFrameFullyRendered:(__unused BOOL)fullyRendered
|
||||||
frameEncodingTime:(double)frameEncodingTime
|
frameEncodingTime:(double)frameEncodingTime
|
||||||
frameRenderingTime:(double)frameRenderingTime
|
frameRenderingTime:(double)frameRenderingTime {
|
||||||
{
|
if (state == State::Benchmarking) {
|
||||||
if (state == State::Benchmarking)
|
frames++;
|
||||||
{
|
totalFrameEncodingTime += frameEncodingTime;
|
||||||
frames++;
|
totalFrameRenderingTime += frameRenderingTime;
|
||||||
totalFrameEncodingTime += frameEncodingTime;
|
|
||||||
totalFrameRenderingTime += frameRenderingTime;
|
|
||||||
|
|
||||||
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - started).count();
|
const auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
if (duration >= benchmarkDuration * 1e6)
|
std::chrono::steady_clock::now() - started)
|
||||||
{
|
.count();
|
||||||
state = State::None;
|
if (duration >= benchmarkDuration * 1e6) {
|
||||||
|
state = State::None;
|
||||||
|
|
||||||
// Report FPS
|
// Report FPS
|
||||||
const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
|
const auto frameEncodingTime = static_cast<double>(totalFrameEncodingTime) / frames;
|
||||||
const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
|
const auto frameRenderingTime = static_cast<double>(totalFrameRenderingTime) / frames;
|
||||||
result.emplace_back(mbgl::bench::locations[idx].name, std::make_pair(frameEncodingTime, frameRenderingTime));
|
result.emplace_back(mbgl::bench::locations[idx].name,
|
||||||
NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)", frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames);
|
std::make_pair(frameEncodingTime, frameRenderingTime));
|
||||||
|
NSLog(@"- Frame encoding time: %.1f ms, Frame rendering time: %.1f ms (%d frames)",
|
||||||
|
frameEncodingTime * 1e3, frameRenderingTime * 1e3, frames);
|
||||||
|
|
||||||
// Start benchmarking the next location.
|
// Start benchmarking the next location.
|
||||||
idx++;
|
idx++;
|
||||||
[self startBenchmarkIteration];
|
[self startBenchmarkIteration];
|
||||||
} else {
|
} else {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
[self renderFrame];
|
[self renderFrame];
|
||||||
});
|
});
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else if (state == State::WaitingForAssets)
|
return;
|
||||||
{
|
} else if (state == State::WaitingForAssets) {
|
||||||
if (map->isFullyLoaded())
|
if (map->isFullyLoaded()) {
|
||||||
{
|
// Start the benchmarking timer.
|
||||||
// Start the benchmarking timer.
|
frames = 0;
|
||||||
frames = 0;
|
totalFrameEncodingTime = 0;
|
||||||
totalFrameEncodingTime = 0;
|
totalFrameRenderingTime = 0;
|
||||||
totalFrameRenderingTime = 0;
|
state = State::Benchmarking;
|
||||||
state = State::Benchmarking;
|
started = std::chrono::steady_clock::now();
|
||||||
started = std::chrono::steady_clock::now();
|
NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
|
||||||
NSLog(@"- Benchmarking for %d seconds...", benchmarkDuration);
|
|
||||||
}
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
[self renderFrame];
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
|
[self renderFrame];
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)supportedInterfaceOrientations
|
- (NSUInteger)supportedInterfaceOrientations {
|
||||||
{
|
return UIInterfaceOrientationMaskLandscape;
|
||||||
return UIInterfaceOrientationMaskLandscape;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user