diff --git a/CMakeLists.txt b/CMakeLists.txt index d021c856f..9169dea81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.1) project(mbgl LANGUAGES CXX C) +set(CMAKE_CXX_STANDARD 14) include(cmake/mbgl.cmake) @@ -44,6 +45,11 @@ if(APPLE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=unused-command-line-argument") endif() +if(CMAKE_COMPILER_IS_GNUCXX) + # https://svn.boost.org/trac/boost/ticket/9240 + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") +endif() + if(NOT EXISTS ${CMAKE_SOURCE_DIR}/platform/${MBGL_PLATFORM}/config.cmake) message(ERROR "Can't find config.cmake file for platform ${MBGL_PLATFORM}") endif() diff --git a/benchmark/api/query.benchmark.cpp b/benchmark/api/query.benchmark.cpp index 7fa078145..5a95cd766 100644 --- a/benchmark/api/query.benchmark.cpp +++ b/benchmark/api/query.benchmark.cpp @@ -2,8 +2,9 @@ #include #include -#include -#include +#include +#include +#include #include #include #include @@ -28,16 +29,15 @@ public: auto image = std::make_unique(std::move(decoded), 1.0); map.addImage("test-icon", std::move(image)); - view.resize(1000, 1000); - - mbgl::benchmark::render(map); + mbgl::benchmark::render(map, view); } util::RunLoop loop; - std::shared_ptr display{ std::make_shared() }; - HeadlessView view{ display, 1 }; + HeadlessBackend backend; + OffscreenView view{ backend.getContext(), {{ 1000, 1000 }} }; DefaultFileSource fileSource{ "benchmark/fixtures/api/cache.db", "." }; - Map map{ view, fileSource, MapMode::Still }; + ThreadPool threadPool{ 4 }; + Map map{ backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still }; ScreenBox box{{ 0, 0 }, { 1000, 1000 }}; }; diff --git a/benchmark/fixtures/api/cache.db b/benchmark/fixtures/api/cache.db index 102c4074e..a62a3b0f0 100644 Binary files a/benchmark/fixtures/api/cache.db and b/benchmark/fixtures/api/cache.db differ diff --git a/benchmark/src/mbgl/benchmark/util.cpp b/benchmark/src/mbgl/benchmark/util.cpp index a8f71f120..39c5f8688 100644 --- a/benchmark/src/mbgl/benchmark/util.cpp +++ b/benchmark/src/mbgl/benchmark/util.cpp @@ -1,16 +1,18 @@ #include +#include #include +#include #include #include namespace mbgl { namespace benchmark { -void render(Map& map) { +void render(Map& map, OffscreenView& view) { PremultipliedImage result; - map.renderStill([&result](std::exception_ptr, PremultipliedImage&& image) { - result = std::move(image); + map.renderStill(view, [&](std::exception_ptr) { + result = view.readStillImage(); }); while (!result.size()) { diff --git a/benchmark/src/mbgl/benchmark/util.hpp b/benchmark/src/mbgl/benchmark/util.hpp index 759015e0e..73acfb69d 100644 --- a/benchmark/src/mbgl/benchmark/util.hpp +++ b/benchmark/src/mbgl/benchmark/util.hpp @@ -3,10 +3,11 @@ namespace mbgl { class Map; +class OffscreenView; namespace benchmark { -void render(Map&); +void render(Map&, OffscreenView&); } // namespace benchmark } // namespace mbgl diff --git a/bin/glfw.cpp b/bin/glfw.cpp index 142095f8a..b51846b4e 100644 --- a/bin/glfw.cpp +++ b/bin/glfw.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -12,10 +13,11 @@ #include #include #include +#include namespace { -std::unique_ptr view; +GLFWView* view = nullptr; } @@ -103,7 +105,8 @@ int main(int argc, char *argv[]) { mbgl::Log::Info(mbgl::Event::General, "BENCHMARK MODE: Some optimizations are disabled."); } - view = std::make_unique(fullscreen, benchmark); + GLFWView backend(fullscreen, benchmark); + view = &backend; mbgl::DefaultFileSource fileSource("/tmp/mbgl-cache.db", "."); @@ -115,7 +118,11 @@ int main(int argc, char *argv[]) { fileSource.setAccessToken(std::string(token)); } - mbgl::Map map(*view, fileSource); + mbgl::ThreadPool threadPool(4); + + mbgl::Map map(backend, view->getSize(), view->getPixelRatio(), fileSource, threadPool); + + backend.setMap(&map); // Load settings mbgl::Settings_JSON settings; @@ -178,5 +185,6 @@ int main(int argc, char *argv[]) { "Exit location: --lat=\"%f\" --lon=\"%f\" --zoom=\"%f\" --bearing \"%f\"", settings.latitude, settings.longitude, settings.zoom, settings.bearing); + view = nullptr; return 0; } diff --git a/bin/render.cpp b/bin/render.cpp index a43bf32dd..ad29d9199 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -3,8 +3,9 @@ #include #include -#include -#include +#include +#include +#include #include #pragma GCC diagnostic push @@ -28,7 +29,6 @@ int main(int argc, char *argv[]) { int width = 512; int height = 512; - double pixelRatio = 1.0; static std::string output = "out.png"; std::string cache_file = "cache.sqlite"; std::string asset_root = "."; @@ -83,8 +83,10 @@ int main(int argc, char *argv[]) { fileSource.setAccessToken(std::string(token)); } - HeadlessView view(pixelRatio, width, height); - Map map(view, fileSource, MapMode::Still); + HeadlessBackend backend; + OffscreenView view(backend.getContext(), {{ static_cast(width), static_cast(height) }}); + ThreadPool threadPool(4); + Map map(backend, view.getSize(), 1, fileSource, threadPool, MapMode::Still); map.setStyleJSON(style); map.setClasses(classes); @@ -97,7 +99,7 @@ int main(int argc, char *argv[]) { map.setDebug(debug ? mbgl::MapDebugOptions::TileBorders | mbgl::MapDebugOptions::ParseStatus : mbgl::MapDebugOptions::NoDebug); } - map.renderStill([&](std::exception_ptr error, PremultipliedImage&& image) { + map.renderStill(view, [&](std::exception_ptr error) { try { if (error) { std::rethrow_exception(error); @@ -107,7 +109,7 @@ int main(int argc, char *argv[]) { exit(1); } - util::write_file(output, encodePNG(image)); + util::write_file(output, encodePNG(view.readStillImage())); loop.stop(); }); diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 8940899c1..4d5cddbc4 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -8,8 +8,6 @@ set(MBGL_CORE_FILES src/mbgl/actor/mailbox.hpp src/mbgl/actor/message.hpp src/mbgl/actor/scheduler.hpp - src/mbgl/actor/thread_pool.cpp - src/mbgl/actor/thread_pool.hpp # algorithm src/mbgl/algorithm/covered_by_children.hpp @@ -56,6 +54,7 @@ set(MBGL_CORE_FILES # gl include/mbgl/gl/gl.hpp + include/mbgl/gl/implementation.hpp src/mbgl/gl/attribute.hpp src/mbgl/gl/context.cpp src/mbgl/gl/context.hpp @@ -63,10 +62,12 @@ set(MBGL_CORE_FILES src/mbgl/gl/debugging.hpp src/mbgl/gl/extension.cpp src/mbgl/gl/extension.hpp + src/mbgl/gl/framebuffer.hpp src/mbgl/gl/gl.cpp src/mbgl/gl/index_buffer.hpp src/mbgl/gl/object.cpp src/mbgl/gl/object.hpp + src/mbgl/gl/renderbuffer.hpp src/mbgl/gl/shader.cpp src/mbgl/gl/shader.hpp src/mbgl/gl/state.hpp @@ -94,18 +95,19 @@ set(MBGL_CORE_FILES src/mbgl/layout/symbol_layout.hpp # map + include/mbgl/map/backend.hpp include/mbgl/map/camera.hpp include/mbgl/map/map.hpp include/mbgl/map/mode.hpp - include/mbgl/map/update.hpp include/mbgl/map/view.hpp + src/mbgl/map/backend.cpp src/mbgl/map/change.hpp src/mbgl/map/map.cpp src/mbgl/map/transform.cpp src/mbgl/map/transform.hpp src/mbgl/map/transform_state.cpp src/mbgl/map/transform_state.hpp - src/mbgl/map/view.cpp + src/mbgl/map/update.hpp src/mbgl/map/zoom_history.hpp # math @@ -132,9 +134,11 @@ set(MBGL_CORE_FILES # platform/default include/mbgl/platform/default/glfw_view.hpp + include/mbgl/platform/default/headless_backend.hpp include/mbgl/platform/default/headless_display.hpp - include/mbgl/platform/default/headless_view.hpp + include/mbgl/platform/default/offscreen_view.hpp include/mbgl/platform/default/settings_json.hpp + include/mbgl/platform/default/thread_pool.hpp # renderer src/mbgl/renderer/bucket.hpp diff --git a/cmake/core.cmake b/cmake/core.cmake index ef98c4ecc..bc5e7eb71 100644 --- a/cmake/core.cmake +++ b/cmake/core.cmake @@ -36,7 +36,7 @@ target_include_directories(mbgl-core target_add_mason_package(mbgl-core PUBLIC geometry) target_add_mason_package(mbgl-core PUBLIC variant) -target_add_mason_package(mbgl-core PRIVATE unique_resource) +target_add_mason_package(mbgl-core PUBLIC unique_resource) target_add_mason_package(mbgl-core PRIVATE rapidjson) target_add_mason_package(mbgl-core PRIVATE boost) target_add_mason_package(mbgl-core PRIVATE geojson) diff --git a/cmake/node.cmake b/cmake/node.cmake index 75def7c46..8dd95a941 100644 --- a/cmake/node.cmake +++ b/cmake/node.cmake @@ -11,6 +11,8 @@ target_sources(mbgl-node PRIVATE platform/node/src/node_request.cpp PRIVATE platform/node/src/node_feature.hpp PRIVATE platform/node/src/node_feature.cpp + PRIVATE platform/node/src/node_thread_pool.hpp + PRIVATE platform/node/src/node_thread_pool.cpp PRIVATE platform/node/src/util/async_queue.hpp # We are compiling with the uv loop, but since this target already has the headers for libuv, diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 2ade1ba53..598e01822 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -89,9 +89,9 @@ set(MBGL_TEST_FILES # tile test/tile/geometry_tile_data.test.cpp test/tile/raster_tile.test.cpp + test/tile/tile_coordinate.test.cpp test/tile/tile_id.test.cpp test/tile/vector_tile.test.cpp - test/tile/tile_coordinate.test.cpp # util test/util/async_task.test.cpp diff --git a/include/mbgl/gl/implementation.hpp b/include/mbgl/gl/implementation.hpp new file mode 100644 index 000000000..4e3a3e51c --- /dev/null +++ b/include/mbgl/gl/implementation.hpp @@ -0,0 +1,14 @@ +#pragma once + +#if defined(__QT__) + #define MBGL_USE_QT 1 +#elif defined(__APPLE__) + #include + #if TARGET_OS_IOS + #define MBGL_USE_EAGL 1 + #else + #define MBGL_USE_CGL 1 + #endif +#else + #define MBGL_USE_GLX 1 +#endif diff --git a/include/mbgl/map/backend.hpp b/include/mbgl/map/backend.hpp new file mode 100644 index 000000000..c11d09490 --- /dev/null +++ b/include/mbgl/map/backend.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include + +namespace mbgl { + +namespace gl { +class Context; +} // namespace gl + +class Backend { +public: + Backend(); + virtual ~Backend(); + + // Returns the backend's context which manages OpenGL state. + gl::Context& getContext(); + + // Called when the backend's GL context needs to be made active or inactive. These are called, + // as a matched pair, in four situations: + // + // 1. When releasing GL resources during Map destruction + // 2. When calling a CustomLayerInitializeFunction, during Map::addLayer + // 3. When calling a CustomLayerDeinitializeFunction, during Map::removeLayer + // 4. When rendering for Map::renderStill + // + // They are *not* called for Map::render; it is assumed that the correct context is already + // activated prior to calling Map::render. + virtual void activate() = 0; + virtual void deactivate() = 0; + + // Called when the map needs to be rendered; the backend should call Map::render() at some point + // in the near future. (Not called for Map::renderStill() mode.) + virtual void invalidate() = 0; + + // Notifies a watcher of map x/y/scale/rotation changes. + virtual void notifyMapChange(MapChange change); + +private: + const std::unique_ptr context; +}; + +} // namespace mbgl diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 914c2cd0b..b1c840e68 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include #include @@ -19,8 +17,10 @@ namespace mbgl { -class FileSource; +class Backend; class View; +class FileSource; +class Scheduler; class SpriteImage; struct CameraOptions; struct AnimationOptions; @@ -32,7 +32,11 @@ class Layer; class Map : private util::noncopyable { public: - explicit Map(View&, FileSource&, + explicit Map(Backend&, + std::array size, + float pixelRatio, + FileSource&, + Scheduler&, MapMode mapMode = MapMode::Continuous, GLContextMode contextMode = GLContextMode::Unique, ConstrainMode constrainMode = ConstrainMode::HeightOnly, @@ -41,14 +45,14 @@ public: // Register a callback that will get called (on the render thread) when all resources have // been loaded and a complete render occurs. - using StillImageCallback = std::function; - void renderStill(StillImageCallback callback); + using StillImageCallback = std::function; + void renderStill(View&, StillImageCallback callback); + + // Triggers a repaint. + void triggerRepaint(); // Main render function. - void render(); - - // Notifies the Map that the state has changed and an update might be necessary. - void update(Update update); + void render(View&); // Styling void addClass(const std::string&); @@ -132,6 +136,7 @@ public: ViewportMode getViewportMode() const; // Size + void setSize(const std::array&); uint16_t getWidth() const; uint16_t getHeight() const; diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index 590eef723..6517c6b22 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -1,63 +1,20 @@ #pragma once -#include -#include -#include - -#include -#include -#include +#include namespace mbgl { class Map; -class View { +class View : private util::noncopyable { public: virtual ~View() = default; - // Called directly after initialization. Must always return the same value, i.e. it may - // not change over time. - virtual float getPixelRatio() const = 0; - - // Called when the View signaled a dimension change. Must return the logical dimension - // of this map in pixels. - virtual std::array getSize() const = 0; - - // Called for every frame that is being rendered. Must return the absolute dimensions of - // the current framebuffer. Typically, this is the logical width scaled by the pixel ratio, - // but in case the view was moved to display with a different pixel ratio, it can also be - // different from that rule. - virtual std::array getFramebufferSize() const = 0; - - // Called when this View is associated with a Map object. - virtual void initialize(Map*); - - // Called when the view's GL context needs to be made active or inactive. These are called, - // as a matched pair, in four situations: - // - // 1. When releasing GL resources during Map destruction - // 2. When calling a CustomLayerInitializeFunction, during Map::addLayer - // 3. When calling a CustomLayerDeinitializeFunction, during Map::removeLayer - // 4. When rendering for Map::renderStill - // - // They are *not* called for Map::render; it is assumed that the correct context is already - // activated prior to calling Map::render. - virtual void activate() = 0; - virtual void deactivate() = 0; - - // Called when the map needs to be rendered; the view should call Map::render() at some point - // in the near future. (Not called for Map::renderStill() mode.) - virtual void invalidate() = 0; - - // Reads the pixel data from the current framebuffer. If your View implementation - // doesn't support reading from the framebuffer, return a null pointer. - virtual PremultipliedImage readStillImage(std::array size = {{ 0, 0 }}); - - // Notifies a watcher of map x/y/scale/rotation changes. - virtual void notifyMapChange(MapChange change); - -protected: - mbgl::Map *map = nullptr; + // Called when this View is used for rendering. Implementations should ensure that a renderable + // object is bound and glClear/glDraw* calls can be done. They should also make sure that + // calling .bind() repeatedly is a no-op and that the appropriate gl::Context values are + // set to the current state. + virtual void bind() = 0; }; + } // namespace mbgl diff --git a/include/mbgl/platform/default/glfw_view.hpp b/include/mbgl/platform/default/glfw_view.hpp index b35270983..e832cd70d 100644 --- a/include/mbgl/platform/default/glfw_view.hpp +++ b/include/mbgl/platform/default/glfw_view.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -11,26 +12,14 @@ #define GL_GLEXT_PROTOTYPES #include -class GLFWView : public mbgl::View { +class GLFWView : public mbgl::View, public mbgl::Backend { public: GLFWView(bool fullscreen = false, bool benchmark = false); ~GLFWView() override; - float getPixelRatio() const override; - std::array getSize() const override; - std::array getFramebufferSize() const override; + float getPixelRatio() const; - void initialize(mbgl::Map*) override; - void activate() override; - void deactivate() override; - void invalidate() override; - - static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods); - static void onScroll(GLFWwindow *window, double xoffset, double yoffset); - static void onWindowResize(GLFWwindow *window, int width, int height); - static void onFramebufferResize(GLFWwindow *window, int width, int height); - static void onMouseClick(GLFWwindow *window, int button, int action, int modifiers); - static void onMouseMove(GLFWwindow *window, double x, double y); + void setMap(mbgl::Map*); // Callback called when the user presses the key mapped to style change. // The expected action is to set a new style, different to the current one. @@ -41,9 +30,33 @@ public: void setWindowTitle(const std::string&); void run(); - void report(float duration); + + // mbgl::View implementation + void updateViewBinding(); + void bind() override; + std::array getSize() const; + std::array getFramebufferSize() const; + + // mbgl::Backend implementation + void activate() override; + void deactivate() override; + void invalidate() override; private: + // Window callbacks + static void onKey(GLFWwindow *window, int key, int scancode, int action, int mods); + static void onScroll(GLFWwindow *window, double xoffset, double yoffset); + static void onWindowResize(GLFWwindow *window, int width, int height); + static void onFramebufferResize(GLFWwindow *window, int width, int height); + static void onMouseClick(GLFWwindow *window, int button, int action, int modifiers); + static void onMouseMove(GLFWwindow *window, double x, double y); + + // Internal + void report(float duration); + + void setMapChangeCallback(std::function callback); + void notifyMapChange(mbgl::MapChange change) override; + mbgl::Color makeRandomColor() const; mbgl::Point makeRandomPoint() const; static std::shared_ptr @@ -62,7 +75,11 @@ private: mbgl::AnnotationIDs annotationIDs; std::vector spriteIDs; + std::function mapChangeCallback; + private: + mbgl::Map* map = nullptr; + bool fullscreen = false; const bool benchmark = false; bool tracking = false; diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_backend.hpp similarity index 53% rename from include/mbgl/platform/default/headless_view.hpp rename to include/mbgl/platform/default/headless_backend.hpp index 23f1e8251..2f4886a36 100644 --- a/include/mbgl/platform/default/headless_view.hpp +++ b/include/mbgl/platform/default/headless_backend.hpp @@ -1,18 +1,12 @@ #pragma once -#if defined(__QT__) -#define MBGL_USE_QT 1 +#include + +#if MBGL_USE_QT class QGLWidget; -#elif defined(__APPLE__) -#include -#if TARGET_OS_IOS -#define MBGL_USE_EAGL 1 -#else -#define MBGL_USE_CGL 1 -#endif -#else -#define GL_GLEXT_PROTOTYPES -#define MBGL_USE_GLX 1 +#elif MBGL_USE_CGL +#include +#elif MBGL_USE_GLX typedef struct _XDisplay Display; typedef struct __GLXcontextRec* GLXContext; typedef struct __GLXFBConfigRec* GLXFBConfig; @@ -20,53 +14,41 @@ typedef long unsigned int XID; typedef XID GLXPbuffer; #endif -#include -#include -#include +#include #include #include -#include +#include namespace mbgl { class HeadlessDisplay; -class HeadlessView : public View { +class HeadlessBackend : public Backend { public: - HeadlessView(float pixelRatio, uint16_t width = 256, uint16_t height = 256); - HeadlessView(std::shared_ptr display, float pixelRatio, uint16_t width = 256, uint16_t height = 256); - ~HeadlessView() override; - - float getPixelRatio() const override; - std::array getSize() const override; - std::array getFramebufferSize() const override; + HeadlessBackend(); + HeadlessBackend(std::shared_ptr); + ~HeadlessBackend() override; void invalidate() override; void activate() override; void deactivate() override; void notifyMapChange(MapChange) override; - PremultipliedImage readStillImage(std::array size = {{ 0, 0 }}) override; - - void resize(uint16_t width, uint16_t height); void setMapChangeCallback(std::function&& cb) { mapChangeCallback = std::move(cb); } +private: + void activateContext(); + void deactivateContext(); + private: // Implementation specific functions static gl::glProc initializeExtension(const char*); void createContext(); void destroyContext(); - void clearBuffers(); - void resizeFramebuffer(); - void activateContext(); - void deactivateContext(); std::shared_ptr display; - const float pixelRatio; - std::array dimensions; - bool needsResize = false; bool extensionsLoaded = false; bool active = false; @@ -91,9 +73,6 @@ private: std::function mapChangeCallback; - gl::FramebufferID fbo = 0; - gl::RenderbufferID fboDepthStencil = 0; - gl::RenderbufferID fboColor = 0; }; } // namespace mbgl diff --git a/include/mbgl/platform/default/headless_display.hpp b/include/mbgl/platform/default/headless_display.hpp index edcc90522..f43e61340 100644 --- a/include/mbgl/platform/default/headless_display.hpp +++ b/include/mbgl/platform/default/headless_display.hpp @@ -1,6 +1,13 @@ #pragma once -#include +#include + +#if MBGL_USE_CGL +#include +#elif MBGL_USE_GLX +typedef struct _XDisplay Display; +typedef struct __GLXFBConfigRec* GLXFBConfig; +#endif namespace mbgl { diff --git a/include/mbgl/platform/default/offscreen_view.hpp b/include/mbgl/platform/default/offscreen_view.hpp new file mode 100644 index 000000000..034aa3aaf --- /dev/null +++ b/include/mbgl/platform/default/offscreen_view.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { + +namespace gl { +class Context; +} // namespace gl + +class OffscreenView : public View { +public: + OffscreenView(gl::Context&, std::array size = {{ 256, 256 }}); + + void bind() override; + + PremultipliedImage readStillImage(); + + std::array getSize() const; + +private: + gl::Context& context; + std::array size; + optional framebuffer; + optional> color; + optional> depthStencil; +}; + +} // namespace mbgl diff --git a/src/mbgl/actor/thread_pool.hpp b/include/mbgl/platform/default/thread_pool.hpp similarity index 100% rename from src/mbgl/actor/thread_pool.hpp rename to include/mbgl/platform/default/thread_pool.hpp diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp index eb4508858..9ca3cd4ab 100644 --- a/include/mbgl/util/projection.hpp +++ b/include/mbgl/util/projection.hpp @@ -2,20 +2,26 @@ #include #include +#include #include -#include namespace mbgl { +// Spherical Mercator projection +// http://docs.openlayers.org/library/spherical_mercator.html class Projection { - public: + // Map pixel width at given scale. + static double worldSize(double scale) { + return scale * util::tileSize; + } + static double getMetersPerPixelAtLatitude(double lat, double zoom) { const double constrainedZoom = util::clamp(zoom, util::MIN_ZOOM, util::MAX_ZOOM); - const double mapPixelWidthAtZoom = std::pow(2.0, constrainedZoom) * util::tileSize; + const double constrainedScale = std::pow(2.0, constrainedZoom); const double constrainedLatitude = util::clamp(lat, -util::LATITUDE_MAX, util::LATITUDE_MAX); - return std::cos(constrainedLatitude * util::DEG2RAD) * util::M2PI * util::EARTH_RADIUS_M / mapPixelWidthAtZoom; + return std::cos(constrainedLatitude * util::DEG2RAD) * util::M2PI * util::EARTH_RADIUS_M / worldSize(constrainedScale); } static ProjectedMeters projectedMetersForLatLng(const LatLng& latLng) { @@ -32,7 +38,7 @@ public: } static LatLng latLngForProjectedMeters(const ProjectedMeters& projectedMeters) { - double latitude = (2 * std::atan(std::exp(projectedMeters.northing / util::EARTH_RADIUS_M)) - (M_PI / 2)) * util::RAD2DEG; + double latitude = (2 * std::atan(std::exp(projectedMeters.northing / util::EARTH_RADIUS_M)) - (M_PI / 2.0)) * util::RAD2DEG; double longitude = projectedMeters.easting * util::RAD2DEG / util::EARTH_RADIUS_M; latitude = util::clamp(latitude, -util::LATITUDE_MAX, util::LATITUDE_MAX); @@ -40,6 +46,22 @@ public: return LatLng(latitude, longitude); } + + static Point project(const LatLng& latLng, double scale) { + return Point { + util::LONGITUDE_MAX + latLng.longitude, + util::LONGITUDE_MAX - util::RAD2DEG * std::log(std::tan(M_PI / 4 + latLng.latitude * M_PI / util::DEGREES_MAX)) + } * worldSize(scale) / util::DEGREES_MAX; + } + + static LatLng unproject(const Point& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) { + auto p2 = p * util::DEGREES_MAX / worldSize(scale); + return LatLng { + util::DEGREES_MAX / M_PI * std::atan(std::exp((util::LONGITUDE_MAX - p2.y) * util::DEG2RAD)) - 90.0, + p2.x - util::LONGITUDE_MAX, + wrapMode + }; + } }; } // namespace mbgl diff --git a/include/mbgl/util/run_loop.hpp b/include/mbgl/util/run_loop.hpp index 939531d6c..5236850d8 100644 --- a/include/mbgl/util/run_loop.hpp +++ b/include/mbgl/util/run_loop.hpp @@ -83,9 +83,7 @@ private: void schedule(std::weak_ptr mailbox) override { invoke([mailbox] () { - if (auto locked = mailbox.lock()) { - locked->receive(); - } + Mailbox::maybeReceive(mailbox); }); } diff --git a/package.json b/package.json index 61936e2fe..de2f6ff39 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lodash": "^4.16.4", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#98a56d538b11fb331aa67a6d632d6ecd6821b007", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#7f62a4fc9f21e619824d68abbc4b03cbc1685572", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#6df3b27868cc00432197e3fc9c166fdba7067913", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#844ecd23c3314eec509dbcdaf0f956d5504e6fef", "mkdirp": "^0.5.1", "node-cmake": "^1.2.1", "request": "^2.72.0", diff --git a/platform/android/CONTRIBUTING_MACOS.md b/platform/android/CONTRIBUTING_MACOS.md index 8343dab83..6ab55921e 100644 --- a/platform/android/CONTRIBUTING_MACOS.md +++ b/platform/android/CONTRIBUTING_MACOS.md @@ -1,15 +1,8 @@ # Contributing to the Android SDK on macOS -Install Oracle JDK 7+ and Android Studio: +## Install JDK 7+ and Android Studio: - brew tap caskroom/cask - brew install brew-cask - brew cask install java - - brew cask install android-studio - -You can [download Android Studio instead of installing it with Homebrew, but you'll still need to `brew install java`](https://developer.android.com/sdk/index.html) -for it to work. + Android Studio requires the Java Development Kit (JDK) which you can [download here](http://www.oracle.com/technetwork/java/javase/downloads/index.html). Once the JDK is installed, you can grab the [latest version of Android Studio here](https://developer.android.com/sdk/index.html) and install it following the on screen instructions. Once Android Studio is installed, the [first time it's run it'll ask to install the Android SDK](http://developer.android.com/sdk/installing/index.html?pkg=studio) which you should do. While doing so in the Android SDK Manager make sure to also select and install the latest versions of "Android Support Repository" and "Android Support Library" from "Extras": @@ -46,6 +39,15 @@ export ANDROID_HOME=//android-sdk-macosx This environment variable configuration should go into a file that's read on your shell's startup, like `~/.profile`. +## Speeding up gradle builds + +To optimise you development machine for faster gradle builds create the following `/Users/name/.gradle/gradle.properties` file: + +``` +org.gradle.daemon=true +org.gradle.jvmargs=-Xmx3072M +``` + ## Running The TestApp In order to run the TestApp on an emulator or device the Core GL portion needs to built first. Core GL is the common C++ based OpenGL engine that powers the maps for iOS, Android, and Qt in the project. To build it, open Terminal and run the following commands from the root of the `mapbox-gl-native` source code diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index 19c1370a6..6aab98529 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -20,6 +20,3 @@ ANDROID_BUILD_SDK_VERSION=23 POM_NAME=Mapbox Android SDK POM_ARTIFACT_ID=mapbox-android-sdk POM_PACKAGING=aar - -# allows gradle to use more memory -org.gradle.jvmargs=-Xmx2048M diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java index b41eebff0..175fd57da 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerView.java @@ -21,11 +21,14 @@ public class MarkerView extends Marker { private MarkerViewManager markerViewManager; + private float width; + private float height; + private float anchorU; private float anchorV; - private float offsetX = -1; - private float offsetY = -1; + private float offsetX = MapboxConstants.UNMEASURED; + private float offsetY = MapboxConstants.UNMEASURED; private float infoWindowAnchorU; private float infoWindowAnchorV; @@ -41,6 +44,7 @@ public class MarkerView extends Marker { private boolean selected; + /** * Publicly hidden default constructor */ @@ -64,6 +68,22 @@ public class MarkerView extends Marker { this.selected = baseMarkerViewOptions.selected; } + float getWidth() { + return width; + } + + void setWidth(float width) { + this.width = width; + } + + float getHeight() { + return height; + } + + void setHeight(float height) { + this.height = height; + } + /** * Specifies the anchor being set on a particular point point of the MarkerView. *

diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java index ded313071..cd333268a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/annotations/MarkerViewManager.java @@ -130,14 +130,18 @@ public class MarkerViewManager { PointF point = mapboxMap.getProjection().toScreenLocation(marker.getPosition()); if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { // ensure view is measured first - if (convertView.getWidth() == 0) { + if (marker.getWidth() == 0) { convertView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + if (convertView.getMeasuredWidth() != 0) { + marker.setWidth(convertView.getMeasuredWidth()); + marker.setHeight(convertView.getMeasuredHeight()); + } } - if (convertView.getMeasuredWidth() != 0) { - int x = (int) (marker.getAnchorU() * convertView.getMeasuredWidth()); - int y = (int) (marker.getAnchorV() * convertView.getMeasuredHeight()); - marker.setOffset(x, y); - } + } + if (marker.getWidth() != 0) { + int x = (int) (marker.getAnchorU() * marker.getWidth()); + int y = (int) (marker.getAnchorV() * marker.getHeight()); + marker.setOffset(x, y); } convertView.setX(point.x - marker.getOffsetX()); @@ -293,6 +297,17 @@ public class MarkerViewManager { return markerViewMap.get(marker); } + @Nullable + public MapboxMap.MarkerViewAdapter getViewAdapter(MarkerView markerView) { + MapboxMap.MarkerViewAdapter adapter = null; + for (MapboxMap.MarkerViewAdapter a : markerViewAdapters) { + if (a.getMarkerClass().equals(markerView.getClass())) { + adapter = a; + } + } + return adapter; + } + /** * Remove a MarkerView from a map. *

@@ -311,7 +326,7 @@ public class MarkerViewManager { if (adapter.getMarkerClass().equals(marker.getClass())) { if (adapter.prepareViewForReuse(marker, viewHolder)) { // reset offset for reuse - marker.setOffset(-1, -1); + marker.setOffset(MapboxConstants.UNMEASURED, MapboxConstants.UNMEASURED); adapter.releaseView(viewHolder); } } @@ -429,21 +444,6 @@ public class MarkerViewManager { } } - adaptedView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(final View v) { - boolean clickHandled = false; - if (onMarkerViewClickListener != null) { - clickHandled = onMarkerViewClickListener.onMarkerClick(marker, v, adapter); - } - - if (!clickHandled) { - ensureInfoWindowOffset(marker); - select(marker, v, adapter); - } - } - }); - marker.setMapboxMap(mapboxMap); markerViewMap.put(marker, adaptedView); if (convertView == null) { @@ -460,6 +460,26 @@ public class MarkerViewManager { update(); } + public void onClickMarkerView(MarkerView markerView) { + boolean clickHandled = false; + + MapboxMap.MarkerViewAdapter adapter = getViewAdapter(markerView); + View view = getView(markerView); + if (adapter == null || view == null) { + // not a valid state + return; + } + + if (onMarkerViewClickListener != null) { + clickHandled = onMarkerViewClickListener.onMarkerClick(markerView, view, adapter); + } + + if (!clickHandled) { + ensureInfoWindowOffset(markerView); + select(markerView, view, adapter); + } + } + //TODO: This whole method is a stopgap for: https://github.com/mapbox/mapbox-gl-native/issues/5384 public void ensureInfoWindowOffset(MarkerView marker) { View view = null; @@ -476,15 +496,19 @@ public class MarkerViewManager { } if (view != null) { - //Ensure the marker's view is measured first - if (view.getMeasuredWidth() == 0) { - view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + if (marker.getWidth() == 0) { + if(view.getMeasuredWidth()==0) { + //Ensure the marker's view is measured first + view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + } + marker.setWidth(view.getMeasuredWidth()); + marker.setHeight(view.getMeasuredHeight()); } // update position on map - if (marker.getOffsetX() == -1) { - int x = (int) (marker.getAnchorU() * view.getMeasuredWidth()); - int y = (int) (marker.getAnchorV() * view.getMeasuredHeight()); + if (marker.getOffsetX() == MapboxConstants.UNMEASURED) { + int x = (int) (marker.getAnchorU() * marker.getWidth()); + int y = (int) (marker.getAnchorV() * marker.getHeight()); marker.setOffset(x, y); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java index f65d012c4..3888abc04 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraPosition.java @@ -171,10 +171,10 @@ public final class CameraPosition implements Parcelable { public Builder(CameraUpdateFactory.CameraPositionUpdate update) { super(); if (update != null) { - bearing(update.getBearing()); - target(update.getTarget()); - tilt(update.getTilt()); - zoom(update.getZoom()); + bearing = update.getBearing(); + target = update.getTarget(); + tilt = update.getTilt(); + zoom = update.getZoom(); } } @@ -202,7 +202,7 @@ public final class CameraPosition implements Parcelable { super(); if (nativeCameraValues != null && nativeCameraValues.length == 5) { target(new LatLng(nativeCameraValues[0], nativeCameraValues[1])); - bearing(-nativeCameraValues[2]); + bearing(nativeCameraValues[2]); tilt(nativeCameraValues[3]); zoom((float) nativeCameraValues[4]); } @@ -217,7 +217,7 @@ public final class CameraPosition implements Parcelable { public Builder bearing(double bearing) { double direction = bearing; - while (direction > 360) { + while (direction >= 360) { direction -= 360; } while (direction < 0) { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java index b12dea643..db05486bc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/camera/CameraUpdateFactory.java @@ -200,16 +200,16 @@ public final class CameraUpdateFactory { private LatLngBounds bounds; private RectF padding; - public CameraBoundsUpdate(LatLngBounds bounds, RectF padding) { + CameraBoundsUpdate(LatLngBounds bounds, RectF padding) { this.bounds = bounds; this.padding = padding; } - public CameraBoundsUpdate(LatLngBounds bounds, int[] padding) { + CameraBoundsUpdate(LatLngBounds bounds, int[] padding) { this(bounds, new RectF(padding[0], padding[1], padding[2], padding[3])); } - public CameraBoundsUpdate(LatLngBounds bounds, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { + CameraBoundsUpdate(LatLngBounds bounds, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) { this(bounds, new int[]{paddingLeft, paddingTop, paddingRight, paddingBottom}); } @@ -283,7 +283,7 @@ public final class CameraUpdateFactory { private float x; private float y; - public CameraMoveUpdate(float x, float y) { + CameraMoveUpdate(float x, float y) { this.x = x; this.y = y; } @@ -315,14 +315,14 @@ public final class CameraUpdateFactory { @IntDef({ZOOM_IN, ZOOM_OUT, ZOOM_BY, ZOOM_TO, ZOOM_TO_POINT}) @Retention(RetentionPolicy.SOURCE) - public @interface Type { + @interface Type { } - public static final int ZOOM_IN = 0; - public static final int ZOOM_OUT = 1; - public static final int ZOOM_BY = 2; - public static final int ZOOM_TO = 3; - public static final int ZOOM_TO_POINT = 4; + static final int ZOOM_IN = 0; + static final int ZOOM_OUT = 1; + static final int ZOOM_BY = 2; + static final int ZOOM_TO = 3; + static final int ZOOM_TO_POINT = 4; @Type private final int type; @@ -364,7 +364,7 @@ public final class CameraUpdateFactory { return y; } - public double transformZoom(double currentZoom) { + double transformZoom(double currentZoom) { switch (getType()) { case CameraUpdateFactory.ZoomUpdate.ZOOM_IN: currentZoom++; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java index 10ebecee2..10d5c5504 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/constants/MapboxConstants.java @@ -115,6 +115,7 @@ public class MapboxConstants { public static final String STATE_COMPASS_MARGIN_TOP = "compassMarginTop"; public static final String STATE_COMPASS_MARGIN_RIGHT = "compassMarginRight"; public static final String STATE_COMPASS_MARGIN_BOTTOM = "compassMarginBottom"; + public static final String STATE_COMPASS_FADE_WHEN_FACING_NORTH = "compassFade"; public static final String STATE_LOGO_GRAVITY = "logoGravity"; public static final String STATE_LOGO_MARGIN_LEFT = "logoMarginLeft"; public static final String STATE_LOGO_MARGIN_TOP = "logoMarginTop"; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java new file mode 100644 index 000000000..3e1b14d64 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/CompassViewSettings.java @@ -0,0 +1,21 @@ +package com.mapbox.mapboxsdk.maps; + +/** + * Settings for the overlain views of a MapboxMap. Used by UiSettings. + */ +class CompassViewSettings extends ViewSettings{ + + private boolean fadeFacingNorth = true; + + public CompassViewSettings() { + super(); + } + + public boolean isFadeFacingNorth() { + return fadeFacingNorth; + } + + public void setFadeFacingNorth(boolean fadeFacingNorth) { + this.fadeFacingNorth = fadeFacingNorth; + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index 0735c4c19..32b327e8d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -18,7 +18,6 @@ import android.graphics.Canvas; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.SurfaceTexture; -import android.graphics.drawable.ColorDrawable; import android.location.Location; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -51,11 +50,9 @@ import android.view.TextureView; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.ListView; import android.widget.ZoomButtonsController; import com.almeros.android.multitouch.gesturedetectors.RotateGestureDetector; @@ -332,6 +329,7 @@ public class MapView extends FrameLayout { int tenDp = (int) getResources().getDimension(R.dimen.ten_dp); uiSettings.setCompassMargins(tenDp, tenDp, tenDp, tenDp); } + uiSettings.setCompassFadeFacingNorth(options.getCompassFadeFacingNorth()); // Logo uiSettings.setLogoEnabled(options.getLogoEnabled()); @@ -417,6 +415,7 @@ public class MapView extends FrameLayout { savedInstanceState.getInt(MapboxConstants.STATE_COMPASS_MARGIN_TOP), savedInstanceState.getInt(MapboxConstants.STATE_COMPASS_MARGIN_RIGHT), savedInstanceState.getInt(MapboxConstants.STATE_COMPASS_MARGIN_BOTTOM)); + uiSettings.setCompassFadeFacingNorth(savedInstanceState.getBoolean(MapboxConstants.STATE_COMPASS_FADE_WHEN_FACING_NORTH)); // Logo uiSettings.setLogoEnabled(savedInstanceState.getBoolean(MapboxConstants.STATE_LOGO_ENABLED)); @@ -554,6 +553,7 @@ public class MapView extends FrameLayout { outState.putInt(MapboxConstants.STATE_COMPASS_MARGIN_TOP, uiSettings.getCompassMarginTop()); outState.putInt(MapboxConstants.STATE_COMPASS_MARGIN_BOTTOM, uiSettings.getCompassMarginBottom()); outState.putInt(MapboxConstants.STATE_COMPASS_MARGIN_RIGHT, uiSettings.getCompassMarginRight()); + outState.putBoolean(MapboxConstants.STATE_COMPASS_FADE_WHEN_FACING_NORTH, uiSettings.isCompassFadeWhenFacingNorth()); // UiSettings - Logo outState.putInt(MapboxConstants.STATE_LOGO_GRAVITY, uiSettings.getLogoGravity()); @@ -1017,12 +1017,12 @@ public class MapView extends FrameLayout { icon = IconFactory.getInstance(getContext()).defaultMarker(); Bitmap bitmap = icon.getBitmap(); averageIconHeight = averageIconHeight + (bitmap.getHeight() / 2 - averageIconHeight) / iconSize; - averageIconWidth = averageIconHeight + (bitmap.getWidth() - averageIconHeight) / iconSize; + averageIconWidth = averageIconWidth + (bitmap.getWidth() - averageIconWidth) / iconSize; marker.setIcon(icon); } else { Bitmap bitmap = icon.getBitmap(); averageIconHeight = averageIconHeight + (bitmap.getHeight() - averageIconHeight) / iconSize; - averageIconWidth = averageIconHeight + (bitmap.getWidth() - averageIconHeight) / iconSize; + averageIconWidth = averageIconWidth + (bitmap.getWidth() - averageIconWidth) / iconSize; } if (!icons.contains(icon)) { @@ -1037,6 +1037,27 @@ public class MapView extends FrameLayout { return icon; } + Icon loadIconForMarkerView(MarkerView marker) { + Icon icon = marker.getIcon(); + int iconSize = icons.size() + 1; + if (icon == null) { + icon = IconFactory.getInstance(getContext()).defaultMarkerView(); + marker.setIcon(icon); + } + Bitmap bitmap = icon.getBitmap(); + averageIconHeight = averageIconHeight + (bitmap.getHeight() - averageIconHeight) / iconSize; + averageIconWidth = averageIconWidth + (bitmap.getWidth() - averageIconWidth) / iconSize; + if (!icons.contains(icon)) { + icons.add(icon); + } else { + Icon oldIcon = icons.get(icons.indexOf(icon)); + if (!oldIcon.getBitmap().sameAs(icon.getBitmap())) { + throw new IconBitmapChangedException(); + } + } + return icon; + } + void loadIcon(Icon icon) { if (destroyed) { return; @@ -1323,7 +1344,7 @@ public class MapView extends FrameLayout { return; } nativeMapView.cancelTransitions(); - nativeMapView.jumpTo(Math.toRadians(-bearing), center, Math.toRadians(pitch), zoom); + nativeMapView.jumpTo(bearing, center, pitch, zoom); } void easeTo(double bearing, LatLng center, long duration, double pitch, double zoom, boolean easingInterpolator, @Nullable final MapboxMap.CancelableCallback cancelableCallback) { @@ -1347,7 +1368,7 @@ public class MapView extends FrameLayout { }); } - nativeMapView.easeTo(Math.toRadians(-bearing), center, duration, Math.toRadians(pitch), zoom, easingInterpolator); + nativeMapView.easeTo(bearing, center, duration, pitch, zoom, easingInterpolator); } void flyTo(double bearing, LatLng center, long duration, double pitch, double zoom, @Nullable final MapboxMap.CancelableCallback cancelableCallback) { @@ -1371,7 +1392,7 @@ public class MapView extends FrameLayout { }); } - nativeMapView.flyTo(Math.toRadians(-bearing), center, duration, Math.toRadians(pitch), zoom); + nativeMapView.flyTo(bearing, center, duration, pitch, zoom); } private void adjustTopOffsetPixels() { @@ -1878,9 +1899,10 @@ public class MapView extends FrameLayout { if (annotation instanceof Marker) { if (annotation.getId() == newSelectedMarkerId) { if (selectedMarkers.isEmpty() || !selectedMarkers.contains(annotation)) { - // only handle click if no marker view is available if (!(annotation instanceof MarkerView)) { mapboxMap.selectMarker((Marker) annotation); + } else { + mapboxMap.getMarkerViewManager().onClickMarkerView((MarkerView) annotation); } } break; @@ -2717,6 +2739,10 @@ public class MapView extends FrameLayout { setWidgetMargins(compassView, left, top, right, bottom); } + void setCompassFadeFacingNorth(boolean compassFadeFacingNorth) { + compassView.fadeCompassViewFacingNorth(compassFadeFacingNorth); + } + // // Logo // @@ -2869,14 +2895,15 @@ public class MapView extends FrameLayout { // Called when someone presses the attribution icon @Override - public void onClick(View v) { - Context context = v.getContext(); - String[] items = context.getResources().getStringArray(R.array.attribution_names); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle); + public void onClick(View view) { + + AlertDialog.Builder builder = new AlertDialog.Builder(mapView.getContext(), R.style.TelemAlertDialogStyle); builder.setTitle(R.string.attributionsDialogTitle); - builder.setAdapter(new ArrayAdapter<>(context, R.layout.attribution_list_item, items), this); - AlertDialog dialog = builder.show(); - dialog.getWindow().setBackgroundDrawable(new ColorDrawable(mapView.getAttributionTintColor())); + String[] items = mapView.getContext().getResources().getStringArray(R.array.attribution_names); + builder.setAdapter(new ArrayAdapter<>(mapView.getContext(), R.layout.attribution_list_item, items), this); + AlertDialog attributionDialog = builder.show(); + + // TODO Change listview text color to mapView.getAttributionTintColor() } // Called when someone selects an attribution, 'Improve this map' adds location data to the url @@ -2884,45 +2911,38 @@ public class MapView extends FrameLayout { public void onClick(DialogInterface dialog, int which) { final Context context = ((Dialog) dialog).getContext(); if (which == ATTRIBUTION_INDEX_TELEMETRY_SETTINGS) { - - int array = R.array.attribution_telemetry_options; - if (MapboxEventManager.getMapboxEventManager().isTelemetryEnabled()) { - array = R.array.attribution_telemetry_options_already_participating; - } - String[] items = context.getResources().getStringArray(array); - AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AttributionAlertDialogStyle); + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.TelemAlertDialogStyle); builder.setTitle(R.string.attributionTelemetryTitle); - LayoutInflater factory = LayoutInflater.from(context); - View content = factory.inflate(R.layout.attribution_telemetry_view, null); - - ListView lv = (ListView) content.findViewById(R.id.telemetryOptionsList); - lv.setAdapter(new ArrayAdapter(context, R.layout.attribution_list_item, items)); - lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - - builder.setView(content); - final AlertDialog telemDialog = builder.show(); - lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + builder.setMessage(R.string.attributionTelemetryMessage); + builder.setPositiveButton(R.string.attributionTelemetryPositive, new DialogInterface.OnClickListener() { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - switch (position) { - case 0: - String url = context.getResources().getStringArray(R.array.attribution_links)[3]; - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - context.startActivity(intent); - telemDialog.cancel(); - return; - case 1: - MapboxEventManager.getMapboxEventManager().setTelemetryEnabled(false); - telemDialog.cancel(); - return; - case 2: - MapboxEventManager.getMapboxEventManager().setTelemetryEnabled(true); - telemDialog.cancel(); - return; - } + public void onClick(DialogInterface dialog, int which) { + MapboxEventManager.getMapboxEventManager().setTelemetryEnabled(true); + dialog.cancel(); } }); + builder.setNeutralButton(R.string.attributionTelemetryNeutral, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String url = context.getResources().getStringArray(R.array.attribution_links)[3]; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + context.startActivity(intent); + dialog.cancel(); + } + }); + builder.setNegativeButton(R.string.attributionTelemetryNegative, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + MapboxEventManager.getMapboxEventManager().setTelemetryEnabled(false); + dialog.cancel(); + } + }); + + AlertDialog telemDialog = builder.show(); + telemDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(mapView.getAttributionTintColor()); + telemDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(mapView.getAttributionTintColor()); + telemDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(mapView.getAttributionTintColor()); return; } String url = context.getResources().getStringArray(R.array.attribution_links)[which]; @@ -2977,162 +2997,180 @@ public class MapView extends FrameLayout { } /** - *

- * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing + * This event is triggered whenever the currently displayed map region is about to changing * without an animation. - *

*

- * This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends - * with {@link MapView#REGION_DID_CHANGE}. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int REGION_WILL_CHANGE = 0; /** - *

- * This {@link MapChange} is triggered whenever the currently displayed map region is about to changing + * This event is triggered whenever the currently displayed map region is about to changing * with an animation. - *

- *

- * This event is followed by a series of {@link MapView#REGION_IS_CHANGING} and ends - * with {@link MapView#REGION_DID_CHANGE_ANIMATED}. + *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int REGION_WILL_CHANGE_ANIMATED = 1; /** + * This event is triggered whenever the currently displayed map region is changing. *

- * This {@link MapChange} is triggered whenever the currently displayed map region is changing. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int REGION_IS_CHANGING = 2; /** - *

- * This {@link MapChange} is triggered whenever the currently displayed map region finished changing + * This event is triggered whenever the currently displayed map region finished changing * without an animation. + *

+ * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int REGION_DID_CHANGE = 3; /** - *

- * This {@link MapChange} is triggered whenever the currently displayed map region finished changing + * This event is triggered whenever the currently displayed map region finished changing * with an animation. + *

+ * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int REGION_DID_CHANGE_ANIMATED = 4; /** + * This event is triggered when the map is about to start loading a new map style. *

- * This {@link MapChange} is triggered when the map is about to start loading a new map style. - *

- *

- * This event is followed by {@link MapView#DID_FINISH_LOADING_MAP} or - * {@link MapView#DID_FAIL_LOADING_MAP}. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int WILL_START_LOADING_MAP = 5; /** + * This is triggered when the map has successfully loaded a new map style. *

- * This {@link MapChange} is triggered when the map has successfully loaded a new map style. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_LOADING_MAP = 6; /** - *

- * This {@link MapChange} is currently not implemented. - *

- *

* This event is triggered when the map has failed to load a new map style. + *

+ * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FAIL_LOADING_MAP = 7; /** + * This event is triggered when the map will start rendering a frame. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int WILL_START_RENDERING_FRAME = 8; /** + * This event is triggered when the map finished rendering a frame. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_FRAME = 9; /** + * This event is triggered when the map finished rendeirng the frame fully. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_FRAME_FULLY_RENDERED = 10; /** + * This event is triggered when the map will start rendering the map. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int WILL_START_RENDERING_MAP = 11; /** + * This event is triggered when the map finished rendering the map. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_MAP = 12; /** + * This event is triggered when the map is fully rendered. *

- * This {@link MapChange} is currently not implemented. + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_RENDERING_MAP_FULLY_RENDERED = 13; /** + * This {@link MapChange} is triggered when a style has finished loading. *

- * This {@link MapChange} is triggered when a style is loaded + * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. *

* + * @see MapChange * @see MapView.OnMapChangedListener */ public static final int DID_FINISH_LOADING_STYLE = 14; /** * Interface definition for a callback to be invoked when the displayed map view changes. + *

+ * Register to {@link MapChange} events with {@link MapView#addOnMapChangedListener(OnMapChangedListener)}. + *

* * @see MapView#addOnMapChangedListener(OnMapChangedListener) * @see MapView.MapChange diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index ea4bb4eaf..b1f0984e3 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -22,7 +22,6 @@ import com.mapbox.mapboxsdk.annotations.Annotation; import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; import com.mapbox.mapboxsdk.annotations.BaseMarkerViewOptions; import com.mapbox.mapboxsdk.annotations.Icon; -import com.mapbox.mapboxsdk.annotations.IconFactory; import com.mapbox.mapboxsdk.annotations.InfoWindow; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.MarkerOptions; @@ -389,15 +388,16 @@ public class MapboxMap { */ @UiThread public final void moveCamera(CameraUpdate update, MapboxMap.CancelableCallback callback) { - // dismiss tracking, moving camera is equal to a gesture - cameraPosition = update.getCameraPosition(this); mapView.resetTrackingModesIfRequired(cameraPosition); mapView.jumpTo(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom); if (callback != null) { callback.onFinish(); } - invalidateCameraPosition(); + + if (onCameraChangeListener != null) { + onCameraChangeListener.onCameraChange(this.cameraPosition); + } } /** @@ -560,8 +560,6 @@ public class MapboxMap { */ @UiThread public final void animateCamera(CameraUpdate update, int durationMs, final MapboxMap.CancelableCallback callback) { - // dismiss tracking, moving camera is equal to a gesture - cameraPosition = update.getCameraPosition(this); mapView.resetTrackingModesIfRequired(cameraPosition); mapView.flyTo(cameraPosition.bearing, cameraPosition.target, getDurationNano(durationMs), cameraPosition.tilt, @@ -602,15 +600,17 @@ public class MapboxMap { * Invalidates the current camera position by reconstructing it from mbgl */ private void invalidateCameraPosition() { - invalidCameraPosition = false; + if(invalidCameraPosition) { + invalidCameraPosition = false; - CameraPosition cameraPosition = mapView.invalidateCameraPosition(); - if (cameraPosition != null) { - this.cameraPosition = cameraPosition; - } + CameraPosition cameraPosition = mapView.invalidateCameraPosition(); + if (cameraPosition != null) { + this.cameraPosition = cameraPosition; + } - if (onCameraChangeListener != null) { - onCameraChangeListener.onCameraChange(this.cameraPosition); + if (onCameraChangeListener != null) { + onCameraChangeListener.onCameraChange(this.cameraPosition); + } } } @@ -1404,12 +1404,7 @@ public class MapboxMap { private MarkerView prepareViewMarker(BaseMarkerViewOptions markerViewOptions) { MarkerView marker = markerViewOptions.getMarker(); - - Icon icon = markerViewOptions.getIcon(); - if (icon == null) { - icon = IconFactory.getInstance(mapView.getContext()).defaultMarkerView(); - } - marker.setIcon(icon); + mapView.loadIconForMarkerView(marker); return marker; } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java index 352b3ad12..a54d24691 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMapOptions.java @@ -44,6 +44,7 @@ public class MapboxMapOptions implements Parcelable { private boolean debugActive; private boolean compassEnabled = true; + private boolean fadeCompassFacingNorth = true; private int compassGravity = Gravity.TOP | Gravity.END; private int[] compassMargins; @@ -98,6 +99,7 @@ public class MapboxMapOptions implements Parcelable { compassEnabled = in.readByte() != 0; compassGravity = in.readInt(); compassMargins = in.createIntArray(); + fadeCompassFacingNorth = in.readByte() != 0; logoEnabled = in.readByte() != 0; logoGravity = in.readInt(); @@ -193,6 +195,7 @@ public class MapboxMapOptions implements Parcelable { , ((int) typedArray.getDimension(R.styleable.MapView_compass_margin_top, DIMENSION_TEN_DP * screenDensity)) , ((int) typedArray.getDimension(R.styleable.MapView_compass_margin_right, DIMENSION_TEN_DP * screenDensity)) , ((int) typedArray.getDimension(R.styleable.MapView_compass_margin_bottom, DIMENSION_TEN_DP * screenDensity))}); + mapboxMapOptions.compassFadesWhenFacingNorth(typedArray.getBoolean(R.styleable.MapView_compass_fade_facing_north, true)); mapboxMapOptions.logoEnabled(typedArray.getBoolean(R.styleable.MapView_logo_enabled, true)); mapboxMapOptions.logoGravity(typedArray.getInt(R.styleable.MapView_logo_gravity, Gravity.BOTTOM | Gravity.START)); @@ -358,6 +361,20 @@ public class MapboxMapOptions implements Parcelable { return this; } + /** + * Specifies if the compass fades to invisible when facing north. + *

+ * By default this value is true. + *

+ * + * @param compassFadeWhenFacingNorth true is compass fades to invisble + * @return This + */ + public MapboxMapOptions compassFadesWhenFacingNorth(boolean compassFadeWhenFacingNorth) { + this.fadeCompassFacingNorth = compassFadeWhenFacingNorth; + return this; + } + /** * Specifies the visibility state of a logo for a map view. * @@ -679,6 +696,15 @@ public class MapboxMapOptions implements Parcelable { return compassMargins; } + /** + * Get the current configured state for fading the compass when facing north. + * + * @return True if compass fades to invisible when facing north + */ + public boolean getCompassFadeFacingNorth() { + return fadeCompassFacingNorth; + } + /** * Get the current configured visibility state for compass for a map view. * @@ -934,6 +960,7 @@ public class MapboxMapOptions implements Parcelable { dest.writeByte((byte) (compassEnabled ? 1 : 0)); dest.writeInt(compassGravity); dest.writeIntArray(compassMargins); + dest.writeByte((byte) (fadeCompassFacingNorth ? 1 : 0)); dest.writeByte((byte) (logoEnabled ? 1 : 0)); dest.writeInt(logoGravity); @@ -979,6 +1006,7 @@ public class MapboxMapOptions implements Parcelable { if (debugActive != options.debugActive) return false; if (compassEnabled != options.compassEnabled) return false; + if (fadeCompassFacingNorth != options.fadeCompassFacingNorth) return false; if (compassGravity != options.compassGravity) return false; if (logoEnabled != options.logoEnabled) return false; if (logoGravity != options.logoGravity) return false; @@ -1022,6 +1050,7 @@ public class MapboxMapOptions implements Parcelable { int result = cameraPosition != null ? cameraPosition.hashCode() : 0; result = 31 * result + (debugActive ? 1 : 0); result = 31 * result + (compassEnabled ? 1 : 0); + result = 31 * result + (fadeCompassFacingNorth ? 1 : 0); result = 31 * result + compassGravity; result = 31 * result + Arrays.hashCode(compassMargins); result = 31 * result + (logoEnabled ? 1 : 0); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 7753787fd..4e0ce33c5 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -9,12 +9,14 @@ import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.DisplayMetrics; +import android.util.Log; import android.view.Surface; import com.mapbox.mapboxsdk.annotations.Icon; import com.mapbox.mapboxsdk.annotations.Marker; import com.mapbox.mapboxsdk.annotations.Polygon; import com.mapbox.mapboxsdk.annotations.Polyline; +import com.mapbox.mapboxsdk.constants.MapboxConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.ProjectedMeters; import com.mapbox.mapboxsdk.offline.OfflineManager; @@ -151,13 +153,17 @@ final class NativeMapView { } if (width > 65535) { - throw new IllegalArgumentException( - "width cannot be greater than 65535."); + // we have seen edge cases where devices return incorrect values #6111 + Log.e(MapboxConstants.TAG, "Device returned an out of range width size, " + + "capping value at 65535 instead of " + width); + width = 65535; } if (height > 65535) { - throw new IllegalArgumentException( - "height cannot be greater than 65535."); + // we have seen edge cases where devices return incorrect values #6111 + Log.e(MapboxConstants.TAG, "Device returned an out of range height size, " + + "capping value at 65535 instead of " + height); + height = 65535; } nativeViewResize(nativeMapViewPtr, width, height); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java index a06f44930..d6ad80d11 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java @@ -15,7 +15,7 @@ public class UiSettings { private MapView mapView; - private ViewSettings compassSettings; + private CompassViewSettings compassSettings; private ViewSettings logoSettings; private ViewSettings attributionSettings; @@ -39,7 +39,7 @@ public class UiSettings { UiSettings(@NonNull MapView mapView) { this.mapView = mapView; - this.compassSettings = new ViewSettings(); + this.compassSettings = new CompassViewSettings(); this.logoSettings = new ViewSettings(); this.attributionSettings = new ViewSettings(); } @@ -85,6 +85,28 @@ public class UiSettings { mapView.setCompassGravity(gravity); } + /** + * Enables or disables fading of the compass when facing north. + *

+ * By default this feature is enabled + *

+ * + * @param compassFadeFacingNorth True to enable the fading animation; false to disable it + */ + public void setCompassFadeFacingNorth(boolean compassFadeFacingNorth) { + compassSettings.setFadeFacingNorth(compassFadeFacingNorth); + mapView.setCompassFadeFacingNorth(compassFadeFacingNorth); + } + + /** + * Returns whether the compass performs a fading animation out when facing north. + * + * @return True if the compass will fade, false if it remains visible + */ + public boolean isCompassFadeWhenFacingNorth(){ + return compassSettings.isFadeFacingNorth(); + } + /** * Returns the gravity value of the CompassView * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index e469eb14a..163c7a610 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -15,18 +15,24 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.maps.MapboxMap; import java.lang.ref.WeakReference; -import java.util.Timer; -import java.util.TimerTask; /** - * UI element overlaid on a map to show the map's bearing - * when it isn't true north (0.0). Tapping the compass resets the bearing to true - * north and hides the compass. + * UI element overlaid on a map to show the map's bearing when it isn't true north (0.0). Tapping + * the compass resets the bearing to true north and hides the compass. + *

+ * You can change the behaviour of this View during initialisation with + * {@link com.mapbox.mapboxsdk.maps.MapboxMapOptions}, and xml attributes. While running you can + * use {@link com.mapbox.mapboxsdk.maps.UiSettings}. + *

*/ -public final class CompassView extends ImageView { +public final class CompassView extends ImageView implements Runnable { - private Timer northTimer; - private double direction = 0.0f; + private static final long TIME_WAIT_IDLE = 500; + private static final long TIME_FADE_ANIMATION = TIME_WAIT_IDLE; + private static final long TIME_MAP_NORTH_ANIMATION = 150; + + private double direction = 0.0; + private boolean fadeCompassViewFacingNorth = true; private ViewPropertyAnimatorCompat fadeAnimator; public CompassView(Context context) { @@ -45,8 +51,6 @@ public final class CompassView extends ImageView { } private void initialize(Context context) { - - // View configuration setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.compass)); setContentDescription(getResources().getString(R.string.compassContentDescription)); setEnabled(false); @@ -57,105 +61,98 @@ public final class CompassView extends ImageView { setLayoutParams(lp); } - public void setMapboxMap(@NonNull MapboxMap mapboxMap){ - setOnClickListener(new CompassClickListener(mapboxMap)); + public void setMapboxMap(@NonNull MapboxMap mapboxMap) { + setOnClickListener(new CompassClickListener(mapboxMap, this)); + } + + private void resetAnimation() { + if (fadeAnimator != null) { + fadeAnimator.cancel(); + } + fadeAnimator = null; + } + + public boolean isHidden() { + return fadeCompassViewFacingNorth && isFacingNorth(); + } + + public boolean isFacingNorth() { + // increase range more than just 0.0 + return direction >= 359.0 || direction <= 1.0; } @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - if (enabled) { - if (direction != 0.0) { - if (northTimer != null) { - northTimer.cancel(); - northTimer = null; - } - if (fadeAnimator != null) { - fadeAnimator.cancel(); - } - fadeAnimator = null; - setAlpha(1.0f); - setVisibility(View.VISIBLE); - } + if (enabled && !isHidden()) { + resetAnimation(); + setAlpha(1.0f); + setVisibility(View.VISIBLE); } else { - if (northTimer != null) { - northTimer.cancel(); - northTimer = null; - } - if (fadeAnimator != null) { - fadeAnimator.cancel(); - } - fadeAnimator = null; + resetAnimation(); + setAlpha(0.0f); setVisibility(View.INVISIBLE); } } - public void update(double direction) { + public void update(final double direction) { this.direction = direction; - setRotation((float) direction); if (!isEnabled()) { return; } - if (direction == 0.0) { - if (getVisibility() == View.INVISIBLE) { + if (isHidden()) { + if (getVisibility() == View.INVISIBLE || fadeAnimator != null) { return; } - - if (northTimer == null) { - if (fadeAnimator != null) { - fadeAnimator.cancel(); - } - fadeAnimator = null; - - northTimer = new Timer("CompassView North timer"); - northTimer.schedule(new TimerTask() { - @Override - public void run() { - post(new Runnable() { - @Override - public void run() { - setAlpha(1.0f); - fadeAnimator = ViewCompat.animate(CompassView.this).alpha(0.0f).setDuration(1000).withLayer(); - fadeAnimator.setListener(new ViewPropertyAnimatorListenerAdapter() { - @Override - public void onAnimationEnd(View view) { - setVisibility(View.INVISIBLE); - northTimer = null; - } - }); - } - }); - } - }, 1000); - } + postDelayed(this, TIME_WAIT_IDLE); + return; } else { - if (northTimer != null) { - northTimer.cancel(); - northTimer = null; - } - if (fadeAnimator != null) { - fadeAnimator.cancel(); - } + resetAnimation(); setAlpha(1.0f); setVisibility(View.VISIBLE); } + + setRotation((float) direction); + } + + public void fadeCompassViewFacingNorth(boolean compassFadeFacingNorth) { + fadeCompassViewFacingNorth = compassFadeFacingNorth; + } + + @Override + public void run() { + if (isFacingNorth() && fadeCompassViewFacingNorth) { + resetAnimation(); + fadeAnimator = ViewCompat.animate(CompassView.this).alpha(0.0f).setDuration(TIME_FADE_ANIMATION).withLayer(); + fadeAnimator.setListener(new ViewPropertyAnimatorListenerAdapter() { + @Override + public void onAnimationEnd(View view) { + setVisibility(View.INVISIBLE); + resetAnimation(); + } + }); + } } static class CompassClickListener implements View.OnClickListener { private WeakReference mapboxMap; + private WeakReference compassView; - public CompassClickListener(final MapboxMap mapboxMap) { + CompassClickListener(final MapboxMap mapboxMap, CompassView compassView) { this.mapboxMap = new WeakReference<>(mapboxMap); + this.compassView = new WeakReference<>(compassView); } @Override public void onClick(View view) { final MapboxMap mapboxMap = this.mapboxMap.get(); - if (mapboxMap != null) { + final CompassView compassView = this.compassView.get(); + if (mapboxMap != null && compassView != null) { mapboxMap.resetNorth(); + compassView.postDelayed(compassView, TIME_WAIT_IDLE + TIME_MAP_NORTH_ANIMATION); } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java index 114bf5f3c..45e0c4903 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/MyLocationView.java @@ -55,13 +55,9 @@ public class MyLocationView extends View { private float contentPaddingX; private float contentPaddingY; - private Location location; private LatLng latLng; - private LatLng interpolatedLocation; - private LatLng previousLocation; + private Location location; private long locationUpdateTimestamp; - - private float gpsDirection; private float previousDirection; private float accuracy; @@ -454,27 +450,29 @@ public class MyLocationView extends View { compassListener.onPause(); if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) { // always face north - gpsDirection = bearing; - setCompass(gpsDirection); + setCompass(0); } } invalidate(); - update(); } public void setMyLocationTrackingMode(@MyLocationTracking.Mode int myLocationTrackingMode) { - MyLocationBehaviorFactory factory = new MyLocationBehaviorFactory(); myLocationBehavior = factory.getBehavioralModel(myLocationTrackingMode); - if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW && location != null) { - // center map directly if we have a location fix + if (location != null) { + if (myLocationTrackingMode == MyLocationTracking.TRACKING_FOLLOW) { + // center map directly + mapboxMap.easeCamera(CameraUpdateFactory.newLatLng(new LatLng(location)), 0, false /*linear interpolator*/, false /*do not disable tracking*/, null); + } else { + // do not use interpolated location from tracking mode + latLng = null; + } myLocationBehavior.updateLatLng(location); - mapboxMap.easeCamera(CameraUpdateFactory.newLatLng(new LatLng(location)), 0, false /*linear interpolator*/, false /*do not disable tracking*/, null); } + this.myLocationTrackingMode = myLocationTrackingMode; invalidate(); - update(); } private void setCompass(float bearing) { @@ -641,7 +639,9 @@ public class MyLocationView extends View { private abstract class MyLocationBehavior { - abstract void updateLatLng(@NonNull Location location); + void updateLatLng(@NonNull Location newLocation) { + location = newLocation; + } void updateLatLng(double lat, double lon) { if (latLng != null) { @@ -670,6 +670,7 @@ public class MyLocationView extends View { @Override void updateLatLng(@NonNull Location location) { + super.updateLatLng(location); if (latLng == null) { // first location fix latLng = new LatLng(location); @@ -677,39 +678,34 @@ public class MyLocationView extends View { } // updateLatLng timestamp - long previousUpdateTimeStamp = locationUpdateTimestamp; + float previousUpdateTimeStamp = locationUpdateTimestamp; locationUpdateTimestamp = SystemClock.elapsedRealtime(); // calculate animation duration - long locationUpdateDuration; + float animationDuration; if (previousUpdateTimeStamp == 0) { - locationUpdateDuration = 0; + animationDuration = 0; } else { - locationUpdateDuration = locationUpdateTimestamp - previousUpdateTimeStamp; + animationDuration = (locationUpdateTimestamp - previousUpdateTimeStamp) * 1.1f /*make animation slightly longer*/; } // calculate interpolated location - previousLocation = latLng; latLng = new LatLng(location); - interpolatedLocation = new LatLng(latLng.getLatitude() , latLng.getLongitude()); - - // build new camera - CameraPosition.Builder builder = new CameraPosition.Builder().target(interpolatedLocation); + CameraPosition.Builder builder = new CameraPosition.Builder().target(latLng); // add direction if (myBearingTrackingMode == MyBearingTracking.GPS) { if (location.hasBearing()) { builder.bearing(location.getBearing()); } - gpsDirection = location.getBearing(); - setCompass(gpsDirection); + setCompass(0); } // accuracy updateAccuracy(location); // ease to new camera position with a linear interpolator - mapboxMap.easeCamera(CameraUpdateFactory.newCameraPosition(builder.build()), (int) locationUpdateDuration, false /*linear interpolator*/, false /*do not disable tracking*/, null); + mapboxMap.easeCamera(CameraUpdateFactory.newCameraPosition(builder.build()), (int) animationDuration, false /*linear interpolator*/, false /*do not disable tracking*/, null); } @Override @@ -726,6 +722,7 @@ public class MyLocationView extends View { @Override void updateLatLng(@NonNull final Location location) { + super.updateLatLng(location); if (latLng == null) { // first location update latLng = new LatLng(location); @@ -733,13 +730,11 @@ public class MyLocationView extends View { } // update LatLng location - previousLocation = latLng; - latLng = new LatLng(location); + LatLng newLocation = new LatLng(location); // update LatLng direction if (myBearingTrackingMode == MyBearingTracking.GPS && location.hasBearing()) { - gpsDirection = location.getBearing(); - setCompass(gpsDirection); + setCompass(location.getBearing() + bearing); } // update LatLng accuracy @@ -748,10 +743,7 @@ public class MyLocationView extends View { // calculate updateLatLng time + add some extra offset to improve animation long previousUpdateTimeStamp = locationUpdateTimestamp; locationUpdateTimestamp = SystemClock.elapsedRealtime(); - long locationUpdateDuration = (long) ((locationUpdateTimestamp - previousUpdateTimeStamp) * 1.3); - - // calculate interpolated entity - interpolatedLocation = new LatLng(latLng.getLatitude() , latLng.getLongitude()); + long locationUpdateDuration = (long) ((locationUpdateTimestamp - previousUpdateTimeStamp) * 1.2f); // animate changes if (locationChangeAnimator != null) { @@ -760,14 +752,12 @@ public class MyLocationView extends View { } locationChangeAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); - locationChangeAnimator.setDuration((long) (locationUpdateDuration * 1.2)); + locationChangeAnimator.setDuration(locationUpdateDuration); locationChangeAnimator.addUpdateListener(new MarkerCoordinateAnimatorListener(this, - previousLocation, interpolatedLocation + latLng, newLocation )); locationChangeAnimator.start(); - - // use interpolated location as current location - latLng = interpolatedLocation; + latLng = newLocation; } @Override diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/package-info.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/package-info.java new file mode 100644 index 000000000..1b207820c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/net/package-info.java @@ -0,0 +1,4 @@ +/** + * Contains the Mapbox Maps Android Network API classes. + */ +package com.mapbox.mapboxsdk.net; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java index 7d2229260..bedb54ca4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/telemetry/MapboxEventManager.java @@ -65,6 +65,7 @@ public class MapboxEventManager { private static MapboxEventManager mapboxEventManager = null; private boolean initialized = false; + private boolean stagingEnv; private boolean telemetryEnabled; private final Vector> events = new Vector<>(); @@ -173,12 +174,10 @@ public class MapboxEventManager { stagingAccessToken = prefs.getString(MapboxConstants.MAPBOX_SHARED_PREFERENCE_KEY_TELEMETRY_STAGING_ACCESS_TOKEN, null); } - if (!TextUtils.isEmpty(stagingURL)) { + if (!TextUtils.isEmpty(stagingURL) && !TextUtils.isEmpty(stagingAccessToken)) { eventsURL = stagingURL; - } - - if (!TextUtils.isEmpty(stagingAccessToken)) { - this.accessToken = stagingAccessToken; + this.accessToken = accessToken; + stagingEnv = true; } // Build User Agent @@ -715,29 +714,32 @@ public class MapboxEventManager { } // Based on http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html - CertificatePinner certificatePinner = new CertificatePinner.Builder() - // Staging - Geotrust - .add("cloudfront-staging.tilestream.net", "sha256/kR9ysyN/lzBl/ecearDERV7qO7xqSN4jt6XuQjIVL0I=") - .add("cloudfront-staging.tilestream.net", "sha256/sPbNCVpVasMJxps3IqFfLTRKkVnRCLrTlZVc5kspqlkw=") - .add("cloudfront-staging.tilestream.net", "sha256/h6801m+z8v3zbgkRHpq6L29Esgfzhj89C1SyUCOQmqU=") - // Prod - Geotrust - .add("events.mapbox.com", "sha256/BhynraKizavqoC5U26qgYuxLZst6pCu9J5stfL6RSYY=") - .add("events.mapbox.com", "sha256/owrR9U9FWDWtrFF+myoRIu75JwU4sJwzvhCNLZoY37g=") - .add("events.mapbox.com", "sha256/SQVGZiOrQXi+kqxcvWWE96HhfydlLVqFr4lQTqI5qqo=") - // Prod - DigiCert - .add("events.mapbox.com", "sha256/Tb0uHZ/KQjWh8N9+CZFLc4zx36LONQ55l6laDi1qtT4=") - .add("events.mapbox.com", "sha256/RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=") - .add("events.mapbox.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=") - .build(); + CertificatePinner.Builder certificatePinnerBuilder = new CertificatePinner.Builder(); + if(stagingEnv){ + // Staging - Geotrust + certificatePinnerBuilder + .add("cloudfront-staging.tilestream.net", "sha256/3euxrJOrEZI15R4104UsiAkDqe007EPyZ6eTL/XxdAY=") + .add("cloudfront-staging.tilestream.net", "sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=") + .add("cloudfront-staging.tilestream.net", "sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="); + }else{ + certificatePinnerBuilder + // Prod - Geotrust + .add("events.mapbox.com", "sha256/BhynraKizavqoC5U26qgYuxLZst6pCu9J5stfL6RSYY=") + .add("events.mapbox.com", "sha256/owrR9U9FWDWtrFF+myoRIu75JwU4sJwzvhCNLZoY37g=") + .add("events.mapbox.com", "sha256/SQVGZiOrQXi+kqxcvWWE96HhfydlLVqFr4lQTqI5qqo=") + // Prod - DigiCert + .add("events.mapbox.com", "sha256/Tb0uHZ/KQjWh8N9+CZFLc4zx36LONQ55l6laDi1qtT4=") + .add("events.mapbox.com", "sha256/RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=") + .add("events.mapbox.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="); + } OkHttpClient client = new OkHttpClient.Builder() - .certificatePinner(certificatePinner) + .certificatePinner(certificatePinnerBuilder.build()) .addInterceptor(new GzipRequestInterceptor()) .build(); RequestBody body = RequestBody.create(JSON, jsonArray.toString()); String url = eventsURL + "/events/v2?access_token=" + accessToken; -// Log.d(TAG, "Events URL = " + url); Request request = new Request.Builder() .url(url) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml index 777d879d4..4fbf3d5fe 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res-public/values/public.xml @@ -29,6 +29,7 @@ + diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml index 1252fb194..763bb118e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_list_item.xml @@ -1,11 +1,13 @@ - + android:paddingLeft="24dp" + android:paddingRight="24dp" + android:textAllCaps="true" + android:textAppearance="?android:attr/textAppearanceButton" + android:textColor="@color/mapbox_blue"/> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml deleted file mode 100644 index b4d232683..000000000 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/layout/attribution_telemetry_view.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml index d5d26d09d..bb466880a 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/arrays.xml @@ -6,16 +6,6 @@ Improve this map Telemetry Settings
- - Tell Me More - Don\'t Participate - Participate - - - Tell Me More - Stop Participating - Keep Participating - https://www.mapbox.com/about/maps/ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml index f0740a4e5..458056c14 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/attrs.xml @@ -64,6 +64,7 @@ + diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml index 687b85b2d..c8bb9d65b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/strings.xml @@ -5,6 +5,9 @@ Mapbox Android SDK Make Mapbox Maps Better You are helping to make OpenStreetMap and Mapbox maps better by contributing anonymous usage data. + Agree + Disagree + More info The Mapbox logo. Title Description diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml index a46f58ae8..708cfc72e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values/styles.xml @@ -6,4 +6,6 @@ @android:color/white +