diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2016-02-11 12:21:49 -0800 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2016-02-12 14:53:52 -0800 |
commit | 13e2acbc754893efb945fe02d20824698415dcdb (patch) | |
tree | e8e4c966f03798fc6896a0fd3163e83921f84a38 /platform/darwin/src | |
parent | ab677a7905b0f81850d6aa3dcdd2caebc0dbc851 (diff) | |
download | qtlocation-mapboxgl-13e2acbc754893efb945fe02d20824698415dcdb.tar.gz |
[ios, osx] Consolidate remaining files in platform/{ios,osx}
Diffstat (limited to 'platform/darwin/src')
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; +} + +} +} |