summaryrefslogtreecommitdiff
path: root/platform/darwin/src
diff options
context:
space:
mode:
authorJohn Firebaugh <john.firebaugh@gmail.com>2016-02-11 12:21:49 -0800
committerJohn Firebaugh <john.firebaugh@gmail.com>2016-02-12 14:53:52 -0800
commit13e2acbc754893efb945fe02d20824698415dcdb (patch)
treee8e4c966f03798fc6896a0fd3163e83921f84a38 /platform/darwin/src
parentab677a7905b0f81850d6aa3dcdd2caebc0dbc851 (diff)
downloadqtlocation-mapboxgl-13e2acbc754893efb945fe02d20824698415dcdb.tar.gz
[ios, osx] Consolidate remaining files in platform/{ios,osx}
Diffstat (limited to 'platform/darwin/src')
-rw-r--r--platform/darwin/src/MGLGeometry.mm46
-rw-r--r--platform/darwin/src/MGLGeometry_Private.h64
-rw-r--r--platform/darwin/src/MGLMapCamera.mm125
-rw-r--r--platform/darwin/src/MGLMultiPoint.mm131
-rw-r--r--platform/darwin/src/MGLMultiPoint_Private.h49
-rw-r--r--platform/darwin/src/MGLPointAnnotation.m7
-rw-r--r--platform/darwin/src/MGLPolygon.mm28
-rw-r--r--platform/darwin/src/MGLPolyline.mm28
-rw-r--r--platform/darwin/src/MGLShape.m14
-rw-r--r--platform/darwin/src/MGLStyle.mm30
-rw-r--r--platform/darwin/src/MGLTypes.m3
-rw-r--r--platform/darwin/src/NSException+MGLAdditions.h3
-rw-r--r--platform/darwin/src/NSString+MGLAdditions.h16
-rw-r--r--platform/darwin/src/NSString+MGLAdditions.m12
-rw-r--r--platform/darwin/src/application_root.mm18
-rw-r--r--platform/darwin/src/http_request_nsurl.mm253
-rw-r--r--platform/darwin/src/image.mm121
-rw-r--r--platform/darwin/src/log_nslog.mm13
-rw-r--r--platform/darwin/src/nsthread.mm13
-rw-r--r--platform/darwin/src/reachability.m469
-rw-r--r--platform/darwin/src/settings_nsuserdefaults.mm60
-rw-r--r--platform/darwin/src/string_nsstring.mm31
22 files changed, 1534 insertions, 0 deletions
diff --git a/platform/darwin/src/MGLGeometry.mm b/platform/darwin/src/MGLGeometry.mm
new file mode 100644
index 0000000000..70f00afd2f
--- /dev/null
+++ b/platform/darwin/src/MGLGeometry.mm
@@ -0,0 +1,46 @@
+#import "MGLGeometry_Private.h"
+
+#import <mbgl/util/projection.hpp>
+
+/** Vertical field of view, measured in degrees, for determining the altitude
+ of the viewpoint.
+
+ TransformState::getProjMatrix() assumes a vertical field of view of
+ 2 arctan ⅓ rad ≈ 36.9°, but MapKit uses a vertical field of view of 30°.
+ flyTo() assumes a field of view of 2 arctan ½ rad. */
+const CLLocationDegrees MGLAngularFieldOfView = 30;
+
+const MGLCoordinateSpan MGLCoordinateSpanZero = {0, 0};
+
+CGRect MGLExtendRect(CGRect rect, CGPoint point) {
+ if (point.x < rect.origin.x) {
+ rect.size.width += rect.origin.x - point.x;
+ rect.origin.x = point.x;
+ }
+ if (point.x > rect.origin.x + rect.size.width) {
+ rect.size.width += point.x - (rect.origin.x + rect.size.width);
+ }
+ if (point.y < rect.origin.y) {
+ rect.size.height += rect.origin.y - point.y;
+ rect.origin.y = point.y;
+ }
+ if (point.y > rect.origin.y + rect.size.height) {
+ rect.size.height += point.y - (rect.origin.y + rect.size.height);
+ }
+ return rect;
+}
+
+CLLocationDistance MGLAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
+ CLLocationDistance metersPerPixel = mbgl::Projection::getMetersPerPixelAtLatitude(latitude, zoomLevel);
+ CLLocationDistance metersTall = metersPerPixel * size.height;
+ CLLocationDistance altitude = metersTall / 2 / std::tan(MGLRadiansFromDegrees(MGLAngularFieldOfView) / 2.);
+ return altitude * std::sin(M_PI_2 - MGLRadiansFromDegrees(pitch)) / std::sin(M_PI_2);
+}
+
+double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size) {
+ CLLocationDistance eyeAltitude = altitude / std::sin(M_PI_2 - MGLRadiansFromDegrees(pitch)) * std::sin(M_PI_2);
+ CLLocationDistance metersTall = eyeAltitude * 2 * std::tan(MGLRadiansFromDegrees(MGLAngularFieldOfView) / 2.);
+ CLLocationDistance metersPerPixel = metersTall / size.height;
+ CGFloat mapPixelWidthAtZoom = std::cos(MGLRadiansFromDegrees(latitude)) * mbgl::util::M2PI * mbgl::util::EARTH_RADIUS_M / metersPerPixel;
+ return ::log2(mapPixelWidthAtZoom / mbgl::util::tileSize);
+}
diff --git a/platform/darwin/src/MGLGeometry_Private.h b/platform/darwin/src/MGLGeometry_Private.h
new file mode 100644
index 0000000000..72123827df
--- /dev/null
+++ b/platform/darwin/src/MGLGeometry_Private.h
@@ -0,0 +1,64 @@
+#import "MGLGeometry.h"
+
+#import <TargetConditionals.h>
+#if TARGET_OS_IPHONE
+ #import <UIKit/UIKit.h>
+#endif
+
+#import <mbgl/map/map.hpp>
+#import <mbgl/util/geo.hpp>
+
+/// Returns the smallest rectangle that contains both the given rectangle and
+/// the given point.
+CGRect MGLExtendRect(CGRect rect, CGPoint point);
+
+NS_INLINE mbgl::LatLng MGLLatLngFromLocationCoordinate2D(CLLocationCoordinate2D coordinate) {
+ return mbgl::LatLng(coordinate.latitude, coordinate.longitude);
+}
+
+NS_INLINE CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng) {
+ return CLLocationCoordinate2DMake(latLng.latitude, latLng.longitude);
+}
+
+NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds) {
+ return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.southwest()),
+ MGLLocationCoordinate2DFromLatLng(latLngBounds.northeast()));
+}
+
+NS_INLINE mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coordinateBounds) {
+ return mbgl::LatLngBounds::hull(MGLLatLngFromLocationCoordinate2D(coordinateBounds.sw),
+ MGLLatLngFromLocationCoordinate2D(coordinateBounds.ne));
+}
+
+NS_INLINE BOOL MGLCoordinateInCoordinateBounds(CLLocationCoordinate2D coordinate, MGLCoordinateBounds coordinateBounds) {
+ mbgl::LatLngBounds bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds);
+ return bounds.contains(MGLLatLngFromLocationCoordinate2D(coordinate));
+}
+
+#if TARGET_OS_IPHONE
+NS_INLINE mbgl::EdgeInsets MGLEdgeInsetsFromNSEdgeInsets(UIEdgeInsets insets) {
+ return { insets.top, insets.left, insets.bottom, insets.right };
+}
+#else
+NS_INLINE mbgl::EdgeInsets MGLEdgeInsetsFromNSEdgeInsets(NSEdgeInsets insets) {
+ return { insets.top, insets.left, insets.bottom, insets.right };
+}
+#endif
+
+/** Converts a map zoom level to a camera altitude.
+
+ @param zoomLevel The zoom level to convert.
+ @param pitch The camera pitch, measured in degrees.
+ @param latitude The latitude of the point at the center of the viewport.
+ @param size The size of the viewport.
+ @return An altitude measured in meters. */
+CLLocationDistance MGLAltitudeForZoomLevel(double zoomLevel, CGFloat pitch, CLLocationDegrees latitude, CGSize size);
+
+/** Converts a camera altitude to a map zoom level.
+
+ @param altitude The altitude to convert, measured in meters.
+ @param pitch The camera pitch, measured in degrees.
+ @param latitude The latitude of the point at the center of the viewport.
+ @param size The size of the viewport.
+ @return A zero-based zoom level. */
+double MGLZoomLevelForAltitude(CLLocationDistance altitude, CGFloat pitch, CLLocationDegrees latitude, CGSize size);
diff --git a/platform/darwin/src/MGLMapCamera.mm b/platform/darwin/src/MGLMapCamera.mm
new file mode 100644
index 0000000000..93f3bd45e0
--- /dev/null
+++ b/platform/darwin/src/MGLMapCamera.mm
@@ -0,0 +1,125 @@
+#import "MGLMapCamera.h"
+
+#include <mbgl/util/projection.hpp>
+
+@implementation MGLMapCamera
+
++ (BOOL)supportsSecureCoding
+{
+ return YES;
+}
+
++ (instancetype)camera
+{
+ return [[self alloc] init];
+}
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
+ eyeAltitude:(CLLocationDistance)eyeAltitude
+{
+ mbgl::LatLng centerLatLng = mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude);
+ mbgl::LatLng eyeLatLng = mbgl::LatLng(eyeCoordinate.latitude, eyeCoordinate.longitude);
+
+ mbgl::ProjectedMeters centerMeters = mbgl::Projection::projectedMetersForLatLng(centerLatLng);
+ mbgl::ProjectedMeters eyeMeters = mbgl::Projection::projectedMetersForLatLng(eyeLatLng);
+ CLLocationDirection heading = std::atan((centerMeters.northing - eyeMeters.northing) /
+ (centerMeters.easting - eyeMeters.easting));
+
+ double groundDistance = std::hypot(centerMeters.northing - eyeMeters.northing,
+ centerMeters.easting - eyeMeters.easting);
+ CGFloat pitch = std::atan(eyeAltitude / groundDistance);
+
+ return [[self alloc] initWithCenterCoordinate:centerCoordinate
+ altitude:eyeAltitude
+ pitch:pitch
+ heading:heading];
+}
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromDistance:(CLLocationDistance)distance
+ pitch:(CGFloat)pitch
+ heading:(CLLocationDirection)heading
+{
+ return [[self alloc] initWithCenterCoordinate:centerCoordinate
+ altitude:distance
+ pitch:pitch
+ heading:heading];
+}
+
+- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ altitude:(CLLocationDistance)altitude
+ pitch:(CGFloat)pitch
+ heading:(CLLocationDirection)heading
+{
+ if (self = [super init])
+ {
+ _centerCoordinate = centerCoordinate;
+ _altitude = altitude;
+ _pitch = pitch;
+ _heading = heading;
+ }
+ return self;
+}
+
+- (nullable instancetype)initWithCoder:(NSCoder *)coder
+{
+ if (self = [super init])
+ {
+ _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
+ [coder decodeDoubleForKey:@"centerLongitude"]);
+ _altitude = [coder decodeDoubleForKey:@"altitude"];
+ _pitch = [coder decodeDoubleForKey:@"pitch"];
+ _heading = [coder decodeDoubleForKey:@"heading"];
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
+ [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
+ [coder encodeDouble:_altitude forKey:@"altitude"];
+ [coder encodeDouble:_pitch forKey:@"pitch"];
+ [coder encodeDouble:_heading forKey:@"heading"];
+}
+
+- (id)copyWithZone:(nullable NSZone *)zone
+{
+ return [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
+ altitude:_altitude
+ pitch:_pitch
+ heading:_heading];
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<%@ %p centerCoordinate:%f, %f altitude:%.0fm heading:%.0f° pitch:%.0f°>",
+ NSStringFromClass([self class]), (void *)self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch];
+}
+
+- (BOOL)isEqual:(id)other
+{
+ if ( ! [other isKindOfClass:[self class]])
+ {
+ return NO;
+ }
+ if (other == self)
+ {
+ return YES;
+ }
+
+ MGLMapCamera *otherCamera = other;
+ return (_centerCoordinate.latitude == otherCamera.centerCoordinate.latitude
+ && _centerCoordinate.longitude == otherCamera.centerCoordinate.longitude
+ && _altitude == otherCamera.altitude
+ && _pitch == otherCamera.pitch && _heading == otherCamera.heading);
+}
+
+- (NSUInteger)hash
+{
+ return (@(_centerCoordinate.latitude).hash + @(_centerCoordinate.longitude).hash
+ + @(_altitude).hash + @(_pitch).hash + @(_heading).hash);
+}
+
+@end
diff --git a/platform/darwin/src/MGLMultiPoint.mm b/platform/darwin/src/MGLMultiPoint.mm
new file mode 100644
index 0000000000..3cd0f0c117
--- /dev/null
+++ b/platform/darwin/src/MGLMultiPoint.mm
@@ -0,0 +1,131 @@
+#import "MGLMultiPoint_Private.h"
+#import "MGLGeometry_Private.h"
+
+#import <mbgl/util/geo.hpp>
+
+mbgl::Color MGLColorObjectFromCGColorRef(CGColorRef cgColor) {
+ if (!cgColor) {
+ return {{ 0, 0, 0, 0 }};
+ }
+ NSCAssert(CGColorGetNumberOfComponents(cgColor) >= 4, @"Color must have at least 4 components");
+ const CGFloat *components = CGColorGetComponents(cgColor);
+ return {{ (float)components[0], (float)components[1], (float)components[2], (float)components[3] }};
+}
+
+@implementation MGLMultiPoint
+{
+ CLLocationCoordinate2D *_coords;
+ size_t _count;
+ MGLCoordinateBounds _bounds;
+}
+
+- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords
+ count:(NSUInteger)count
+{
+ self = [super init];
+
+ if (self)
+ {
+ _count = count;
+ _coords = (CLLocationCoordinate2D *)malloc(_count * sizeof(CLLocationCoordinate2D));
+
+ mbgl::LatLngBounds bounds = mbgl::LatLngBounds::empty();
+
+ for (NSUInteger i = 0; i < _count; i++)
+ {
+ _coords[i] = coords[i];
+ bounds.extend(mbgl::LatLng(coords[i].latitude, coords[i].longitude));
+ }
+
+ _bounds = MGLCoordinateBoundsFromLatLngBounds(bounds);
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ free(_coords);
+}
+
+- (CLLocationCoordinate2D)coordinate
+{
+ if ([self isMemberOfClass:[MGLMultiPoint class]])
+ {
+ [[NSException exceptionWithName:@"MGLAbstractClassException"
+ reason:@"MGLMultiPoint is an abstract class"
+ userInfo:nil] raise];
+ }
+
+ assert(_count > 0);
+
+ return CLLocationCoordinate2DMake(_coords[0].latitude, _coords[0].longitude);
+}
+
+- (NSUInteger)pointCount
+{
+ if ([self isMemberOfClass:[MGLMultiPoint class]])
+ {
+ [[NSException exceptionWithName:@"MGLAbstractClassException"
+ reason:@"MGLMultiPoint is an abstract class"
+ userInfo:nil] raise];
+ }
+
+ return _count;
+}
+
+- (void)getCoordinates:(CLLocationCoordinate2D *)coords range:(NSRange)range
+{
+ if ([self isMemberOfClass:[MGLMultiPoint class]])
+ {
+ [[NSException exceptionWithName:@"MGLAbstractClassException"
+ reason:@"MGLMultiPoint is an abstract class"
+ userInfo:nil] raise];
+ }
+
+ assert(range.location + range.length <= _count);
+
+ NSUInteger index = 0;
+
+ for (NSUInteger i = range.location; i < range.location + range.length; i++)
+ {
+ coords[index] = _coords[i];
+ index++;
+ }
+}
+
+- (MGLCoordinateBounds)overlayBounds
+{
+ return _bounds;
+}
+
+- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds
+{
+ return MGLLatLngBoundsFromCoordinateBounds(_bounds).intersects(MGLLatLngBoundsFromCoordinateBounds(overlayBounds));
+}
+
+- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate {
+ NSUInteger count = self.pointCount;
+ if (count == 0) {
+ return;
+ }
+
+ CLLocationCoordinate2D *coordinates = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D));
+ NSAssert(coordinates, @"Unable to allocate annotation with %lu points", (unsigned long)count);
+ [self getCoordinates:coordinates range:NSMakeRange(0, count)];
+
+ mbgl::AnnotationSegment segment;
+ segment.reserve(count);
+ for (NSUInteger i = 0; i < count; i++) {
+ segment.push_back(MGLLatLngFromLocationCoordinate2D(coordinates[i]));
+ }
+ free(coordinates);
+ shapes.emplace_back(mbgl::AnnotationSegments {{ segment }},
+ [self shapeAnnotationPropertiesObjectWithDelegate:delegate]);
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(__unused id <MGLMultiPointDelegate>)delegate {
+ return mbgl::ShapeAnnotation::Properties();
+}
+
+@end
diff --git a/platform/darwin/src/MGLMultiPoint_Private.h b/platform/darwin/src/MGLMultiPoint_Private.h
new file mode 100644
index 0000000000..c1f1fa1584
--- /dev/null
+++ b/platform/darwin/src/MGLMultiPoint_Private.h
@@ -0,0 +1,49 @@
+#import "MGLMultiPoint.h"
+
+#import "MGLGeometry.h"
+#import "MGLTypes.h"
+
+#import <mbgl/annotation/shape_annotation.hpp>
+#import <vector>
+
+#import <CoreGraphics/CoreGraphics.h>
+#import <CoreLocation/CoreLocation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MGLPolygon;
+@class MGLPolyline;
+
+@protocol MGLMultiPointDelegate;
+
+@interface MGLMultiPoint (Private)
+
+- (instancetype)initWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
+- (BOOL)intersectsOverlayBounds:(MGLCoordinateBounds)overlayBounds;
+
+/** Adds a shape annotation to the given vector by asking the delegate for style values. */
+- (void)addShapeAnnotationObjectToCollection:(std::vector<mbgl::ShapeAnnotation> &)shapes withDelegate:(id <MGLMultiPointDelegate>)delegate;
+
+/** Constructs a shape annotation properties object by asking the delegate for style values. */
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate;
+
+@end
+
+/** An object that tells the MGLMultiPoint instance how to style itself. */
+@protocol MGLMultiPointDelegate <NSObject>
+
+/** Returns the fill alpha value for the given annotation. */
+- (double)alphaForShapeAnnotation:(MGLShape *)annotation;
+
+/** Returns the stroke color object for the given annotation. */
+- (mbgl::Color)strokeColorForShapeAnnotation:(MGLShape *)annotation;
+
+/** Returns the fill color object for the given annotation. */
+- (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation;
+
+/** Returns the stroke width object for the given annotation. */
+- (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLPointAnnotation.m b/platform/darwin/src/MGLPointAnnotation.m
new file mode 100644
index 0000000000..13fbba1083
--- /dev/null
+++ b/platform/darwin/src/MGLPointAnnotation.m
@@ -0,0 +1,7 @@
+#import "MGLPointAnnotation.h"
+
+@implementation MGLPointAnnotation
+
+@synthesize coordinate;
+
+@end
diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm
new file mode 100644
index 0000000000..5019385cb2
--- /dev/null
+++ b/platform/darwin/src/MGLPolygon.mm
@@ -0,0 +1,28 @@
+#import "MGLPolygon.h"
+
+#import "MGLMultiPoint_Private.h"
+
+@implementation MGLPolygon
+
+@dynamic overlayBounds;
+
++ (instancetype)polygonWithCoordinates:(CLLocationCoordinate2D *)coords
+ count:(NSUInteger)count
+{
+ return [[self alloc] initWithCoordinates:coords count:count];
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate {
+ mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate];
+
+ mbgl::FillAnnotationProperties fillProperties;
+ fillProperties.opacity = [delegate alphaForShapeAnnotation:self];
+ fillProperties.outlineColor = [delegate strokeColorForShapeAnnotation:self];
+ fillProperties.color = [delegate fillColorForPolygonAnnotation:self];
+
+ shapeProperties.set<mbgl::FillAnnotationProperties>(fillProperties);
+
+ return shapeProperties;
+}
+
+@end
diff --git a/platform/darwin/src/MGLPolyline.mm b/platform/darwin/src/MGLPolyline.mm
new file mode 100644
index 0000000000..f560a571bc
--- /dev/null
+++ b/platform/darwin/src/MGLPolyline.mm
@@ -0,0 +1,28 @@
+#import "MGLPolyline.h"
+
+#import "MGLMultiPoint_Private.h"
+
+@implementation MGLPolyline
+
+@dynamic overlayBounds;
+
++ (instancetype)polylineWithCoordinates:(CLLocationCoordinate2D *)coords
+ count:(NSUInteger)count
+{
+ return [[self alloc] initWithCoordinates:coords count:count];
+}
+
+- (mbgl::ShapeAnnotation::Properties)shapeAnnotationPropertiesObjectWithDelegate:(id <MGLMultiPointDelegate>)delegate {
+ mbgl::ShapeAnnotation::Properties shapeProperties = [super shapeAnnotationPropertiesObjectWithDelegate:delegate];
+
+ mbgl::LineAnnotationProperties lineProperties;
+ lineProperties.opacity = [delegate alphaForShapeAnnotation:self];
+ lineProperties.color = [delegate strokeColorForShapeAnnotation:self];
+ lineProperties.width = [delegate lineWidthForPolylineAnnotation:self];
+
+ shapeProperties.set<mbgl::LineAnnotationProperties>(lineProperties);
+
+ return shapeProperties;
+}
+
+@end
diff --git a/platform/darwin/src/MGLShape.m b/platform/darwin/src/MGLShape.m
new file mode 100644
index 0000000000..e3d92c38c8
--- /dev/null
+++ b/platform/darwin/src/MGLShape.m
@@ -0,0 +1,14 @@
+#import "MGLShape.h"
+
+@implementation MGLShape
+
+- (CLLocationCoordinate2D)coordinate
+{
+ [[NSException exceptionWithName:@"MGLAbstractClassException"
+ reason:@"MGLShape is an abstract class"
+ userInfo:nil] raise];
+
+ return CLLocationCoordinate2DMake(MAXFLOAT, MAXFLOAT);
+}
+
+@end
diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm
new file mode 100644
index 0000000000..15a25db9e3
--- /dev/null
+++ b/platform/darwin/src/MGLStyle.mm
@@ -0,0 +1,30 @@
+#import "MGLStyle.h"
+
+#import <mbgl/util/default_styles.hpp>
+
+@implementation MGLStyle
+
+// name is lowercase
+#define MGL_DEFINE_STYLE(name) \
+ static NSURL *MGLStyleURL_##name; \
+ + (NSURL *)name##StyleURL { \
+ static dispatch_once_t onceToken; \
+ dispatch_once(&onceToken, ^{ \
+ MGLStyleURL_##name = [NSURL URLWithString:@(mbgl::util::default_styles::name.url)]; \
+ }); \
+ return MGLStyleURL_##name; \
+ }
+
+MGL_DEFINE_STYLE(streets)
+MGL_DEFINE_STYLE(emerald)
+MGL_DEFINE_STYLE(light)
+MGL_DEFINE_STYLE(dark)
+MGL_DEFINE_STYLE(satellite)
+MGL_DEFINE_STYLE(hybrid)
+
+// Make sure all the styles listed in mbgl::util::default_styles::orderedStyles
+// are defined above and also declared in MGLStyle.h.
+static_assert(6 == mbgl::util::default_styles::numOrderedStyles,
+ "mbgl::util::default_styles::orderedStyles and MGLStyle have different numbers of styles.");
+
+@end
diff --git a/platform/darwin/src/MGLTypes.m b/platform/darwin/src/MGLTypes.m
new file mode 100644
index 0000000000..01e9a1467c
--- /dev/null
+++ b/platform/darwin/src/MGLTypes.m
@@ -0,0 +1,3 @@
+#import "MGLTypes.h"
+
+NSString * const MGLErrorDomain = @"MGLErrorDomain";
diff --git a/platform/darwin/src/NSException+MGLAdditions.h b/platform/darwin/src/NSException+MGLAdditions.h
new file mode 100644
index 0000000000..f75b54c15c
--- /dev/null
+++ b/platform/darwin/src/NSException+MGLAdditions.h
@@ -0,0 +1,3 @@
+#import <Foundation/Foundation.h>
+
+#define MGLAssertIsMainThread() NSAssert([[NSThread currentThread] isMainThread], @"%s must be accessed on the main thread, not %@", __PRETTY_FUNCTION__, [NSThread currentThread])
diff --git a/platform/darwin/src/NSString+MGLAdditions.h b/platform/darwin/src/NSString+MGLAdditions.h
new file mode 100644
index 0000000000..6064f8b40f
--- /dev/null
+++ b/platform/darwin/src/NSString+MGLAdditions.h
@@ -0,0 +1,16 @@
+#import <Foundation/Foundation.h>
+
+#import "MGLTypes.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+void mgl_linkStringCategory();
+
+@interface NSString (MGLAdditions)
+
+/** Returns the receiver if non-empty or nil if empty. */
+- (nullable NSString *)mgl_stringOrNilIfEmpty;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/NSString+MGLAdditions.m b/platform/darwin/src/NSString+MGLAdditions.m
new file mode 100644
index 0000000000..b94a5f0198
--- /dev/null
+++ b/platform/darwin/src/NSString+MGLAdditions.m
@@ -0,0 +1,12 @@
+#import "NSString+MGLAdditions.h"
+
+void mgl_linkStringCategory() {}
+
+@implementation NSString (MGLAdditions)
+
+- (nullable NSString *)mgl_stringOrNilIfEmpty
+{
+ return self.length ? self : nil;
+}
+
+@end
diff --git a/platform/darwin/src/application_root.mm b/platform/darwin/src/application_root.mm
new file mode 100644
index 0000000000..d4702c7ec5
--- /dev/null
+++ b/platform/darwin/src/application_root.mm
@@ -0,0 +1,18 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the root folder of the application.
+const std::string &applicationRoot() {
+ static const std::string root = []() -> std::string {
+ NSString *path = [[[NSBundle mainBundle] resourceURL] path];
+ return {[path cStringUsingEncoding : NSUTF8StringEncoding],
+ [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ }();
+ return root;
+}
+}
+}
diff --git a/platform/darwin/src/http_request_nsurl.mm b/platform/darwin/src/http_request_nsurl.mm
new file mode 100644
index 0000000000..90b2ab9131
--- /dev/null
+++ b/platform/darwin/src/http_request_nsurl.mm
@@ -0,0 +1,253 @@
+#include <mbgl/storage/http_context_base.hpp>
+#include <mbgl/storage/http_request_base.hpp>
+#include <mbgl/storage/resource.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/async_task.hpp>
+#include <mbgl/util/run_loop.hpp>
+
+#import <Foundation/Foundation.h>
+
+#include <map>
+#include <cassert>
+
+namespace mbgl {
+
+class HTTPNSURLContext;
+
+class HTTPNSURLRequest : public HTTPRequestBase {
+public:
+ HTTPNSURLRequest(HTTPNSURLContext*, const Resource&, Callback);
+ ~HTTPNSURLRequest();
+
+ void cancel() final;
+
+private:
+ void handleResult(NSData *data, NSURLResponse *res, NSError *error);
+ void handleResponse();
+
+ HTTPNSURLContext *context = nullptr;
+ bool cancelled = false;
+ NSURLSessionDataTask *task = nullptr;
+ std::unique_ptr<Response> response;
+ util::AsyncTask async;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class HTTPNSURLContext : public HTTPContextBase {
+public:
+ HTTPNSURLContext();
+ ~HTTPNSURLContext();
+
+ HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final;
+
+ NSURLSession *session = nil;
+ NSString *userAgent = nil;
+ NSInteger accountType = 0;
+};
+
+HTTPNSURLContext::HTTPNSURLContext() {
+ @autoreleasepool {
+ NSURLSessionConfiguration* sessionConfig =
+ [NSURLSessionConfiguration defaultSessionConfiguration];
+ sessionConfig.timeoutIntervalForResource = 30;
+ sessionConfig.HTTPMaximumConnectionsPerHost = 8;
+ sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ sessionConfig.URLCache = nil;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+ [session retain];
+
+ // Write user agent string
+ userAgent = @"MapboxGL";
+
+ accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"];
+ }
+}
+
+HTTPNSURLContext::~HTTPNSURLContext() {
+ [session release];
+ session = nullptr;
+
+ [userAgent release];
+ userAgent = nullptr;
+}
+
+HTTPRequestBase* HTTPNSURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) {
+ return new HTTPNSURLRequest(this, resource, callback);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPNSURLRequest::HTTPNSURLRequest(HTTPNSURLContext* context_,
+ const Resource& resource_,
+ Callback callback_)
+ : HTTPRequestBase(resource_, callback_),
+ context(context_),
+ async([this] { handleResponse(); }) {
+ // Hold the main loop alive until the request returns. This
+ // is needed because completion handler runs in another
+ // thread and will notify this thread using AsyncTask.
+ util::RunLoop::Get()->ref();
+
+ @autoreleasepool {
+ NSURL* url = [NSURL URLWithString:@(resource.url.c_str())];
+ if (context->accountType == 0 &&
+ ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"])) {
+ NSString* absoluteString = [url.absoluteString
+ stringByAppendingFormat:(url.query ? @"&%@" : @"?%@"), @"events=true"];
+ url = [NSURL URLWithString:absoluteString];
+ }
+
+ NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
+ if (resource.priorEtag) {
+ [req addValue:@(resource.priorEtag->c_str())
+ forHTTPHeaderField:@"If-None-Match"];
+ } else if (resource.priorModified) {
+ [req addValue:@(util::rfc1123(*resource.priorModified).c_str())
+ forHTTPHeaderField:@"If-Modified-Since"];
+ }
+
+ [req addValue:context->userAgent forHTTPHeaderField:@"User-Agent"];
+
+ task = [context->session
+ dataTaskWithRequest:req
+ completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) {
+ handleResult(data, res, error);
+ }];
+ [task retain];
+ [task resume];
+ }
+}
+
+HTTPNSURLRequest::~HTTPNSURLRequest() {
+ util::RunLoop::Get()->unref();
+ assert(!task);
+}
+
+void HTTPNSURLRequest::handleResponse() {
+ if (task) {
+ [task release];
+ task = nullptr;
+ }
+
+ if (!cancelled) {
+ assert(response);
+ notify(*response);
+ }
+
+ delete this;
+}
+
+void HTTPNSURLRequest::cancel() {
+ cancelled = true;
+
+ if (task) {
+ [task cancel];
+ [task release];
+ task = nullptr;
+ } else {
+ delete this;
+ }
+}
+
+void HTTPNSURLRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error) {
+ response = std::make_unique<Response>();
+ using Error = Response::Error;
+
+ if (error) {
+ if ([error code] == NSURLErrorCancelled) {
+ response.reset();
+
+ } else {
+ 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];
+
+ NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
+ NSString *cache_control = [headers objectForKey:@"Cache-Control"];
+ if (cache_control) {
+ response->expires = parseCacheControl([cache_control UTF8String]);
+ }
+
+ NSString *expires = [headers objectForKey:@"Expires"];
+ if (expires) {
+ response->expires = util::parseTimePoint([expires UTF8String]);
+ }
+
+ NSString *last_modified = [headers objectForKey:@"Last-Modified"];
+ if (last_modified) {
+ response->modified = util::parseTimePoint([last_modified UTF8String]);
+ }
+
+ NSString *etag = [headers objectForKey:@"ETag"];
+ if (etag) {
+ response->etag = std::string([etag UTF8String]);
+ }
+
+ if (responseCode == 200) {
+ response->data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
+ } else if (responseCode == 204 || (responseCode == 404 && resource.kind == Resource::Kind::Tile)) {
+ 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 >= 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 {
+ // This should never happen.
+ response->error = std::make_unique<Error>(Error::Reason::Other,
+ "Response class is not NSHTTPURLResponse");
+ }
+
+ async.send();
+}
+
+std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() {
+ return std::make_unique<HTTPNSURLContext>();
+}
+
+uint32_t HTTPContextBase::maximumConcurrentRequests() {
+ return 20;
+}
+
+}
diff --git a/platform/darwin/src/image.mm b/platform/darwin/src/image.mm
new file mode 100644
index 0000000000..854b9157f8
--- /dev/null
+++ b/platform/darwin/src/image.mm
@@ -0,0 +1,121 @@
+#include <mbgl/util/image.hpp>
+
+#import <ImageIO/ImageIO.h>
+
+#if TARGET_OS_IPHONE
+#import <MobileCoreServices/MobileCoreServices.h>
+#else
+#import <CoreServices/CoreServices.h>
+#endif
+
+namespace mbgl {
+
+std::string encodePNG(const PremultipliedImage& src) {
+ CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, src.data.get(), src.size(), NULL);
+ if (!provider) {
+ return "";
+ }
+
+ CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
+ if (!color_space) {
+ CGDataProviderRelease(provider);
+ return "";
+ }
+
+ CGImageRef image = CGImageCreate(src.width, src.height, 8, 32, 4 * src.width, color_space,
+ kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, provider, NULL, false,
+ kCGRenderingIntentDefault);
+ if (!image) {
+ CGColorSpaceRelease(color_space);
+ CGDataProviderRelease(provider);
+ return "";
+ }
+
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ if (!data) {
+ CGImageRelease(image);
+ CGColorSpaceRelease(color_space);
+ CGDataProviderRelease(provider);
+ return "";
+ }
+
+ CGImageDestinationRef image_destination = CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL);
+ if (!image_destination) {
+ CFRelease(data);
+ CGImageRelease(image);
+ CGColorSpaceRelease(color_space);
+ CGDataProviderRelease(provider);
+ return "";
+ }
+
+ CGImageDestinationAddImage(image_destination, image, NULL);
+ CGImageDestinationFinalize(image_destination);
+
+ const std::string result {
+ reinterpret_cast<const char *>(CFDataGetBytePtr(data)),
+ static_cast<size_t>(CFDataGetLength(data))
+ };
+
+ CFRelease(image_destination);
+ CFRelease(data);
+ CGImageRelease(image);
+ CGColorSpaceRelease(color_space);
+ CGDataProviderRelease(provider);
+
+ return result;
+}
+
+PremultipliedImage decodeImage(const std::string &source_data) {
+ CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const unsigned char *>(source_data.data()), source_data.size(), kCFAllocatorNull);
+ if (!data) {
+ throw std::runtime_error("CFDataCreateWithBytesNoCopy failed");
+ }
+
+ CGImageSourceRef image_source = CGImageSourceCreateWithData(data, NULL);
+ if (!image_source) {
+ CFRelease(data);
+ throw std::runtime_error("CGImageSourceCreateWithData failed");
+ }
+
+ CGImageRef image = CGImageSourceCreateImageAtIndex(image_source, 0, NULL);
+ if (!image) {
+ CFRelease(image_source);
+ CFRelease(data);
+ throw std::runtime_error("CGImageSourceCreateImageAtIndex failed");
+ }
+
+ CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
+ if (!color_space) {
+ CGImageRelease(image);
+ CFRelease(image_source);
+ CFRelease(data);
+ throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
+ }
+
+ PremultipliedImage result { CGImageGetWidth(image), CGImageGetHeight(image) };
+
+ CGContextRef context = CGBitmapContextCreate(result.data.get(), result.width, result.height, 8, result.stride(),
+ color_space, kCGImageAlphaPremultipliedLast);
+ if (!context) {
+ CGColorSpaceRelease(color_space);
+ CGImageRelease(image);
+ CFRelease(image_source);
+ CFRelease(data);
+ throw std::runtime_error("CGBitmapContextCreate failed");
+ }
+
+ CGContextSetBlendMode(context, kCGBlendModeCopy);
+
+ CGRect rect = {{ 0, 0 }, { static_cast<CGFloat>(result.width), static_cast<CGFloat>(result.height) }};
+ CGContextDrawImage(context, rect, image);
+
+ CGContextRelease(context);
+ CGColorSpaceRelease(color_space);
+ CGImageRelease(image);
+ CFRelease(image_source);
+ CFRelease(data);
+
+ return result;
+}
+
+}
diff --git a/platform/darwin/src/log_nslog.mm b/platform/darwin/src/log_nslog.mm
new file mode 100644
index 0000000000..a2e31968ab
--- /dev/null
+++ b/platform/darwin/src/log_nslog.mm
@@ -0,0 +1,13 @@
+#include <mbgl/platform/log.hpp>
+
+#import <Foundation/Foundation.h>
+
+namespace mbgl {
+
+void Log::platformRecord(EventSeverity severity, const std::string &msg) {
+ NSString *message =
+ [[NSString alloc] initWithBytes:msg.data() length:msg.size() encoding:NSUTF8StringEncoding];
+ NSLog(@"[%s] %@", EventSeverityClass(severity).c_str(), message);
+}
+
+}
diff --git a/platform/darwin/src/nsthread.mm b/platform/darwin/src/nsthread.mm
new file mode 100644
index 0000000000..9ac1d2caa0
--- /dev/null
+++ b/platform/darwin/src/nsthread.mm
@@ -0,0 +1,13 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+void makeThreadLowPriority() {
+ [[NSThread currentThread] setThreadPriority:0.0];
+}
+
+}
+}
diff --git a/platform/darwin/src/reachability.m b/platform/darwin/src/reachability.m
new file mode 100644
index 0000000000..8abcf5ae6d
--- /dev/null
+++ b/platform/darwin/src/reachability.m
@@ -0,0 +1,469 @@
+/*
+ Copyright (c) 2011, Tony Million.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <mbgl/platform/darwin/reachability.h>
+
+#import <sys/socket.h>
+#import <netinet/in.h>
+#import <netinet6/in6.h>
+#import <arpa/inet.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+
+
+NSString *const kMGLReachabilityChangedNotification = @"kMGLReachabilityChangedNotification";
+
+
+@interface MGLReachability ()
+
+@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
+@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
+@property (nonatomic, strong) id reachabilityObject;
+
+-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
+-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
+
+@end
+
+
+static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags)
+{
+ return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
+#if TARGET_OS_IPHONE
+ (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
+#else
+ 'X',
+#endif
+ (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
+ (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
+ (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
+ (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
+ (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
+ (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
+ (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
+ (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
+}
+
+// Start listening for reachability notifications on the current run loop
+static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
+{
+#pragma unused (target)
+
+ MGLReachability *reachability = ((__bridge MGLReachability*)info);
+
+ // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
+ // but what the heck eh?
+ @autoreleasepool
+ {
+ [reachability reachabilityChanged:flags];
+ }
+}
+
+
+@implementation MGLReachability
+
+#pragma mark - Class Constructor Methods
+
++(instancetype)reachabilityWithHostName:(NSString*)hostname
+{
+ return [MGLReachability reachabilityWithHostname:hostname];
+}
+
++(instancetype)reachabilityWithHostname:(NSString*)hostname
+{
+ SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
+ if (ref)
+ {
+ id reachability = [[self alloc] initWithReachabilityRef:ref];
+
+ return reachability;
+ }
+
+ return nil;
+}
+
++(instancetype)reachabilityWithAddress:(void *)hostAddress
+{
+ SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
+ if (ref)
+ {
+ id reachability = [[self alloc] initWithReachabilityRef:ref];
+
+ return reachability;
+ }
+
+ return nil;
+}
+
++(instancetype)reachabilityForInternetConnection
+{
+ struct sockaddr_in zeroAddress;
+ bzero(&zeroAddress, sizeof(zeroAddress));
+ zeroAddress.sin_len = sizeof(zeroAddress);
+ zeroAddress.sin_family = AF_INET;
+
+ return [self reachabilityWithAddress:&zeroAddress];
+}
+
++(instancetype)reachabilityForLocalWiFi
+{
+ struct sockaddr_in localWifiAddress;
+ bzero(&localWifiAddress, sizeof(localWifiAddress));
+ localWifiAddress.sin_len = sizeof(localWifiAddress);
+ localWifiAddress.sin_family = AF_INET;
+ // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
+ localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
+
+ return [self reachabilityWithAddress:&localWifiAddress];
+}
+
+
+// Initialization methods
+
+-(instancetype)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
+{
+ self = [super init];
+ if (self != nil)
+ {
+ self.reachableOnWWAN = YES;
+ self.reachabilityRef = ref;
+
+ // We need to create a serial queue.
+ // We allocate this once for the lifetime of the notifier.
+
+ self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
+ }
+
+ return self;
+}
+
+-(void)dealloc
+{
+ [self stopNotifier];
+
+ if(self.reachabilityRef)
+ {
+ CFRelease(self.reachabilityRef);
+ self.reachabilityRef = nil;
+ }
+
+ self.reachableBlock = nil;
+ self.unreachableBlock = nil;
+ self.reachabilitySerialQueue = nil;
+}
+
+#pragma mark - Notifier Methods
+
+// Notifier
+// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
+// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
+// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
+
+-(BOOL)startNotifier
+{
+ // allow start notifier to be called multiple times
+ if(self.reachabilityObject && (self.reachabilityObject == self))
+ {
+ return YES;
+ }
+
+
+ SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
+ context.info = (__bridge void *)self;
+
+ if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
+ {
+ // Set it as our reachability queue, which will retain the queue
+ if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
+ {
+ // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
+ // woah
+ self.reachabilityObject = self;
+ return YES;
+ }
+ else
+ {
+#ifdef DEBUG
+ NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
+#endif
+
+ // UH OH - FAILURE - stop any callbacks!
+ SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
+ }
+ }
+ else
+ {
+#ifdef DEBUG
+ NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
+#endif
+ }
+
+ // if we get here we fail at the internet
+ self.reachabilityObject = nil;
+ return NO;
+}
+
+-(void)stopNotifier
+{
+ // First stop, any callbacks!
+ SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
+
+ // Unregister target from the GCD serial dispatch queue.
+ SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
+
+ self.reachabilityObject = nil;
+}
+
+#pragma mark - reachability tests
+
+// This is for the case where you flick the airplane mode;
+// you end up getting something like this:
+//Reachability: WR ct-----
+//Reachability: -- -------
+//Reachability: WR ct-----
+//Reachability: -- -------
+// We treat this as 4 UNREACHABLE triggers - really apple should do better than this
+
+#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
+
+-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
+{
+ BOOL connectionUP = YES;
+
+ if(!(flags & kSCNetworkReachabilityFlagsReachable))
+ connectionUP = NO;
+
+ if( (flags & testcase) == testcase )
+ connectionUP = NO;
+
+#if TARGET_OS_IPHONE
+ if(flags & kSCNetworkReachabilityFlagsIsWWAN)
+ {
+ // We're on 3G.
+ if(!self.reachableOnWWAN)
+ {
+ // We don't want to connect when on 3G.
+ connectionUP = NO;
+ }
+ }
+#endif
+
+ return connectionUP;
+}
+
+-(BOOL)isReachable
+{
+ SCNetworkReachabilityFlags flags;
+
+ if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ return NO;
+
+ return [self isReachableWithFlags:flags];
+}
+
+-(BOOL)isReachableViaWWAN
+{
+#if TARGET_OS_IPHONE
+
+ SCNetworkReachabilityFlags flags = 0;
+
+ if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ // Check we're REACHABLE
+ if(flags & kSCNetworkReachabilityFlagsReachable)
+ {
+ // Now, check we're on WWAN
+ if(flags & kSCNetworkReachabilityFlagsIsWWAN)
+ {
+ return YES;
+ }
+ }
+ }
+#endif
+
+ return NO;
+}
+
+-(BOOL)isReachableViaWiFi
+{
+ SCNetworkReachabilityFlags flags = 0;
+
+ if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ // Check we're reachable
+ if((flags & kSCNetworkReachabilityFlagsReachable))
+ {
+#if TARGET_OS_IPHONE
+ // Check we're NOT on WWAN
+ if((flags & kSCNetworkReachabilityFlagsIsWWAN))
+ {
+ return NO;
+ }
+#endif
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+
+// WWAN may be available, but not active until a connection has been established.
+// WiFi may require a connection for VPN on Demand.
+-(BOOL)isConnectionRequired
+{
+ return [self connectionRequired];
+}
+
+-(BOOL)connectionRequired
+{
+ SCNetworkReachabilityFlags flags;
+
+ if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
+ }
+
+ return NO;
+}
+
+// Dynamic, on demand connection?
+-(BOOL)isConnectionOnDemand
+{
+ SCNetworkReachabilityFlags flags;
+
+ if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
+ (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
+ }
+
+ return NO;
+}
+
+// Is user intervention required?
+-(BOOL)isInterventionRequired
+{
+ SCNetworkReachabilityFlags flags;
+
+ if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
+ (flags & kSCNetworkReachabilityFlagsInterventionRequired));
+ }
+
+ return NO;
+}
+
+
+#pragma mark - reachability status stuff
+
+-(NetworkStatus)currentReachabilityStatus
+{
+ if([self isReachable])
+ {
+ if([self isReachableViaWiFi])
+ return ReachableViaWiFi;
+
+#if TARGET_OS_IPHONE
+ return ReachableViaWWAN;
+#endif
+ }
+
+ return NotReachable;
+}
+
+-(SCNetworkReachabilityFlags)reachabilityFlags
+{
+ SCNetworkReachabilityFlags flags = 0;
+
+ if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
+ {
+ return flags;
+ }
+
+ return 0;
+}
+
+-(NSString*)currentReachabilityString
+{
+ NetworkStatus temp = [self currentReachabilityStatus];
+
+ if(temp == ReachableViaWWAN)
+ {
+ // Updated for the fact that we have CDMA phones now!
+ return NSLocalizedString(@"Cellular", @"");
+ }
+ if (temp == ReachableViaWiFi)
+ {
+ return NSLocalizedString(@"WiFi", @"");
+ }
+
+ return NSLocalizedString(@"No Connection", @"");
+}
+
+-(NSString*)currentReachabilityFlags
+{
+ return reachabilityFlags([self reachabilityFlags]);
+}
+
+#pragma mark - Callback function calls this method
+
+-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
+{
+ if([self isReachableWithFlags:flags])
+ {
+ if(self.reachableBlock)
+ {
+ self.reachableBlock(self);
+ }
+ }
+ else
+ {
+ if(self.unreachableBlock)
+ {
+ self.unreachableBlock(self);
+ }
+ }
+
+ // this makes sure the change notification happens on the MAIN THREAD
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[NSNotificationCenter defaultCenter] postNotificationName:kMGLReachabilityChangedNotification
+ object:self];
+ });
+}
+
+#pragma mark - Debug Description
+
+- (NSString *) description
+{
+ NSString *description = [NSString stringWithFormat:@"<%@: %#x (%@)>",
+ NSStringFromClass([self class]), (unsigned int) self, [self currentReachabilityFlags]];
+ return description;
+}
+
+@end
diff --git a/platform/darwin/src/settings_nsuserdefaults.mm b/platform/darwin/src/settings_nsuserdefaults.mm
new file mode 100644
index 0000000000..548ee9b220
--- /dev/null
+++ b/platform/darwin/src/settings_nsuserdefaults.mm
@@ -0,0 +1,60 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
+
+using namespace mbgl;
+
+Settings_NSUserDefaults::Settings_NSUserDefaults()
+{
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ @"longitude" : @(longitude),
+ @"latitude" : @(latitude),
+ @"zoom" : @(zoom),
+ @"bearing" : @(bearing),
+ @"pitch" : @(pitch),
+ @"userTrackingMode" : @(userTrackingMode),
+ @"showsUserLocation" : @(showsUserLocation),
+ @"debug" : @(debug),
+ }];
+ load();
+}
+
+void Settings_NSUserDefaults::load()
+{
+ NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
+
+ longitude = [settings[@"longitude"] doubleValue];
+ latitude = [settings[@"latitude"] doubleValue];
+ zoom = [settings[@"zoom"] doubleValue];
+ bearing = [settings[@"bearing"] doubleValue];
+ pitch = [settings[@"pitch"] doubleValue];
+ debug = [settings[@"debug"] boolValue];
+
+ unsigned uncheckedTrackingMode = [settings[@"userTrackingMode"] unsignedIntValue];
+ if (uncheckedTrackingMode > MGLUserTrackingModeNone &&
+ uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse)
+ {
+ userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode;
+ }
+ showsUserLocation = [settings[@"showsUserLocation"] boolValue];
+}
+
+void Settings_NSUserDefaults::save()
+{
+ [[NSUserDefaults standardUserDefaults] setValuesForKeysWithDictionary:@{
+ @"longitude" : @(longitude),
+ @"latitude" : @(latitude),
+ @"zoom" : @(zoom),
+ @"bearing" : @(bearing),
+ @"pitch" : @(pitch),
+ @"userTrackingMode" : @(userTrackingMode),
+ @"showsUserLocation" : @(showsUserLocation),
+ @"debug" : @(debug),
+ }];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+void Settings_NSUserDefaults::clear()
+{
+ [NSUserDefaults resetStandardUserDefaults];
+}
diff --git a/platform/darwin/src/string_nsstring.mm b/platform/darwin/src/string_nsstring.mm
new file mode 100644
index 0000000000..9bf199afc0
--- /dev/null
+++ b/platform/darwin/src/string_nsstring.mm
@@ -0,0 +1,31 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+std::string uppercase(const std::string &string) {
+ NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
+ length:string.size()
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ NSString *uppercase = [original uppercaseString];
+ const std::string result{[uppercase cStringUsingEncoding : NSUTF8StringEncoding],
+ [uppercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ return result;
+}
+
+std::string lowercase(const std::string &string) {
+ NSString *original = [[NSString alloc] initWithBytesNoCopy:const_cast<char *>(string.data())
+ length:string.size()
+ encoding:NSUTF8StringEncoding
+ freeWhenDone:NO];
+ NSString *lowercase = [original lowercaseString];
+ const std::string result{[lowercase cStringUsingEncoding : NSUTF8StringEncoding],
+ [lowercase lengthOfBytesUsingEncoding:NSUTF8StringEncoding]};
+ return result;
+}
+
+}
+}