summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Leege <bleege@gmail.com>2015-08-12 18:22:17 -0500
committerBrad Leege <bleege@gmail.com>2015-08-12 18:22:17 -0500
commit72aaf3934a8aa3569e455b04975d4f8ee2126dc4 (patch)
tree5b9deaff8db61bde467ef27679d765bdabb014fe
parent05a7e29d2fac31cb6e10901ee82f334a44b9ee3f (diff)
parentd4159d51419ae59824c1a20cb682d2a66150a7ea (diff)
downloadqtlocation-mapboxgl-72aaf3934a8aa3569e455b04975d4f8ee2126dc4.tar.gz
Merge branch 'master' into 1856-material-take-2
# Conflicts: # android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
-rw-r--r--LICENSE.md10
-rw-r--r--android/cpp/jni.cpp142
-rw-r--r--android/cpp/native_map_view.cpp81
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java93
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java51
-rw-r--r--android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java12
-rw-r--r--android/mapboxgl-app.gypi5
-rw-r--r--gyp/http-android.gypi70
-rw-r--r--include/mbgl/android/jni.hpp29
-rw-r--r--include/mbgl/map/map.hpp1
-rw-r--r--include/mbgl/platform/default/glfw_view.hpp10
-rw-r--r--linux/main.cpp57
-rw-r--r--macosx/main.mm53
-rw-r--r--macosx/mapboxgl-app.gypi1
-rw-r--r--mbgl.gyp1
-rw-r--r--platform/android/http_request_android.cpp399
-rw-r--r--platform/default/glfw_view.cpp37
-rw-r--r--platform/default/http_request_curl.cpp109
-rw-r--r--platform/default/thread.cpp18
-rw-r--r--platform/ios/MGLMapView.mm5
-rw-r--r--platform/ios/MGLUserLocationAnnotationView.m328
-rw-r--r--scripts/android/configure.sh4
-rw-r--r--scripts/android/defaults.mk2
-rw-r--r--src/mbgl/map/map.cpp4
-rw-r--r--src/mbgl/map/map_context.cpp6
25 files changed, 1136 insertions, 392 deletions
diff --git a/LICENSE.md b/LICENSE.md
index e5ed29c9ae..6de1da4956 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -53,3 +53,13 @@ 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.
+
+===========================================================================
+
+Mapbox GL uses portions of SVPulsingAnnotationView.
+
+Copyright (c) 2013, Sam Vermette <hello@samvermette.com>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp
index 6d7def861c..f1e9072cbd 100644
--- a/android/cpp/jni.cpp
+++ b/android/cpp/jni.cpp
@@ -28,6 +28,8 @@
namespace mbgl {
namespace android {
+JavaVM* theJVM;
+
std::string cachePath;
std::string dataPath;
std::string apkPath;
@@ -90,7 +92,15 @@ jmethodID pointFConstructorId = nullptr;
jfieldID pointFXId = nullptr;
jfieldID pointFYId = nullptr;
-bool throw_error(JNIEnv *env, const char *msg) {
+jclass httpContextClass = nullptr;
+jmethodID httpContextGetInstanceId = nullptr;
+jmethodID httpContextCreateRequestId = nullptr;
+
+jclass httpRequestClass = nullptr;
+jmethodID httpRequestStartId = nullptr;
+jmethodID httpRequestCancelId = nullptr;
+
+bool throw_jni_error(JNIEnv *env, const char *msg) {
if (env->ThrowNew(runtimeExceptionClass, msg) < 0) {
env->ExceptionDescribe();
return false;
@@ -99,6 +109,41 @@ bool throw_error(JNIEnv *env, const char *msg) {
return true;
}
+bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName) {
+ JavaVMAttachArgs args = {JNI_VERSION_1_2, threadName.c_str(), NULL};
+
+ jint ret;
+ *env = nullptr;
+ bool detach = false;
+ ret = vm->GetEnv(reinterpret_cast<void **>(env), JNI_VERSION_1_6);
+ if (ret != JNI_OK) {
+ if (ret != JNI_EDETACHED) {
+ mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
+ throw new std::runtime_error("GetEnv() failed");
+ } else {
+ ret = vm->AttachCurrentThread(env, &args);
+ if (ret != JNI_OK) {
+ mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
+ throw new std::runtime_error("AttachCurrentThread() failed");
+ }
+ detach = true;
+ }
+ }
+
+ return detach;
+}
+
+void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach) {
+ if (detach) {
+ jint ret;
+ if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
+ mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
+ throw new std::runtime_error("DetachCurrentThread() failed");
+ }
+ }
+ *env = nullptr;
+}
+
std::string std_string_from_jstring(JNIEnv *env, jstring jstr) {
std::string str;
@@ -342,7 +387,7 @@ void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapVi
{
nativeMapView->initializeDisplay();
} catch(const std::exception& e) {
- throw_error(env, "Unable to initialize GL display.");
+ throw_jni_error(env, "Unable to initialize GL display.");
}
}
@@ -361,7 +406,7 @@ void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapVi
try {
nativeMapView->initializeContext();
} catch(const std::exception& e) {
- throw_error(env, "Unable to initialize GL context.");
+ throw_jni_error(env, "Unable to initialize GL context.");
}
}
@@ -380,7 +425,7 @@ void JNICALL nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPt
try {
nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface));
} catch(const std::exception& e) {
- throw_error(env, "Unable to create GL surface.");
+ throw_jni_error(env, "Unable to create GL surface.");
}
}
@@ -1131,6 +1176,8 @@ extern "C" {
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad");
+ theJVM = vm;
+
JNIEnv *env = nullptr;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
@@ -1426,6 +1473,36 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_ERR;
}
+ httpContextClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext");
+ if (httpContextClass == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ httpContextGetInstanceId = env->GetStaticMethodID(httpContextClass, "getInstance", "()Lcom/mapbox/mapboxgl/http/HTTPContext;");
+ if (httpContextGetInstanceId == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ httpContextCreateRequestId = env->GetMethodID(httpContextClass, "createRequest", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/mapbox/mapboxgl/http/HTTPContext$HTTPRequest;");
+ if (httpContextCreateRequestId == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ httpRequestClass = env->FindClass("com/mapbox/mapboxgl/http/HTTPContext$HTTPRequest");
+ if (httpRequestClass == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ httpRequestStartId = env->GetMethodID(httpRequestClass, "start", "()V");
+ if (httpRequestStartId == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ httpRequestCancelId = env->GetMethodID(httpRequestClass, "cancel", "()V");
+ if (httpRequestCancelId == nullptr) {
+ env->ExceptionDescribe();
+ }
+
const std::vector<JNINativeMethod> methods = {
{"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;FIJ)J",
reinterpret_cast<void *>(&nativeCreate)},
@@ -1524,10 +1601,16 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
//{"nativeGetWorldBoundsMeters", "(J)V", reinterpret_cast<void *>(&nativeGetWorldBoundsMeters)},
//{"nativeGetWorldBoundsLatLng", "(J)V", reinterpret_cast<void *>(&nativeGetWorldBoundsLatLng)},
{"nativeGetMetersPerPixelAtLatitude", "(JDD)D", reinterpret_cast<void *>(&nativeGetMetersPerPixelAtLatitude)},
- {"nativeProjectedMetersForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;", reinterpret_cast<void *>(&nativeProjectedMetersForLatLng)},
- {"nativeLatLngForProjectedMeters", "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast<void *>(&nativeLatLngForProjectedMeters)},
- {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;", reinterpret_cast<void *>(&nativePixelForLatLng)},
- {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;", reinterpret_cast<void *>(&nativeLatLngForPixel)},
+ {"nativeProjectedMetersForLatLng",
+ "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Lcom/mapbox/mapboxgl/geometry/ProjectedMeters;",
+ reinterpret_cast<void *>(&nativeProjectedMetersForLatLng)},
+ {"nativeLatLngForProjectedMeters",
+ "(JLcom/mapbox/mapboxgl/geometry/ProjectedMeters;)Lcom/mapbox/mapboxgl/geometry/LatLng;",
+ reinterpret_cast<void *>(&nativeLatLngForProjectedMeters)},
+ {"nativePixelForLatLng", "(JLcom/mapbox/mapboxgl/geometry/LatLng;)Landroid/graphics/PointF;",
+ reinterpret_cast<void *>(&nativePixelForLatLng)},
+ {"nativeLatLngForPixel", "(JLandroid/graphics/PointF;)Lcom/mapbox/mapboxgl/geometry/LatLng;",
+ reinterpret_cast<void *>(&nativeLatLngForPixel)},
};
if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) {
@@ -1641,6 +1724,37 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_ERR;
}
+ httpContextClass = reinterpret_cast<jclass>(env->NewGlobalRef(httpContextClass));
+ if (httpContextClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(latLngClass);
+ env->DeleteGlobalRef(markerClass);
+ env->DeleteGlobalRef(latLngZoomClass);
+ env->DeleteGlobalRef(polylineClass);
+ env->DeleteGlobalRef(polygonClass);
+ env->DeleteGlobalRef(runtimeExceptionClass);
+ env->DeleteGlobalRef(nullPointerExceptionClass);
+ env->DeleteGlobalRef(arrayListClass);
+ env->DeleteGlobalRef(projectedMetersClass);
+ env->DeleteGlobalRef(pointFClass);
+ }
+
+ httpRequestClass = reinterpret_cast<jclass>(env->NewGlobalRef(httpRequestClass));
+ if (httpRequestClass == nullptr) {
+ env->ExceptionDescribe();
+ env->DeleteGlobalRef(latLngClass);
+ env->DeleteGlobalRef(markerClass);
+ env->DeleteGlobalRef(latLngZoomClass);
+ env->DeleteGlobalRef(polylineClass);
+ env->DeleteGlobalRef(polygonClass);
+ env->DeleteGlobalRef(runtimeExceptionClass);
+ env->DeleteGlobalRef(nullPointerExceptionClass);
+ env->DeleteGlobalRef(arrayListClass);
+ env->DeleteGlobalRef(projectedMetersClass);
+ env->DeleteGlobalRef(pointFClass);
+ env->DeleteGlobalRef(httpContextClass);
+ }
+
char release[PROP_VALUE_MAX] = "";
__system_property_get("ro.build.version.release", release);
androidRelease = std::string(release);
@@ -1651,6 +1765,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload");
+ theJVM = vm;
+
JNIEnv *env = nullptr;
jint ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (ret != JNI_OK) {
@@ -1725,5 +1841,15 @@ extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
pointFConstructorId = nullptr;
pointFXId = nullptr;
pointFYId = nullptr;
+
+ env->DeleteGlobalRef(httpContextClass);
+ httpContextGetInstanceId = nullptr;
+ httpContextCreateRequestId = nullptr;
+
+ env->DeleteGlobalRef(httpRequestClass);
+ httpRequestStartId = nullptr;
+ httpRequestCancelId = nullptr;
+
+ theJVM = nullptr;
}
}
diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp
index d0581212df..d7c40ba5a1 100644
--- a/android/cpp/native_map_view.cpp
+++ b/android/cpp/native_map_view.cpp
@@ -148,38 +148,15 @@ void NativeMapView::invalidate() {
assert(vm != nullptr);
assert(obj != nullptr);
- JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::invalidate()", NULL};
-
- jint ret;
JNIEnv *env = nullptr;
- bool detach = false;
- ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
- if (ret != JNI_OK) {
- if (ret != JNI_EDETACHED) {
- mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
- throw new std::runtime_error("GetEnv() failed");
- } else {
- ret = vm->AttachCurrentThread(&env, &args);
- if (ret != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("AttachCurrentThread() failed");
- }
- detach = true;
- }
- }
+ bool detach = attach_jni_thread(vm, &env, "NativeMapView::invalidate()");
env->CallVoidMethod(obj, onInvalidateId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
- if (detach) {
- if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("DetachCurrentThread() failed");
- }
- }
- env = nullptr;
+ detach_jni_thread(vm, &env, detach);
}
void NativeMapView::swap() {
@@ -662,38 +639,15 @@ void NativeMapView::notifyMapChange(mbgl::MapChange) {
assert(vm != nullptr);
assert(obj != nullptr);
- JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL};
-
- jint ret;
JNIEnv *env = nullptr;
- bool detach = false;
- ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
- if (ret != JNI_OK) {
- if (ret != JNI_EDETACHED) {
- mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
- throw new std::runtime_error("GetEnv() failed");
- } else {
- ret = vm->AttachCurrentThread(&env, &args);
- if (ret != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("AttachCurrentThread() failed");
- }
- detach = true;
- }
- }
+ bool detach = attach_jni_thread(vm, &env, "NativeMapView::notifyMapChange()");
env->CallVoidMethod(obj, onMapChangedId);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
- if (detach) {
- if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("DetachCurrentThread() failed");
- }
- }
- env = nullptr;
+ detach_jni_thread(vm, &env, detach);
}
void NativeMapView::enableFps(bool enable) {
@@ -727,38 +681,15 @@ void NativeMapView::updateFps() {
assert(vm != nullptr);
assert(obj != nullptr);
- JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL};
-
- jint ret;
JNIEnv *env = nullptr;
- bool detach = false;
- ret = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
- if (ret != JNI_OK) {
- if (ret != JNI_EDETACHED) {
- mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret);
- throw new std::runtime_error("GetEnv() failed");
- } else {
- ret = vm->AttachCurrentThread(&env, &args);
- if (ret != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("AttachCurrentThread() failed");
- }
- detach = true;
- }
- }
+ bool detach = attach_jni_thread(vm, &env, "NativeMapView::updateFps()");
env->CallVoidMethod(obj, onFpsChangedId, fps);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
}
- if (detach) {
- if ((ret = vm->DetachCurrentThread()) != JNI_OK) {
- mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret);
- throw new std::runtime_error("DetachCurrentThread() failed");
- }
- }
- env = nullptr;
+ detach_jni_thread(vm, &env, detach);
}
void NativeMapView::onInvalidate() {
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java
new file mode 100644
index 0000000000..509c79a4a5
--- /dev/null
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/http/HTTPContext.java
@@ -0,0 +1,93 @@
+package com.mapbox.mapboxgl.http;
+
+import com.mapbox.mapboxgl.constants.MapboxConstants;
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ProtocolException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+import javax.net.ssl.SSLException;
+
+class HTTPContext {
+
+ private static final int CONNECTION_ERROR = 0;
+ private static final int TEMPORARY_ERROR = 1;
+ private static final int PERMANENT_ERROR = 2;
+
+ private static HTTPContext mInstance = null;
+
+ private OkHttpClient mClient;
+
+ private HTTPContext() {
+ super();
+ mClient = new OkHttpClient();
+ }
+
+ public static HTTPContext getInstance() {
+ if (mInstance == null) {
+ mInstance = new HTTPContext();
+ }
+
+ return mInstance;
+ }
+
+ public HTTPRequest createRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
+ return new HTTPRequest(nativePtr, resourceUrl, userAgent, etag, modified);
+ }
+
+ public class HTTPRequest implements Callback {
+ private long mNativePtr = 0;
+
+ private Call mCall;
+ private Request mRequest;
+
+ private native void nativeOnFailure(long nativePtr, int type, String message);
+ private native void nativeOnResponse(long nativePtr, int code, String message, String etag, String modified, String cacheControl, String expires, byte[] body);
+
+ private HTTPRequest(long nativePtr, String resourceUrl, String userAgent, String etag, String modified) {
+ mNativePtr = nativePtr;
+ Request.Builder builder = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).addHeader("User-Agent", userAgent);
+ if (etag.length() > 0) {
+ builder = builder.addHeader("If-None-Match", etag);
+ } else if (modified.length() > 0) {
+ builder = builder.addHeader("If-Modified-Since", modified);
+ }
+ mRequest = builder.build();
+ }
+
+ public void start() {
+ mCall = HTTPContext.getInstance().mClient.newCall(mRequest);
+ mCall.enqueue(this);
+ }
+
+ public void cancel() {
+ mCall.cancel();
+ }
+
+ @Override
+ public void onFailure(Request request, IOException e) {
+ int type = PERMANENT_ERROR;
+ if ((e instanceof UnknownHostException) || (e instanceof SocketException) || (e instanceof ProtocolException) || (e instanceof SSLException)) {
+ type = CONNECTION_ERROR;
+ } else if ((e instanceof InterruptedIOException)) {
+ type = TEMPORARY_ERROR;
+ }
+ nativeOnFailure(mNativePtr, type, e.getMessage());
+ }
+
+ @Override
+ public void onResponse(Response response) throws IOException {
+ byte[] body = response.body().bytes();
+ response.body().close();
+
+ nativeOnResponse(mNativePtr, response.code(), response.message(), response.header("ETag"), response.header("Last-Modified"), response.header("Cache-Control"), response.header("Expires"), body);
+ }
+ }
+}
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java
deleted file mode 100644
index bd86241888..0000000000
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/util/JavaFileSource.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.mapbox.mapboxgl.util;
-
-import com.mapbox.mapboxgl.constants.MapboxConstants;
-import com.squareup.okhttp.Callback;
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Request;
-
-public class JavaFileSource {
-
- private static JavaFileSource instance = null;
-
- // Single reference to OkHttp for performance gains
- private OkHttpClient client;
-
- /**
- * Private Constructor to support Singleton pattern
- */
- private JavaFileSource() {
- super();
- client = new OkHttpClient();
- }
-
- /**
- * Get the singleton instance of JavaFileSource
- * @return Reference to the Singleton Instance of JavaFileSource
- */
- public static JavaFileSource getInstance() {
- if (instance == null) {
- instance = new JavaFileSource();
- }
- return instance;
- }
-
- /**
- * Make an HTTP Request
- * @param resourceUrl URL to resource
- * @param callback Callback class
- */
- public void request(final String resourceUrl, final Callback callback) {
- Request request = new Request.Builder().url(resourceUrl).tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE)).build();
- client.newCall(request).enqueue(callback);
- }
-
- /**
- * Attempt to cancel HTTP Request made
- * @param resourceUrl URL of request to cancel
- */
- public void cancel(final String resourceUrl) {
- client.cancel(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE));
- }
-}
diff --git a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
index 60c57593c1..ca0b2526f8 100644
--- a/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
+++ b/android/java/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxgl/views/MapView.java
@@ -191,6 +191,7 @@ public class MapView extends FrameLayout implements LocationListener {
//
// Common initialization code goes here
+ @TargetApi(16)
private void initialize(Context context, AttributeSet attrs) {
// Save the context
@@ -217,9 +218,12 @@ public class MapView extends FrameLayout implements LocationListener {
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryInfo(memoryInfo);
- long totalMemory = memoryInfo.totalMem;
- mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity,availableProcessors, totalMemory);
-
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ long totalMemory = memoryInfo.totalMem;
+ mNativeMapView = new NativeMapView(this, cachePath, dataPath, apkPath, mScreenDensity, availableProcessors, totalMemory);
+ } else {
+ throw new RuntimeException("Need to implement totalMemory on pre-Jelly Bean devices.");
+ }
// Load the attributes
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MapView, 0, 0);
try {
@@ -678,7 +682,7 @@ public class MapView extends FrameLayout implements LocationListener {
}
public void onSizeChanged(int width, int height, int oldw, int oldh) {
- mNativeMapView.resizeView((int)(width / mScreenDensity), (int)(height / mScreenDensity));
+ mNativeMapView.resizeView((int) (width / mScreenDensity), (int) (height / mScreenDensity));
}
// This class handles SurfaceHolder callbacks
diff --git a/android/mapboxgl-app.gypi b/android/mapboxgl-app.gypi
index e32775ff72..e3db1269d6 100644
--- a/android/mapboxgl-app.gypi
+++ b/android/mapboxgl-app.gypi
@@ -25,8 +25,6 @@
'<@(boost_cflags)',
],
'libraries': [
- '<@(openssl_static_libs)',
- '<@(libcurl_static_libs)',
'<@(libpng_static_libs)',
'<@(jpeg_static_libs)',
'<@(sqlite_static_libs)',
@@ -45,8 +43,6 @@
'<@(libpng_ldflags)',
'<@(jpeg_ldflags)',
'<@(sqlite_ldflags)',
- '<@(openssl_ldflags)',
- '<@(libcurl_ldflags)',
'<@(zlib_ldflags)',
'<@(libzip_ldflags)',
],
@@ -74,7 +70,6 @@
'copies': [
{
'files': [
- '../common/ca-bundle.crt',
'../styles/styles'
],
'destination': '<(pwd)/../android/java/MapboxGLAndroidSDK/src/main/assets'
diff --git a/gyp/http-android.gypi b/gyp/http-android.gypi
new file mode 100644
index 0000000000..c210db0fb6
--- /dev/null
+++ b/gyp/http-android.gypi
@@ -0,0 +1,70 @@
+{
+ 'targets': [
+ { 'target_name': 'http-android',
+ 'product_name': 'mbgl-http-android',
+ 'type': 'static_library',
+ 'standalone_static_library': 1,
+ 'hard_dependency': 1,
+
+ 'sources': [
+ '../platform/android/http_request_android.cpp',
+ ],
+
+ 'include_dirs': [
+ '../include',
+ '../src',
+ ],
+
+ 'variables': {
+ 'cflags_cc': [
+ '<@(libuv_cflags)',
+ '<@(boost_cflags)',
+ ],
+ 'ldflags': [
+ '<@(libuv_ldflags)',
+ ],
+ 'libraries': [
+ '<@(libuv_static_libs)',
+ ],
+ 'defines': [
+ '-DMBGL_HTTP_ANDROID'
+ ],
+ },
+
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(cflags_cc)' ],
+ },
+ }, {
+ 'cflags_cc': [ '<@(cflags_cc)' ],
+ }],
+ ],
+
+ 'direct_dependent_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'xcode_settings': {
+ 'OTHER_CFLAGS': [ '<@(defines)' ],
+ 'OTHER_CPLUSPLUSFLAGS': [ '<@(defines)' ],
+ }
+ }, {
+ 'cflags': [ '<@(defines)' ],
+ 'cflags_cc': [ '<@(defines)' ],
+ }]
+ ],
+ },
+
+ 'link_settings': {
+ 'conditions': [
+ ['OS == "mac"', {
+ 'libraries': [ '<@(libraries)' ],
+ 'xcode_settings': { 'OTHER_LDFLAGS': [ '<@(ldflags)' ] }
+ }, {
+ 'libraries': [ '<@(libraries)', '<@(ldflags)' ],
+ }]
+ ],
+ },
+ },
+ ],
+}
diff --git a/include/mbgl/android/jni.hpp b/include/mbgl/android/jni.hpp
index f27c8c8449..62cb399d3f 100644
--- a/include/mbgl/android/jni.hpp
+++ b/include/mbgl/android/jni.hpp
@@ -2,15 +2,27 @@
#define MBGL_ANDROID_JNI
#include <string>
+#include <vector>
// Forward definition of JNI types
typedef class _jclass* jclass;
+typedef class _jstring* jstring;
+typedef class _jobject* jobject;
+typedef class _jlongArray* jlongArray;
typedef struct _jmethodID* jmethodID;
typedef struct _jfieldID* jfieldID;
+struct _JavaVM;
+typedef _JavaVM JavaVM;
+
+struct _JNIEnv;
+typedef _JNIEnv JNIEnv;
+
namespace mbgl {
namespace android {
+extern JavaVM* theJVM;
+
extern std::string cachePath;
extern std::string dataPath;
extern std::string apkPath;
@@ -73,6 +85,23 @@ extern jmethodID pointFConstructorId;
extern jfieldID pointFXId;
extern jfieldID pointFYId;
+extern jclass httpContextClass;
+extern jmethodID httpContextGetInstanceId;
+extern jmethodID httpContextCreateRequestId;
+
+extern jclass httpRequestClass;
+extern jmethodID httpRequestStartId;
+extern jmethodID httpRequestCancelId;
+
+extern bool throw_jni_error(JNIEnv *env, const char *msg);
+extern bool attach_jni_thread(JavaVM* vm, JNIEnv** env, std::string threadName);
+extern void detach_jni_thread(JavaVM* vm, JNIEnv** env, bool detach);
+extern std::string std_string_from_jstring(JNIEnv *env, jstring jstr);
+extern jstring std_string_to_jstring(JNIEnv *env, std::string str);
+extern std::vector<std::string> std_vector_string_from_jobject(JNIEnv *env, jobject jlist);
+extern jobject std_vector_string_to_jobject(JNIEnv *env, std::vector<std::string> vector);
+extern jlongArray std_vector_uint_to_jobject(JNIEnv *env, std::vector<uint32_t> vector);
+
}
}
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index ae43dc8a8f..ebd79620d6 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -164,6 +164,7 @@ public:
void setDebug(bool value);
void toggleDebug();
bool getDebug() const;
+ void setNeedsRepaint();
void setCollisionDebug(bool value);
void toggleCollisionDebug();
bool getCollisionDebug() const;
diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp
index 1bc210b29e..ec99080732 100644
--- a/include/mbgl/platform/default/glfw_view.hpp
+++ b/include/mbgl/platform/default/glfw_view.hpp
@@ -12,7 +12,7 @@
class GLFWView : public mbgl::View {
public:
- GLFWView(bool fullscreen = false);
+ GLFWView(bool fullscreen = false, bool benchmark = false);
~GLFWView();
float getPixelRatio() const override;
@@ -42,7 +42,7 @@ public:
void setWindowTitle(const std::string&);
void run();
- void fps();
+ void report(float duration);
private:
mbgl::LatLng makeRandomPoint() const;
@@ -61,9 +61,15 @@ private:
private:
bool fullscreen = false;
+ const bool benchmark = false;
bool tracking = false;
bool rotating = false;
+ // Frame timer
+ int frames = 0;
+ float frameTime = 0;
+ double lastReported = 0;
+
int width = 1024;
int height = 768;
int fbWidth;
diff --git a/linux/main.cpp b/linux/main.cpp
index c21d7446e8..6bd2f7c430 100644
--- a/linux/main.cpp
+++ b/linux/main.cpp
@@ -32,17 +32,26 @@ void quit_handler(int) {
int main(int argc, char *argv[]) {
bool fullscreen = false;
+ bool benchmark = false;
std::string style;
+ double latitude = 0, longitude = 0;
+ double bearing = 0, zoom = 1;
+ bool skipConfig = false;
const struct option long_options[] = {
{"fullscreen", no_argument, 0, 'f'},
+ {"benchmark", no_argument, 0, 'b'},
{"style", required_argument, 0, 's'},
+ {"lon", required_argument, 0, 'x'},
+ {"lat", required_argument, 0, 'y'},
+ {"zoom", required_argument, 0, 'z'},
+ {"bearing", required_argument, 0, 'r'},
{0, 0, 0, 0}
};
while (true) {
int option_index = 0;
- int opt = getopt_long(argc, argv, "fs:", long_options, &option_index);
+ int opt = getopt_long(argc, argv, "fbs:", long_options, &option_index);
if (opt == -1) break;
switch (opt)
{
@@ -52,8 +61,28 @@ int main(int argc, char *argv[]) {
case 'f':
fullscreen = true;
break;
+ case 'b':
+ benchmark = true;
+ break;
case 's':
style = std::string("asset://") + std::string(optarg);
+ break;
+ case 'x':
+ longitude = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'y':
+ latitude = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'z':
+ zoom = atof(optarg);
+ skipConfig = true;
+ break;
+ case 'r':
+ bearing = atof(optarg);
+ skipConfig = true;
+ break;
default:
break;
}
@@ -67,7 +96,11 @@ int main(int argc, char *argv[]) {
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
- view = std::make_unique<GLFWView>(fullscreen);
+ if (benchmark) {
+ mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled.");
+ }
+
+ view = std::make_unique<GLFWView>(fullscreen, benchmark);
mbgl::SQLiteCache cache("/tmp/mbgl-cache.db");
mbgl::DefaultFileSource fileSource(&cache);
@@ -84,9 +117,16 @@ int main(int argc, char *argv[]) {
// Load settings
mbgl::Settings_JSON settings;
- map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom);
- map.setBearing(settings.bearing);
- map.setDebug(settings.debug);
+
+ if (skipConfig) {
+ map.setLatLngZoom(mbgl::LatLng(latitude, longitude), zoom);
+ map.setBearing(bearing);
+ mbgl::Log::Info(mbgl::Event::General, "Location: %f/%f (z%.2f, %.2f deg)", latitude, longitude, zoom, bearing);
+ } else {
+ map.setLatLngZoom(mbgl::LatLng(settings.latitude, settings.longitude), settings.zoom);
+ map.setBearing(settings.bearing);
+ map.setDebug(settings.debug);
+ }
view->setChangeStyleCallback([&map] () {
static uint8_t currentStyleIndex;
@@ -120,7 +160,12 @@ int main(int argc, char *argv[]) {
settings.zoom = map.getZoom();
settings.bearing = map.getBearing();
settings.debug = map.getDebug();
- settings.save();
+ if (!skipConfig) {
+ settings.save();
+ }
+ mbgl::Log::Info(mbgl::Event::General,
+ "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"",
+ settings.latitude, settings.longitude, settings.zoom, settings.bearing);
return 0;
}
diff --git a/macosx/main.mm b/macosx/main.mm
index 4a786f5772..24251e6d4f 100644
--- a/macosx/main.mm
+++ b/macosx/main.mm
@@ -12,6 +12,19 @@
#import <Foundation/Foundation.h>
+#pragma GCC diagnostic push
+#ifndef __clang__
+#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
+#pragma GCC diagnostic ignored "-Wshadow"
+#endif
+#include <boost/program_options.hpp>
+#pragma GCC diagnostic pop
+
+#include <iostream>
+
+namespace po = boost::program_options;
+
+
@interface URLHandler : NSObject
@property (nonatomic) mbgl::Map *map;
@@ -101,8 +114,33 @@ const std::string &defaultCacheDatabase() {
return path;
}
-int main() {
- GLFWView view;
+int main(int argc, char* argv[]) {
+ bool fullscreen = false;
+ bool benchmark = false;
+ std::string style;
+
+ po::options_description desc("Allowed options");
+ desc.add_options()
+ ("fullscreen,f", po::bool_switch(&fullscreen)->default_value(fullscreen), "Fullscreen mode")
+ ("style,s", po::value(&style)->value_name("json"), "Map stylesheet")
+ ("benchmark,b", po::bool_switch(&benchmark)->default_value(benchmark), "Benchmark mode")
+ ;
+
+ try {
+ auto parsed = po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
+ po::variables_map vm;
+ po::store(parsed, vm);
+ po::notify(vm);
+ } catch(std::exception& e) {
+ std::cout << "Error: " << e.what() << std::endl << desc;
+ exit(1);
+ }
+
+ if (benchmark) {
+ mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled.");
+ }
+
+ GLFWView view(fullscreen, benchmark);
mbgl::SQLiteCache cache(defaultCacheDatabase());
mbgl::DefaultFileSource fileSource(&cache);
@@ -146,10 +184,15 @@ int main() {
mbgl::Log::Info(mbgl::Event::Setup, std::string("Changed style to: ") + newStyle.first);
});
+
// Load style
- const auto& newStyle = mbgl::util::defaultStyles.front();
- map.setStyleURL(newStyle.first);
- view.setWindowTitle(newStyle.second);
+ if (style.empty()) {
+ const auto& newStyle = mbgl::util::defaultStyles.front();
+ style = newStyle.first;
+ view.setWindowTitle(newStyle.second);
+ }
+
+ map.setStyleURL(style);
view.run();
diff --git a/macosx/mapboxgl-app.gypi b/macosx/mapboxgl-app.gypi
index 589ae29bd5..e1d9476e1d 100644
--- a/macosx/mapboxgl-app.gypi
+++ b/macosx/mapboxgl-app.gypi
@@ -43,6 +43,7 @@
],
'libraries': [
'<@(glfw_static_libs)',
+ '<@(boost_libprogram_options_static_libs)'
],
},
diff --git a/mbgl.gyp b/mbgl.gyp
index c5ede28233..959a9729d6 100644
--- a/mbgl.gyp
+++ b/mbgl.gyp
@@ -18,6 +18,7 @@
['platform_lib == "android" and host == "android"', { 'includes': [ './gyp/platform-android.gypi' ] } ],
['http_lib == "curl"', { 'includes': [ './gyp/http-curl.gypi' ] } ],
['http_lib == "nsurl" and (host == "osx" or host == "ios")', { 'includes': [ './gyp/http-nsurl.gypi' ] } ],
+ ['http_lib == "android" and host == "android"', { 'includes': [ './gyp/http-android.gypi' ] } ],
['asset_lib == "fs"', { 'includes': [ './gyp/asset-fs.gypi' ] } ],
['asset_lib == "zip"', { 'includes': [ './gyp/asset-zip.gypi' ] } ],
['cache_lib == "sqlite"', { 'includes': [ './gyp/cache-sqlite.gypi' ] } ],
diff --git a/platform/android/http_request_android.cpp b/platform/android/http_request_android.cpp
new file mode 100644
index 0000000000..044f772628
--- /dev/null
+++ b/platform/android/http_request_android.cpp
@@ -0,0 +1,399 @@
+#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/chrono.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/android/jni.hpp>
+
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/parsedate.h>
+
+#include <jni.h>
+
+namespace mbgl {
+
+void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message);
+void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body);
+
+class HTTPAndroidRequest;
+
+class HTTPAndroidContext : public HTTPContextBase {
+public:
+ explicit HTTPAndroidContext(uv_loop_t *loop);
+ ~HTTPAndroidContext();
+
+ HTTPRequestBase* createRequest(const Resource&,
+ RequestBase::Callback,
+ uv_loop_t*,
+ std::shared_ptr<const Response>) final;
+
+ uv_loop_t *loop = nullptr;
+
+ JavaVM *vm = nullptr;
+ jobject obj = nullptr;
+};
+
+class HTTPAndroidRequest : public HTTPRequestBase {
+public:
+ HTTPAndroidRequest(HTTPAndroidContext*,
+ const Resource&,
+ Callback,
+ uv_loop_t*,
+ std::shared_ptr<const Response>);
+ ~HTTPAndroidRequest();
+
+ void cancel() final;
+ void retry() final;
+
+ void onFailure(int type, std::string message);
+ void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body);
+
+private:
+ void retry(uint64_t timeout) final;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void restart(uv_timer_t *timer, int);
+#else
+ static void restart(uv_timer_t *timer);
+#endif
+ void finish(ResponseStatus status);
+ void start();
+
+ HTTPAndroidContext *context = nullptr;
+
+ bool cancelled = false;
+
+ std::unique_ptr<Response> response;
+ const std::shared_ptr<const Response> existingResponse;
+
+ jobject obj = nullptr;
+
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+ int attempts = 0;
+
+ static const int maxAttempts = 4;
+
+ static const int connectionError = 0;
+ static const int temporaryError = 1;
+ static const int permanentError = 1;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_)
+ : HTTPContextBase(loop_),
+ loop(loop_),
+ vm(mbgl::android::theJVM) {
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()");
+
+ const std::vector<JNINativeMethod> methods = {
+ {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast<void *>(&nativeOnFailure)},
+ {"nativeOnResponse",
+ "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V",
+ reinterpret_cast<void *>(&nativeOnResponse)}
+ };
+
+ if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) {
+ env->ExceptionDescribe();
+ }
+
+ obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId);
+ if (env->ExceptionCheck() || (obj == nullptr)) {
+ env->ExceptionDescribe();
+ }
+
+ obj = env->NewGlobalRef(obj);
+ if (obj == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(vm, &env, detach);
+}
+
+HTTPAndroidContext::~HTTPAndroidContext() {
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()");
+
+ env->DeleteGlobalRef(obj);
+ obj = nullptr;
+
+ mbgl::android::detach_jni_thread(vm, &env, detach);
+
+ vm = nullptr;
+}
+
+HTTPRequestBase* HTTPAndroidContext::createRequest(const Resource& resource,
+ RequestBase::Callback callback,
+ uv_loop_t* loop_,
+ std::shared_ptr<const Response> response) {
+ return new HTTPAndroidRequest(this, resource, callback, loop_, response);
+}
+
+HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
+ : HTTPRequestBase(resource_, callback_),
+ context(context_),
+ existingResponse(response_) {
+
+ std::string etagStr;
+ std::string modifiedStr;
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ etagStr = existingResponse->etag;
+ } else if (existingResponse->modified) {
+ modifiedStr = util::rfc1123(existingResponse->modified);
+ }
+ }
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()");
+
+ jstring resourceUrl = mbgl::android::std_string_to_jstring(env, resource.url);
+ jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0");
+ jstring etag = mbgl::android::std_string_to_jstring(env, etagStr);
+ jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr);
+ obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast<jlong>(this), resourceUrl, userAgent, etag, modified);
+ if (env->ExceptionCheck() || (obj == nullptr)) {
+ env->ExceptionDescribe();
+ }
+
+ obj = env->NewGlobalRef(obj);
+ if (obj == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+
+ context->addRequest(this);
+ start();
+}
+
+HTTPAndroidRequest::~HTTPAndroidRequest() {
+ context->removeRequest(this);
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()");
+
+ env->DeleteGlobalRef(obj);
+ obj = nullptr;
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+
+ if (timer) {
+ uv_timer_stop(timer);
+ uv::close(timer);
+ timer = nullptr;
+ }
+}
+
+void HTTPAndroidRequest::cancel() {
+ cancelled = true;
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()");
+
+ env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+}
+
+void HTTPAndroidRequest::start() {
+ attempts++;
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()");
+
+ env->CallVoidMethod(obj, mbgl::android::httpRequestStartId);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+}
+
+void HTTPAndroidRequest::retry(uint64_t timeout) {
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(context->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPAndroidRequest::retry() {
+ if (timer && strategy == PreemptImmediately) {
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void HTTPAndroidRequest::restart(uv_timer_t *timer, int) {
+#else
+void HTTPAndroidRequest::restart(uv_timer_t *timer) {
+#endif
+ auto baton = reinterpret_cast<HTTPAndroidRequest *>(timer->data);
+
+ baton->timer = nullptr;
+ uv::close(timer);
+
+ baton->start();
+}
+
+void HTTPAndroidRequest::finish(ResponseStatus status) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ if (status == ResponseStatus::NotModified) {
+ notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ delete this;
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return 0;
+}
+
+void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) {
+ if (cancelled) {
+ delete this;
+ return;
+ }
+
+ if (!response) {
+ response = std::make_unique<Response>();
+ }
+
+ response->message = message;
+ response->modified = parse_date(modified.c_str());
+ response->etag = etag;
+ response->expires = parseCacheControl(cacheControl.c_str());
+ if (!expires.empty()) {
+ response->expires = parse_date(expires.c_str());
+ }
+ response->data = body;
+
+ if (code == 304) {
+ if (existingResponse) {
+ response->status = existingResponse->status;
+ response->message = existingResponse->message;
+ response->modified = existingResponse->modified;
+ response->etag = existingResponse->etag;
+ response->data = existingResponse->data;
+ return finish(ResponseStatus::NotModified);
+ } else {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ }
+ } else if (code == 200) {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ } else if (code >= 500 && code < 600) {
+ response->status = Response::Error;
+ response->message = "HTTP status code " + util::toString(code);
+ return finish(ResponseStatus::TemporaryError);
+ } else {
+ response->status = Response::Error;
+ response->message = "HTTP status code " + util::toString(code);
+ return finish(ResponseStatus::PermanentError);
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+void HTTPAndroidRequest::onFailure(int type, std::string message) {
+ if (cancelled) {
+ delete this;
+ return;
+ }
+
+ if (!response) {
+ response = std::make_unique<Response>();
+ }
+
+ response->status = Response::Error;
+ response->message = message;
+
+ switch (type) {
+ case connectionError:
+ return finish(ResponseStatus::ConnectionError);
+
+ case temporaryError:
+ return finish(ResponseStatus::TemporaryError);
+
+ default:
+ return finish(ResponseStatus::PermanentError);
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext(uv_loop_t* loop) {
+ return std::make_unique<HTTPAndroidContext>(loop);
+}
+
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure");
+ assert(nativePtr != 0);
+ HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
+ std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
+ return request->onFailure(type, messageStr);
+}
+
+void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse");
+ assert(nativePtr != 0);
+ HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
+ std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
+ std::string etagStr;
+ if (etag != nullptr) {
+ etagStr = mbgl::android::std_string_from_jstring(env, etag);
+ }
+ std::string modifiedStr;
+ if (modified != nullptr) {
+ modifiedStr = mbgl::android::std_string_from_jstring(env, modified);
+ }
+ std::string cacheControlStr;
+ if (cacheControl != nullptr) {
+ cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl);
+ }
+ std::string expiresStr;
+ if (expires != nullptr) {
+ expiresStr = mbgl::android::std_string_from_jstring(env, expires);
+ }
+ jbyte* bodyData = env->GetByteArrayElements(body, nullptr);
+ std::string bodyStr(reinterpret_cast<char*>(bodyData), env->GetArrayLength(body));
+ env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT);
+ return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr);
+}
+
+}
diff --git a/platform/default/glfw_view.cpp b/platform/default/glfw_view.cpp
index 078c26feef..8453845d1e 100644
--- a/platform/default/glfw_view.cpp
+++ b/platform/default/glfw_view.cpp
@@ -15,7 +15,8 @@ void glfwError(int error, const char *description) {
assert(false);
}
-GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) {
+GLFWView::GLFWView(bool fullscreen_, bool benchmark_)
+ : fullscreen(fullscreen_), benchmark(benchmark_) {
glfwSetErrorCallback(glfwError);
std::srand(std::time(0));
@@ -28,6 +29,9 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) {
GLFWmonitor *monitor = nullptr;
if (fullscreen) {
monitor = glfwGetPrimaryMonitor();
+ auto videoMode = glfwGetVideoMode(monitor);
+ width = videoMode->width;
+ height = videoMode->height;
}
#ifdef DEBUG
@@ -56,7 +60,13 @@ GLFWView::GLFWView(bool fullscreen_) : fullscreen(fullscreen_) {
glfwSetWindowUserPointer(window, this);
glfwMakeContextCurrent(window);
- glfwSwapInterval(1);
+ if (benchmark) {
+ // Disables vsync on platforms that support it.
+ glfwSwapInterval(0);
+ } else {
+ glfwSwapInterval(1);
+ }
+
glfwSetCursorPosCallback(window, onMouseMove);
glfwSetMouseButtonCallback(window, onMouseClick);
@@ -336,7 +346,12 @@ void GLFWView::run() {
glfwWaitEvents();
const bool dirty = !clean.test_and_set();
if (dirty) {
+ const double started = glfwGetTime();
map->renderSync();
+ report(1000 * (glfwGetTime() - started));
+ if (benchmark) {
+ map->setNeedsRepaint();
+ }
map->nudgeTransitions();
}
}
@@ -373,20 +388,20 @@ void GLFWView::invalidate() {
void GLFWView::swap() {
glfwSwapBuffers(window);
- fps();
}
-void GLFWView::fps() {
- static int frames = 0;
- static double timeElapsed = 0;
-
+void GLFWView::report(float duration) {
frames++;
- double currentTime = glfwGetTime();
+ frameTime += duration;
- if (currentTime - timeElapsed >= 1) {
- mbgl::Log::Info(mbgl::Event::OpenGL, "FPS: %4.2f", frames / (currentTime - timeElapsed));
- timeElapsed = currentTime;
+ const double currentTime = glfwGetTime();
+ if (currentTime - lastReported >= 1) {
+ frameTime /= frames;
+ mbgl::Log::Info(mbgl::Event::OpenGL, "Frame time: %6.2fms (%6.2f fps)", frameTime,
+ 1000 / frameTime);
frames = 0;
+ frameTime = 0;
+ lastReported = currentTime;
}
}
diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp
index 0f7f8c0ac5..e416034b40 100644
--- a/platform/default/http_request_curl.cpp
+++ b/platform/default/http_request_curl.cpp
@@ -11,12 +11,6 @@
#include <curl/curl.h>
-#ifdef __ANDROID__
-#include <mbgl/android/jni.hpp>
-#include <zip.h>
-#include <openssl/ssl.h>
-#endif
-
#include <queue>
#include <map>
#include <cassert>
@@ -331,104 +325,6 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us
// -------------------------------------------------------------------------------------------------
-#ifdef __ANDROID__
-
-// This function is called to load the CA bundle
-// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯
-static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) {
-
- int error = 0;
- struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
- if (apk == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
- if (apkFile == nullptr) {
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_stat stat;
- if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- if (stat.size > std::numeric_limits<int>::max()) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- const auto pem = std::make_unique<char[]>(stat.size);
-
- if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a pointer to the X509 certificate store (which may be empty!)
- X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
- if (store == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a BIO
- BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
- if (bio == nullptr) {
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // use it to read the PEM formatted certificate from memory into an X509
- // structure that SSL can use
- X509 *cert = nullptr;
- while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
- if (cert == nullptr) {
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // add our certificate to this store
- if (X509_STORE_add_cert(store, cert) == 0) {
- X509_free(cert);
- cert = nullptr;
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- X509_free(cert);
- cert = nullptr;
- }
-
- // decrease reference counts
- BIO_free(bio);
- bio = nullptr;
-
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
-
- // all set to go
- return CURLE_OK;
-}
-#endif
-
HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
: HTTPRequestBase(resource_, callback_),
context(context_),
@@ -458,12 +354,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso
handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error));
-#ifdef __ANDROID__
- handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM"));
- handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function));
-#else
handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
-#endif
handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str()));
handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
diff --git a/platform/default/thread.cpp b/platform/default/thread.cpp
index c0a1069b9c..12d1802c2f 100644
--- a/platform/default/thread.cpp
+++ b/platform/default/thread.cpp
@@ -1,11 +1,23 @@
#include <mbgl/platform/platform.hpp>
+#include <mbgl/platform/log.hpp>
+
+#include <pthread.h>
+#include <sched.h>
+
namespace mbgl {
namespace platform {
void makeThreadLowPriority() {
- // no-op
+#ifdef SCHED_IDLE
+ struct sched_param param;
+ param.sched_priority = 0;
+ int status = sched_setscheduler(0, SCHED_IDLE, &param);
+ if (status != 0) {
+ Log::Warning(Event::General, "Couldn't set thread scheduling policy");
+ }
+#endif
}
-}
-}
+} // namespace platform
+} // namespace mbgl
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index 81338f2889..8950663551 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -737,6 +737,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
{
[self updateHeadingForDeviceOrientation];
[self updateCompass];
+ [self updateUserLocationAnnotationView];
}
}
@@ -2046,7 +2047,9 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
self.selectedAnnotation = annotation;
- if (annotation.title && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] &&
+ if ([annotation respondsToSelector:@selector(title)] &&
+ annotation.title &&
+ [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] &&
[self.delegate mapView:self annotationCanShowCallout:annotation])
{
// build the callout
diff --git a/platform/ios/MGLUserLocationAnnotationView.m b/platform/ios/MGLUserLocationAnnotationView.m
index 67f360f68a..536326d1f6 100644
--- a/platform/ios/MGLUserLocationAnnotationView.m
+++ b/platform/ios/MGLUserLocationAnnotationView.m
@@ -5,7 +5,8 @@
#import "MGLAnnotation.h"
#import "MGLMapView.h"
-const CGFloat MGLTrackingDotRingWidth = 24.0;
+const CGFloat MGLUserLocationAnnotationDotSize = 22.0;
+const CGFloat MGLUserLocationAnnotationHaloSize = 115.0;
@interface MGLUserLocationAnnotationView ()
@@ -17,9 +18,15 @@ const CGFloat MGLTrackingDotRingWidth = 24.0;
@implementation MGLUserLocationAnnotationView
{
+ CALayer *_headingIndicatorLayer;
+ CAShapeLayer *_headingIndicatorMaskLayer;
CALayer *_accuracyRingLayer;
CALayer *_dotBorderLayer;
CALayer *_dotLayer;
+
+ double _oldHeadingAccuracy;
+ CLLocationAccuracy _oldHorizontalAccuracy;
+ double _oldZoom;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -30,7 +37,7 @@ const CGFloat MGLTrackingDotRingWidth = 24.0;
- (instancetype)initInMapView:(MGLMapView *)mapView
{
- if (self = [super initWithFrame:CGRectMake(0, 0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth)])
+ if (self = [super initWithFrame:CGRectMake(0, 0, MGLUserLocationAnnotationDotSize, MGLUserLocationAnnotationDotSize)])
{
self.annotation = [[MGLUserLocation alloc] initWithMapView:mapView];
_mapView = mapView;
@@ -48,164 +55,279 @@ const CGFloat MGLTrackingDotRingWidth = 24.0;
- (void)setTintColor:(UIColor *)tintColor
{
- UIImage *trackingDotHaloImage = [self trackingDotHaloImage];
- _haloLayer.bounds = CGRectMake(0, 0, trackingDotHaloImage.size.width, trackingDotHaloImage.size.height);
- _haloLayer.contents = (__bridge id)[trackingDotHaloImage CGImage];
+ if (_accuracyRingLayer)
+ {
+ _accuracyRingLayer.backgroundColor = [tintColor CGColor];
+ }
- UIImage *dotImage = [self dotImage];
- _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height);
- _dotLayer.contents = (__bridge id)[dotImage CGImage];
+ _haloLayer.backgroundColor = [tintColor CGColor];
+ _dotLayer.backgroundColor = [tintColor CGColor];
+
+ _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
}
- (void)setupLayers
{
if (CLLocationCoordinate2DIsValid(self.annotation.coordinate))
{
- if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy)
+ // update heading indicator
+ //
+ if (_headingIndicatorLayer)
{
- UIImage *accuracyRingImage = [self accuracyRingImage];
- _accuracyRingLayer = [CALayer layer];
- _haloLayer.bounds = CGRectMake(0, 0, accuracyRingImage.size.width, accuracyRingImage.size.height);
- _haloLayer.contents = (__bridge id)[accuracyRingImage CGImage];
- _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0);
+ _headingIndicatorLayer.hidden = (_mapView.userTrackingMode == MGLUserTrackingModeFollowWithHeading) ? NO : YES;
- [self.layer addSublayer:_accuracyRingLayer];
+ if (_oldHeadingAccuracy != self.annotation.heading.headingAccuracy)
+ {
+ // recalculate the clipping mask based on updated accuracy
+ _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
+
+ _oldHeadingAccuracy = self.annotation.heading.headingAccuracy;
+ }
}
- if ( ! _haloLayer)
+ // heading indicator (tinted, semi-circle)
+ //
+ if ( ! _headingIndicatorLayer && self.annotation.heading.headingAccuracy)
{
- UIImage *haloImage = [self trackingDotHaloImage];
- _haloLayer = [CALayer layer];
- _haloLayer.bounds = CGRectMake(0, 0, haloImage.size.width, haloImage.size.height);
- _haloLayer.contents = (__bridge id)[haloImage CGImage];
- _haloLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0);
-
- [CATransaction begin];
+ CGFloat headingIndicatorSize = MGLUserLocationAnnotationHaloSize;
- [CATransaction setAnimationDuration:3.5];
- [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
+ _headingIndicatorLayer = [CALayer layer];
+ _headingIndicatorLayer.bounds = CGRectMake(0, 0, headingIndicatorSize, headingIndicatorSize);
+ _headingIndicatorLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ _headingIndicatorLayer.contents = (__bridge id)[[self headingIndicatorTintedGradientImage] CGImage];
+ _headingIndicatorLayer.contentsGravity = kCAGravityBottom;
+ _headingIndicatorLayer.contentsScale = [UIScreen mainScreen].scale;
+ _headingIndicatorLayer.opacity = 0.4;
+ _headingIndicatorLayer.shouldRasterize = YES;
+ _headingIndicatorLayer.rasterizationScale = [UIScreen mainScreen].scale;
+ _headingIndicatorLayer.drawsAsynchronously = YES;
- // scale out radially
- //
- CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
- boundsAnimation.repeatCount = MAXFLOAT;
- boundsAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1.0)];
- boundsAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0, 2.0, 1.0)];
- boundsAnimation.removedOnCompletion = NO;
-
- [_haloLayer addAnimation:boundsAnimation forKey:@"animateScale"];
+ [self.layer insertSublayer:_headingIndicatorLayer below:_dotBorderLayer];
+ }
+
+ // heading indicator accuracy mask (fan-shaped)
+ //
+ if ( ! _headingIndicatorMaskLayer && self.annotation.heading.headingAccuracy)
+ {
+ _headingIndicatorMaskLayer = [CAShapeLayer layer];
+ _headingIndicatorMaskLayer.frame = _headingIndicatorLayer.bounds;
+ _headingIndicatorMaskLayer.path = [[self headingIndicatorClippingMask] CGPath];
+
+ // apply the mask to the halo-radius-sized gradient layer
+ _headingIndicatorLayer.mask = _headingIndicatorMaskLayer;
- // go transparent as scaled out
- //
- CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
- opacityAnimation.repeatCount = MAXFLOAT;
- opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0];
- opacityAnimation.toValue = [NSNumber numberWithFloat:-1.0];
- opacityAnimation.removedOnCompletion = NO;
+ _oldHeadingAccuracy = self.annotation.heading.headingAccuracy;
+ }
+
+ // update accuracy ring (if zoom or horizontal accuracy have changed)
+ //
+ if (_accuracyRingLayer && (_oldZoom != self.mapView.zoomLevel || _oldHorizontalAccuracy != self.annotation.location.horizontalAccuracy))
+ {
+ CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
- [_haloLayer addAnimation:opacityAnimation forKey:@"animateOpacity"];
+ // only show the accuracy ring if it won't be obscured by the location dot
+ if (accuracyRingSize > MGLUserLocationAnnotationDotSize + 15)
+ {
+ _accuracyRingLayer.hidden = NO;
+ _accuracyRingLayer.bounds = CGRectMake(0, 0, accuracyRingSize, accuracyRingSize);
+ _accuracyRingLayer.cornerRadius = accuracyRingSize / 2;
+
+ // match the halo to the accuracy ring
+ _haloLayer.bounds = _accuracyRingLayer.bounds;
+ _haloLayer.cornerRadius = _accuracyRingLayer.cornerRadius;
+ _haloLayer.shouldRasterize = NO;
+ }
+ else
+ {
+ _accuracyRingLayer.hidden = YES;
+
+ _haloLayer.bounds = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
+ _haloLayer.cornerRadius = MGLUserLocationAnnotationHaloSize / 2.0;
+ _haloLayer.shouldRasterize = YES;
+ _haloLayer.rasterizationScale = [UIScreen mainScreen].scale;
+ }
- [CATransaction commit];
+ // store accuracy and zoom so we're not redrawing unchanged location updates
+ _oldHorizontalAccuracy = self.annotation.location.horizontalAccuracy;
+ _oldZoom = self.mapView.zoomLevel;
+ }
+
+ // accuracy ring (circular, tinted, mostly-transparent)
+ //
+ if ( ! _accuracyRingLayer && self.annotation.location.horizontalAccuracy)
+ {
+ CGFloat accuracyRingSize = [self calculateAccuracyRingSize];
+ _accuracyRingLayer = [self circleLayerWithSize:accuracyRingSize];
+ _accuracyRingLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _accuracyRingLayer.opacity = 0.1;
+ _accuracyRingLayer.shouldRasterize = NO;
+ _accuracyRingLayer.allowsGroupOpacity = NO;
- [self.layer addSublayer:_haloLayer];
+ [self.layer addSublayer:_accuracyRingLayer];
}
- // white dot background with shadow
+ // expanding sonar-like pulse (circular, tinted, fades out)
//
- if ( ! _dotBorderLayer)
+ if ( ! _haloLayer)
{
- CGRect rect = CGRectMake(0, 0, MGLTrackingDotRingWidth * 1.5, MGLTrackingDotRingWidth * 1.5);
+ _haloLayer = [self circleLayerWithSize:MGLUserLocationAnnotationHaloSize];
+ _haloLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _haloLayer.allowsGroupOpacity = NO;
- UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]);
- CGContextRef context = UIGraphicsGetCurrentContext();
+ // set defaults for the animations
+ CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:3.0];
- CGContextSetShadow(context, CGSizeMake(0, 0), MGLTrackingDotRingWidth / 4.0);
+ // scale out radially with initial acceleration
+ CAKeyframeAnimation *boundsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.xy"];
+ boundsAnimation.values = @[@0, @0.35, @1];
+ boundsAnimation.keyTimes = @[@0, @0.2, @1];
- CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
- CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - MGLTrackingDotRingWidth) / 2.0, (rect.size.height - MGLTrackingDotRingWidth) / 2.0, MGLTrackingDotRingWidth, MGLTrackingDotRingWidth));
+ // go transparent as scaled out, start semi-opaque
+ CAKeyframeAnimation *opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
+ opacityAnimation.values = @[@0.4, @0.4, @0];
+ opacityAnimation.keyTimes = @[@0, @0.2, @1];
- UIImage *whiteBackground = UIGraphicsGetImageFromCurrentImageContext();
+ animationGroup.animations = @[boundsAnimation, opacityAnimation];
- UIGraphicsEndImageContext();
+ [_haloLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
+
+ [self.layer addSublayer:_haloLayer];
+ }
+
+ // background dot (white with black shadow)
+ //
+ if ( ! _dotBorderLayer)
+ {
+ _dotBorderLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize];
+ _dotBorderLayer.backgroundColor = [[UIColor whiteColor] CGColor];
+ _dotBorderLayer.shadowColor = [[UIColor blackColor] CGColor];
+ _dotBorderLayer.shadowOffset = CGSizeMake(0, 0);
+ _dotBorderLayer.shadowRadius = 3;
+ _dotBorderLayer.shadowOpacity = 0.25;
- _dotBorderLayer = [CALayer layer];
- _dotBorderLayer.bounds = CGRectMake(0, 0, whiteBackground.size.width, whiteBackground.size.height);
- _dotBorderLayer.contents = (__bridge id)[whiteBackground CGImage];
- _dotBorderLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0);
[self.layer addSublayer:_dotBorderLayer];
}
-
- // pulsing, tinted dot sublayer
+
+ // inner dot (pulsing, tinted)
//
if ( ! _dotLayer)
{
- UIImage *dotImage = [self dotImage];
- _dotLayer = [CALayer layer];
- _dotLayer.bounds = CGRectMake(0, 0, dotImage.size.width, dotImage.size.height);
- _dotLayer.contents = (__bridge id)[dotImage CGImage];
- _dotLayer.position = CGPointMake(super.layer.bounds.size.width / 2.0, super.layer.bounds.size.height / 2.0);
-
- CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform"];
- animation.repeatCount = MAXFLOAT;
- animation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 1.0)];
- animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)];
- animation.removedOnCompletion = NO;
- animation.autoreverses = YES;
- animation.duration = 1.5;
- animation.beginTime = CACurrentMediaTime() + 1.0;
- animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
-
- [_dotLayer addAnimation:animation forKey:@"animateTransform"];
+ _dotLayer = [self circleLayerWithSize:MGLUserLocationAnnotationDotSize * 0.75];
+ _dotLayer.backgroundColor = [_mapView.tintColor CGColor];
+ _dotLayer.shouldRasterize = NO;
+
+ // set defaults for the animations
+ CAAnimationGroup *animationGroup = [self loopingAnimationGroupWithDuration:1.5];
+ animationGroup.autoreverses = YES;
+ animationGroup.fillMode = kCAFillModeBoth;
+
+ // scale the dot up and down
+ CABasicAnimation *pulseAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale.xy"];
+ pulseAnimation.fromValue = @0.8;
+ pulseAnimation.toValue = @1;
+
+ // fade opacity in and out, subtly
+ CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+ opacityAnimation.fromValue = @0.8;
+ opacityAnimation.toValue = @1;
+
+ animationGroup.animations = @[pulseAnimation, opacityAnimation];
+
+ [_dotLayer addAnimation:animationGroup forKey:@"animateTransformAndOpacity"];
[self.layer addSublayer:_dotLayer];
}
}
}
-- (UIImage *)accuracyRingImage
+- (CALayer *)circleLayerWithSize:(CGFloat)layerSize
{
- CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f;
- CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude];
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(pixelRadius * 2, pixelRadius * 2), NO, [[UIScreen mainScreen] scale]);
+ CALayer *circleLayer = [CALayer layer];
+ circleLayer.bounds = CGRectMake(0, 0, layerSize, layerSize);
+ circleLayer.position = CGPointMake(super.bounds.size.width / 2.0, super.bounds.size.height / 2.0);
+ circleLayer.cornerRadius = layerSize / 2.0;
+ circleLayer.shouldRasterize = YES;
+ circleLayer.rasterizationScale = [UIScreen mainScreen].scale;
+ circleLayer.drawsAsynchronously = YES;
- CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.7] CGColor]);
- CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor colorWithRed:0.378 green:0.552 blue:0.827 alpha:0.15] CGColor]);
- CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 2.0);
- CGContextStrokeEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, pixelRadius * 2, pixelRadius * 2));
+ return circleLayer;
+}
+
+- (CAAnimationGroup *)loopingAnimationGroupWithDuration:(CGFloat)animationDuration
+{
+ CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
+ animationGroup.duration = animationDuration;
+ animationGroup.repeatCount = INFINITY;
+ animationGroup.removedOnCompletion = NO;
+ animationGroup.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
- UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return finalImage;
+ return animationGroup;
}
-- (UIImage *)trackingDotHaloImage
+- (CGFloat)calculateAccuracyRingSize
{
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, [[UIScreen mainScreen] scale]);
- CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[_mapView.tintColor colorWithAlphaComponent:0.75] CGColor]);
- CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 100, 100));
- UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
+ CGFloat latRadians = self.annotation.coordinate.latitude * M_PI / 180.0f;
+ CGFloat pixelRadius = self.annotation.location.horizontalAccuracy / cos(latRadians) / [self.mapView metersPerPixelAtLatitude:self.annotation.coordinate.latitude];
- return finalImage;
+ return pixelRadius * 2;
}
-- (UIImage *)dotImage
+- (UIImage *)headingIndicatorTintedGradientImage
{
- CGFloat tintedWidth = MGLTrackingDotRingWidth * 0.7;
+ UIImage *image;
- CGRect rect = CGRectMake(0, 0, tintedWidth, tintedWidth);
+ CGFloat haloRadius = MGLUserLocationAnnotationHaloSize / 2.0;
- UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]);
+ UIGraphicsBeginImageContextWithOptions(CGSizeMake(MGLUserLocationAnnotationHaloSize, haloRadius), NO, 0);
+
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
- CGContextSetFillColorWithColor(context, [_mapView.tintColor CGColor]);
- CGContextFillEllipseInRect(context, CGRectMake((rect.size.width - tintedWidth) / 2.0, (rect.size.height - tintedWidth) / 2.0, tintedWidth, tintedWidth));
+ // gradient from the tint color to no-alpha tint color
+ CGFloat gradientLocations[] = {0.0, 1.0};
+ CGGradientRef gradient = CGGradientCreateWithColors(
+ colorSpace, (__bridge CFArrayRef)@[(id)[_mapView.tintColor CGColor],
+ (id)[[_mapView.tintColor colorWithAlphaComponent:0] CGColor]], gradientLocations);
- UIImage *tintedForeground = UIGraphicsGetImageFromCurrentImageContext();
+ // draw the gradient from the center point to the edge (full halo radius)
+ CGPoint centerPoint = CGPointMake(haloRadius, haloRadius);
+ CGContextDrawRadialGradient(context, gradient,
+ centerPoint, 0.0,
+ centerPoint, haloRadius,
+ nil);
+ image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
- return tintedForeground;
+ CGGradientRelease(gradient);
+ CGColorSpaceRelease(colorSpace);
+
+ return image;
+}
+
+- (UIBezierPath *)headingIndicatorClippingMask
+{
+ CGFloat accuracy = self.annotation.heading.headingAccuracy;
+
+ // size the mask using exagerated accuracy, but keep within a good display range
+ CGFloat clippingDegrees = 90 - (accuracy * 1.5);
+ clippingDegrees = fmin(clippingDegrees, 55);
+ clippingDegrees = fmax(clippingDegrees, 10);
+
+ CGRect ovalRect = CGRectMake(0, 0, MGLUserLocationAnnotationHaloSize, MGLUserLocationAnnotationHaloSize);
+ UIBezierPath *ovalPath = UIBezierPath.bezierPath;
+
+ // clip the oval to ± incoming accuracy degrees (converted to radians), from the top
+ [ovalPath addArcWithCenter:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))
+ radius:CGRectGetWidth(ovalRect) / 2.0
+ startAngle:(-180 + clippingDegrees) * M_PI / 180
+ endAngle:-clippingDegrees * M_PI / 180
+ clockwise:YES];
+
+ [ovalPath addLineToPoint:CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
+ [ovalPath closePath];
+
+ return ovalPath;
}
@end
diff --git a/scripts/android/configure.sh b/scripts/android/configure.sh
index 667ea68911..70c8973a8b 100644
--- a/scripts/android/configure.sh
+++ b/scripts/android/configure.sh
@@ -1,8 +1,6 @@
#!/usr/bin/env bash
BOOST_VERSION=1.57.0
-LIBCURL_VERSION=7.40.0
-OPENSSL_VERSION=1.0.1l
LIBPNG_VERSION=1.6.16
JPEG_VERSION=v9a
SQLITE_VERSION=3.8.8.1
@@ -11,4 +9,4 @@ ZLIB_VERSION=system
NUNICODE_VERSION=1.5.1
LIBZIP_VERSION=0.11.2
-export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION} \ No newline at end of file
+export MASON_ANDROID_ABI=${MASON_PLATFORM_VERSION}
diff --git a/scripts/android/defaults.mk b/scripts/android/defaults.mk
index 436f9db710..29f047c887 100644
--- a/scripts/android/defaults.mk
+++ b/scripts/android/defaults.mk
@@ -1,7 +1,7 @@
HEADLESS ?= none
PLATFORM ?= android
ASSET ?= zip
-HTTP ?= curl
+HTTP ?= android
CACHE ?= sqlite
GYP_FLAVOR_SUFFIX=-android
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index fadd4c48d9..a7015695c1 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -375,6 +375,10 @@ bool Map::getDebug() const {
return data->getDebug();
}
+void Map::setNeedsRepaint() {
+ data->setNeedsRepaint(true);
+}
+
void Map::setCollisionDebug(bool value) {
data->setCollisionDebug(value);
update(Update::Repaint);
diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp
index 33e4e6d181..c14a61b8b7 100644
--- a/src/mbgl/map/map_context.cpp
+++ b/src/mbgl/map/map_context.cpp
@@ -345,12 +345,8 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame)
}
view.swap();
-
viewInvalidated = false;
-
- if (style->hasTransitions() || painter->needsAnimation()) {
- data.setNeedsRepaint(true);
- }
+ data.setNeedsRepaint(style->hasTransitions() || painter->needsAnimation());
return isLoaded();
}