diff --git a/Makefile b/Makefile index 434645c81..14f78ed0f 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ ANDROID_ABI ?= arm-v7 ifeq ($(shell uname -s), Darwin) HOST ?= osx +HEADLESS ?= cgl JOBS ?= $(shell sysctl -n hw.ncpu) endif HOST ?= linux diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp index 628f24a15..ac5188484 100644 --- a/android/cpp/jni.cpp +++ b/android/cpp/jni.cpp @@ -927,9 +927,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { return JNI_ERR; } - // NOTE: if you get java.lang.UnsatisfiedLinkError you likely forgot to set the size of the - // array correctly (too large) - std::array methods = {{ // Can remove the extra brace in C++14 + const std::vector methods = { {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", reinterpret_cast(&nativeCreate)}, {"nativeDestroy", "(J)V", reinterpret_cast(&nativeDestroy)}, @@ -1014,7 +1012,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { {"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForProjectedMeters)}, {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast(&nativePixelForLatLng)}, {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast(&nativeLatLngForPixel)}, - }}; + }; if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { env->ExceptionDescribe(); diff --git a/bin/render.cpp b/bin/render.cpp index 78cb5b59f..cd7a54f48 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -83,7 +83,7 @@ int main(int argc, char *argv[]) { HeadlessView view; Map map(view, fileSource); - map.start(Map::Mode::Static); + map.start(Map::Mode::Still); // Set access token if present if (token.size()) { diff --git a/gyp/styles.gypi b/gyp/styles.gypi index e1cd344fe..8b3032b58 100644 --- a/gyp/styles.gypi +++ b/gyp/styles.gypi @@ -19,7 +19,7 @@ 'hard_dependency': 1, 'dependencies': [ 'touch_styles' ], # required for xcode http://openradar.appspot.com/7232149 'conditions': [ - ['OS == "mac"', { + ['platform_lib == "osx" or platform_lib == "ios"', { 'direct_dependent_settings': { 'mac_bundle_resources': [ '../styles/styles' ], } diff --git a/include/mbgl/ios/MGLMapboxEvents.h b/include/mbgl/ios/MGLMapboxEvents.h index b68b9b1b3..7a4c45fe1 100644 --- a/include/mbgl/ios/MGLMapboxEvents.h +++ b/include/mbgl/ios/MGLMapboxEvents.h @@ -25,7 +25,7 @@ extern NSString *const MGLEventGesturePanStart; extern NSString *const MGLEventGesturePinchStart; extern NSString *const MGLEventGestureRotateStart; -@interface MGLMapboxEvents : NSObject +@interface MGLMapboxEvents : NSObject // You must call these methods from the main thread. // diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 992c12b83..f9738debe 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -51,7 +51,7 @@ public: enum class Mode : uint8_t { None, // we're not doing any processing Continuous, // continually updating map - Static, // a once-off static image. + Still, // a once-off still image. }; explicit Map(View&, FileSource&); diff --git a/include/mbgl/util/math.hpp b/include/mbgl/util/math.hpp index e800fc784..647fa5e67 100644 --- a/include/mbgl/util/math.hpp +++ b/include/mbgl/util/math.hpp @@ -98,6 +98,12 @@ T clamp(T value, T min, T max) { return value < min ? min : (value > max ? max : value); } +template +T wrap(T value, T min, T max) { + T d = max - min; + return value == max ? value : std::fmod((std::fmod((value - min), d) + d), d) + min; +} + template T smoothstep(T edge0, T edge1, T x) { T t = clamp((x - edge0) / (edge1 - edge0), T(0), T(1)); diff --git a/ios/MapboxGL.podspec b/ios/MapboxGL.podspec index b67f7faee..29e406362 100644 --- a/ios/MapboxGL.podspec +++ b/ios/MapboxGL.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |m| m.name = 'MapboxGL' - m.version = '0.2.13' + m.version = '0.2.14' m.summary = 'Open source vector map solution for iOS with full styling capabilities.' m.description = 'Open source OpenGL-based vector map solution for iOS with full styling capabilities and Cocoa bindings.' diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index 5acf04ef7..5f5e99b0e 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -1701,6 +1701,8 @@ CLLocationCoordinate2D latLngToCoordinate(mbgl::LatLng latLng) } self.userLocationAnnotationView = [[MGLUserLocationAnnotationView alloc] initInMapView:self]; + self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); self.locationManager = [CLLocationManager new]; diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m index 605c6a3fa..c4bd7cf0b 100644 --- a/platform/ios/MGLMapboxEvents.m +++ b/platform/ios/MGLMapboxEvents.m @@ -73,6 +73,8 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; @property (atomic) NSUInteger flushAt; @property (atomic) NSDateFormatter *rfc3339DateFormatter; @property (atomic) CGFloat scale; +@property (atomic) NSURLSession *session; +@property (atomic) NSData *geoTrustCert; // The isPaused state tracker is only ever accessed from the main thread. @@ -132,11 +134,31 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; _serialQueue = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.events.serial", _appBundleId, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL); // Configure Events Infrastructure + // =============================== + + _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; + + // Load Local Copy of Server's Public Key + NSString *cerPath = nil; + NSArray *bundles = [NSBundle allFrameworks]; + for (int lc = 0; lc < bundles.count; lc++) { + NSBundle *b = [bundles objectAtIndex:lc]; + cerPath = [[NSBundle mainBundle] pathForResource:@"api_mapbox_com-geotrust" ofType:@"der"]; + if (cerPath != nil) { + break; + } + } + if (cerPath != nil) { + _geoTrustCert = [NSData dataWithContentsOfFile:cerPath]; + } + + // Events Control _eventQueue = [[NSMutableArray alloc] init]; _flushAt = 20; _flushAfter = 60; _token = nil; _instanceID = [[NSUUID UUID] UUIDString]; + // Dynamic detection of ASIdentifierManager from Mixpanel // https://github.com/mixpanel/mixpanel-iphone/blob/master/LICENSE _advertiserId = @""; @@ -177,6 +199,9 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; [NSTimeZone resetSystemTimeZone]; [_rfc3339DateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; + // Enable Battery Monitoring + [UIDevice currentDevice].batteryMonitoringEnabled = YES; + _isPaused = NO; } return self; @@ -300,7 +325,7 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; [evt setValue:weakSelf.model forKey:@"model"]; [evt setValue:weakSelf.iOSVersion forKey:@"operatingSystem"]; [evt setValue:[weakSelf getDeviceOrientation] forKey:@"orientation"]; - [evt setValue:@(100 * [UIDevice currentDevice].batteryLevel) forKey:@"batteryLevel"]; + [evt setValue:@((int)(100 * [UIDevice currentDevice].batteryLevel)) forKey:@"batteryLevel"]; [evt setValue:@(weakSelf.scale) forKey:@"resolution"]; [evt setValue:weakSelf.carrier forKey:@"carrier"]; @@ -379,10 +404,13 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; __weak MGLMapboxEvents *weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ + MGLMapboxEvents *strongSelf = weakSelf; + if (!strongSelf) return; + // Setup URL Request - NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, weakSelf.token]; + NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, strongSelf.token]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; - [request setValue:[weakSelf getUserAgent] forHTTPHeaderField:@"User-Agent"]; + [request setValue:[strongSelf getUserAgent] forHTTPHeaderField:@"User-Agent"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request setHTTPMethod:@"POST"]; @@ -392,9 +420,8 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; [request setHTTPBody:jsonData]; // Send non blocking HTTP Request to server - [NSURLConnection sendAsynchronousRequest:request - queue:nil - completionHandler:nil]; + NSURLSessionDataTask *task = [_session dataTaskWithRequest:request completionHandler: nil]; + [task resume]; } }); } @@ -635,4 +662,49 @@ NSString *const MGLEventGestureRotateStart = @"Rotation"; return result; } +#pragma mark NSURLSessionDelegate +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + + if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + + SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; + NSString *domain = [[challenge protectionSpace] host]; + SecTrustResultType trustResult; + + // Validate the certificate chain with the device's trust store anyway + // This *might* give use revocation checking + SecTrustEvaluate(serverTrust, &trustResult); + if (trustResult == kSecTrustResultUnspecified) + { + // Look for a pinned certificate in the server's certificate chain + long numKeys = SecTrustGetCertificateCount(serverTrust); + + BOOL found = false; + for (int lc = 0; lc < numKeys; lc++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc); + NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate)); + + // Compare Remote Key With Local Version + if ([remoteCertificateData isEqualToData:_geoTrustCert]) { + // Found the certificate; continue connecting + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + found = true; + break; + } + } + + if (!found) { + // The certificate wasn't found in the certificate chain; cancel the connection + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } + } + else + { + // Certificate chain validation failed; cancel the connection + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } + } + +} + @end diff --git a/platform/ios/resources/api_mapbox_com-geotrust.der b/platform/ios/resources/api_mapbox_com-geotrust.der new file mode 100644 index 000000000..116144bc0 Binary files /dev/null and b/platform/ios/resources/api_mapbox_com-geotrust.der differ diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 5e7473820..e931d09e0 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -247,15 +247,15 @@ void Map::resume() { void Map::renderStill(StillImageCallback fn) { assert(Environment::currentlyOn(ThreadType::Main)); - if (mode != Mode::Static) { - throw util::Exception("Map is not in static render mode"); + if (mode != Mode::Still) { + throw util::Exception("Map is not in still image render mode"); } if (callback) { throw util::Exception("Map is currently rendering an image"); } - assert(mode == Mode::Static); + assert(mode == Mode::Still); callback = std::move(fn); @@ -287,7 +287,7 @@ void Map::run() { uv_run(env->loop, UV_RUN_DEFAULT); checkForPause(); } - } else if (mode == Mode::Static) { + } else if (mode == Mode::Still) { terminating = false; while (!terminating) { uv_run(env->loop, UV_RUN_DEFAULT); diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 10f69e2ed..cb73915e3 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -12,6 +12,21 @@ using namespace mbgl; +/** Converts the given angle (in radians) to be numerically close to the anchor angle, allowing it to be interpolated properly without sudden jumps. */ +static double _normalizeAngle(double angle, double anchorAngle) +{ + angle = util::wrap(angle, -M_PI, M_PI); + double diff = std::abs(angle - anchorAngle); + if (std::abs(angle - util::M2PI - anchorAngle) < diff) { + angle -= util::M2PI; + } + if (std::abs(angle + util::M2PI - anchorAngle) < diff) { + angle += util::M2PI; + } + + return angle; +} + Transform::Transform(View &view_) : view(view_) { @@ -330,12 +345,7 @@ void Transform::_setAngle(double new_angle, const Duration duration) { MapChangeRegionWillChangeAnimated : MapChangeRegionWillChange); - while (new_angle > M_PI) - new_angle -= util::M2PI; - while (new_angle <= -M_PI) - new_angle += util::M2PI; - - final.angle = new_angle; + final.angle = _normalizeAngle(new_angle, current.angle); if (duration == Duration::zero()) { current.angle = final.angle; diff --git a/test/api/repeated_render.cpp b/test/api/repeated_render.cpp index 16688ec36..25a5d5d28 100644 --- a/test/api/repeated_render.cpp +++ b/test/api/repeated_render.cpp @@ -24,7 +24,7 @@ TEST(API, RepeatedRender) { Map map(view, fileSource); - map.start(Map::Mode::Static); + map.start(Map::Mode::Still); { view.resize(128, 512, 1); diff --git a/test/api/set_style.cpp b/test/api/set_style.cpp index 973989d1c..11921a43c 100644 --- a/test/api/set_style.cpp +++ b/test/api/set_style.cpp @@ -18,7 +18,7 @@ TEST(API, SetStyle) { Map map(view, fileSource); - map.start(Map::Mode::Static); + map.start(Map::Mode::Still); map.setStyleJSON("invalid", "test/suite"); diff --git a/test/headless/headless.cpp b/test/headless/headless.cpp index 4ce9b668f..aac5dda83 100644 --- a/test/headless/headless.cpp +++ b/test/headless/headless.cpp @@ -16,10 +16,10 @@ #include #include -#include - #include +#include + void rewriteLocalScheme(rapidjson::Value &value, rapidjson::Document::AllocatorType &allocator) { ASSERT_TRUE(value.IsString()); auto string = std::string { value.GetString(),value.GetStringLength() }; @@ -145,7 +145,7 @@ TEST_P(HeadlessTest, render) { DefaultFileSource fileSource(nullptr); Map map(view, fileSource); - map.start(Map::Mode::Static); + map.start(Map::Mode::Still); map.setClasses(classes); map.setStyleJSON(style, "test/suite"); @@ -154,32 +154,15 @@ TEST_P(HeadlessTest, render) { map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom); map.setBearing(bearing); - struct Data { - std::string path; - std::unique_ptr image; - }; + std::promise promise; - uv_async_t *async = new uv_async_t; - async->data = new Data { "test/suite/tests/" + base + "/" + name + "/actual.png", nullptr }; - uv_async_init(uv_default_loop(), async, [](uv_async_t *as, int) { - auto data = std::unique_ptr(reinterpret_cast(as->data)); - as->data = nullptr; - uv_close(reinterpret_cast(as), [](uv_handle_t *handle) { - delete reinterpret_cast(handle); - }); - - assert(data); - const std::string png = util::compress_png(data->image->width, data->image->height, data->image->pixels.get()); - util::write_file(data->path, png); + map.renderStill([&](std::unique_ptr image) { + const std::string png = util::compress_png(image->width, image->height, image->pixels.get()); + util::write_file("test/suite/tests/" + base + "/" + name + "/actual.png", png); + promise.set_value(); }); - map.renderStill([async](std::unique_ptr image) { - reinterpret_cast(async->data)->image = std::move(image); - uv_async_send(async); - }); - - // This loop will terminate once the async was fired. - uv_run(uv_default_loop(), UV_RUN_DEFAULT); + promise.get_future().get(); map.stop(); }