diff options
135 files changed, 3245 insertions, 649 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 64dde928c5..530461d1fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ mason_use(kdbush VERSION 0.1.1-1 HEADER_ONLY) mason_use(earcut VERSION 0.12.3 HEADER_ONLY) mason_use(protozero VERSION 1.5.2 HEADER_ONLY) mason_use(pixelmatch VERSION 0.10.0 HEADER_ONLY) -mason_use(geojson VERSION 0.4.0 HEADER_ONLY) +mason_use(geojson VERSION 0.4.2 HEADER_ONLY) mason_use(polylabel VERSION 1.0.3 HEADER_ONLY) mason_use(wagyu VERSION 0.4.3 HEADER_ONLY) mason_use(shelf-pack VERSION 2.1.1 HEADER_ONLY) @@ -108,7 +108,7 @@ run-test-%: test run-benchmark: run-benchmark-. run-benchmark-%: benchmark - $(MACOS_OUTPUT_PATH)/$(BUILDTYPE)/mbgl-benchmark --benchmark_filter=$* + $(MACOS_OUTPUT_PATH)/$(BUILDTYPE)/mbgl-benchmark --benchmark_filter=$* ${BENCHMARK_ARGS} .PHONY: node-benchmark node-benchmark: $(MACOS_PROJ_PATH) @@ -440,6 +440,12 @@ test-node: node npm test npm run test-suite +.PHONY: test-node-recycle-map +test-node-recycle-map: node + npm test + npm run test-render -- --recycle-map + npm run test-query + #### Android targets ########################################################### MBGL_ANDROID_ABIS = arm-v5;armeabi @@ -588,6 +594,7 @@ run-android-ui-test-aws: platform/android/configuration.gradle # Builds a release package of the Android SDK .PHONY: apackage apackage: platform/android/configuration.gradle + make android-lib-arm-v5 && make android-lib-arm-v7 && make android-lib-arm-v8 && make android-lib-x86 && make android-lib-x86-64 && make android-lib-mips cd platform/android && $(MBGL_ANDROID_GRADLE) -Pmapbox.abis=all assemble$(BUILDTYPE) # Uploads the compiled Android SDK to Maven diff --git a/benchmark/function/camera_function.benchmark.cpp b/benchmark/function/camera_function.benchmark.cpp new file mode 100644 index 0000000000..1f8fe4579f --- /dev/null +++ b/benchmark/function/camera_function.benchmark.cpp @@ -0,0 +1,69 @@ +#include <benchmark/benchmark.h> + +#include <mbgl/style/function/source_function.hpp> + +#include <mbgl/style/rapidjson_conversion.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/function.hpp> + +#include <rapidjson/document.h> + + +using namespace mbgl; +using namespace mbgl::style; + +static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) { + rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc; + + std::string stops = "["; + for (size_t i = 0; i < stopCount; i++) { + std::string value = std::to_string(24.0f / stopCount * i); + if (stops.size() > 1) stops += ","; + stops += "[" + value + ", " + value + "]"; + } + stops += "]"; + + doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + "}"); + return doc; +} + +static void Parse_CameraFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + + while (state.KeepRunning()) { + conversion::Error error; + state.PauseTiming(); + auto doc = createFunctionJSON(stopCount); + state.ResumeTiming(); + optional<CameraFunction<float>> result = conversion::convert<CameraFunction<float>, JSValue>(doc, error); + if (!result) { + state.SkipWithError(error.message.c_str()); + } + } + state.SetLabel(std::to_string(stopCount).c_str()); +} + +static void Evaluate_CameraFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + auto doc = createFunctionJSON(stopCount); + conversion::Error error; + optional<CameraFunction<float>> function = conversion::convert<CameraFunction<float>, JSValue>(doc, error); + if (!function) { + state.SkipWithError(error.message.c_str()); + } + + while(state.KeepRunning()) { + float z = 24.0f * static_cast<float>(rand() % 100) / 100; + function->evaluate(z); + } + + state.SetLabel(std::to_string(stopCount).c_str()); +} + +BENCHMARK(Parse_CameraFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + +BENCHMARK(Evaluate_CameraFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + + diff --git a/benchmark/function/composite_function.benchmark.cpp b/benchmark/function/composite_function.benchmark.cpp new file mode 100644 index 0000000000..f04b6c7073 --- /dev/null +++ b/benchmark/function/composite_function.benchmark.cpp @@ -0,0 +1,76 @@ +#include <benchmark/benchmark.h> + +#include <mbgl/benchmark/stub_geometry_tile_feature.hpp> + +#include <mbgl/style/function/composite_exponential_stops.hpp> +#include <mbgl/style/function/composite_function.hpp> + +#include <mbgl/style/rapidjson_conversion.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/function.hpp> + +#include <rapidjson/document.h> + + +using namespace mbgl; +using namespace mbgl::style; + +static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) { + rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc; + + std::string stops = "["; + for (size_t outerStop = 0; outerStop < stopCount; outerStop++) { + for (size_t innerStop = 0; innerStop < stopCount; innerStop++) { + std::string zoom = std::to_string(24.0f / stopCount * outerStop); + std::string value = std::to_string(100.0f / stopCount * innerStop); + + if (stops.size() > 1) stops += ","; + stops += R"([{"zoom":)" + zoom + R"(,"value":)" + value + "}, " + value + "]"; + } + } + stops += "]"; + + doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})"); + return doc; +} + +static void Parse_CompositeFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + + while (state.KeepRunning()) { + conversion::Error error; + state.PauseTiming(); + auto doc = createFunctionJSON(stopCount); + state.ResumeTiming(); + optional<CompositeFunction<float>> result = conversion::convert<style::CompositeFunction<float>, JSValue>(doc, error); + if (!result) { + state.SkipWithError(error.message.c_str()); + } + } + state.SetLabel(std::to_string(stopCount).c_str()); +} + +static void Evaluate_CompositeFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + auto doc = createFunctionJSON(stopCount); + conversion::Error error; + optional<CompositeFunction<float>> function = conversion::convert<CompositeFunction<float>, JSValue>(doc, error); + if (!function) { + state.SkipWithError(error.message.c_str()); + } + + while(state.KeepRunning()) { + float z = 24.0f * static_cast<float>(rand() % 100) / 100; + function->evaluate(z, StubGeometryTileFeature(PropertyMap { { "x", static_cast<int64_t>(rand() % 100) } }), -1.0f); + } + + state.SetLabel(std::to_string(stopCount).c_str()); +} + +BENCHMARK(Parse_CompositeFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + +BENCHMARK(Evaluate_CompositeFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + + diff --git a/benchmark/function/source_function.benchmark.cpp b/benchmark/function/source_function.benchmark.cpp new file mode 100644 index 0000000000..14e729eee2 --- /dev/null +++ b/benchmark/function/source_function.benchmark.cpp @@ -0,0 +1,70 @@ +#include <benchmark/benchmark.h> + +#include <mbgl/benchmark/stub_geometry_tile_feature.hpp> + +#include <mbgl/style/function/source_function.hpp> + +#include <mbgl/style/rapidjson_conversion.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/function.hpp> + +#include <rapidjson/document.h> + + +using namespace mbgl; +using namespace mbgl::style; + +static rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> createFunctionJSON(size_t stopCount) { + rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc; + + std::string stops = "["; + for (size_t i = 0; i < stopCount; i++) { + std::string value = std::to_string(100.0f / stopCount * i); + if (stops.size() > 1) stops += ","; + stops += "[" + value + ", " + value + "]"; + } + stops += "]"; + + doc.Parse<0>(R"({"type": "exponential", "base": 2, "stops": )" + stops + R"(, "property": "x"})"); + return doc; +} + +static void Parse_SourceFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + + while (state.KeepRunning()) { + conversion::Error error; + state.PauseTiming(); + auto doc = createFunctionJSON(stopCount); + state.ResumeTiming(); + optional<SourceFunction<float>> result = conversion::convert<SourceFunction<float>, JSValue>(doc, error); + if (!result) { + state.SkipWithError(error.message.c_str()); + } + } + state.SetLabel(std::to_string(stopCount).c_str()); +} + +static void Evaluate_SourceFunction(benchmark::State& state) { + size_t stopCount = state.range(0); + auto doc = createFunctionJSON(stopCount); + conversion::Error error; + optional<SourceFunction<float>> function = conversion::convert<SourceFunction<float>, JSValue>(doc, error); + if (!function) { + state.SkipWithError(error.message.c_str()); + } + + while(state.KeepRunning()) { + function->evaluate(StubGeometryTileFeature(PropertyMap { { "x", static_cast<int64_t>(rand() % 100) } }), -1.0f); + } + + state.SetLabel(std::to_string(stopCount).c_str()); +} + +BENCHMARK(Parse_SourceFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + +BENCHMARK(Evaluate_SourceFunction) + ->Arg(1)->Arg(2)->Arg(4)->Arg(6)->Arg(8)->Arg(10)->Arg(12); + + diff --git a/benchmark/src/mbgl/benchmark/stub_geometry_tile_feature.hpp b/benchmark/src/mbgl/benchmark/stub_geometry_tile_feature.hpp new file mode 100644 index 0000000000..e27aeeb48b --- /dev/null +++ b/benchmark/src/mbgl/benchmark/stub_geometry_tile_feature.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/util/feature.hpp> + +using namespace mbgl; + +class StubGeometryTileFeature : public GeometryTileFeature { +public: + StubGeometryTileFeature(PropertyMap properties_) + : properties(std::move(properties_)) { + } + + StubGeometryTileFeature(optional<FeatureIdentifier> id_, FeatureType type_, GeometryCollection geometry_, PropertyMap properties_) + : properties(std::move(properties_)), + id(std::move(id_)), + type(type_), + geometry(std::move(geometry_)) { + } + + PropertyMap properties; + optional<FeatureIdentifier> id; + FeatureType type = FeatureType::Point; + GeometryCollection geometry; + + FeatureType getType() const override { + return type; + } + + optional<FeatureIdentifier> getID() const override { + return id; + } + + optional<Value> getValue(const std::string& key) const override { + return properties.count(key) ? properties.at(key) : optional<Value>(); + } + + GeometryCollection getGeometries() const override { + return geometry; + } +}; diff --git a/circle.yml b/circle.yml index 52d1c66787..58c431a41d 100644 --- a/circle.yml +++ b/circle.yml @@ -96,6 +96,12 @@ step-library: command: | xvfb-run --server-args="-screen 0 1024x768x24" \ logbt -- apitrace trace --api=egl -v make test-node + - &run-node-tests-recycle-map + run: + name: Run node tests (recycling the map object) + command: | + xvfb-run --server-args="-screen 0 1024x768x24" \ + logbt -- apitrace trace --api=egl -v make test-node-recycle-map - &run-unit-tests run: name: Run tests @@ -115,6 +121,10 @@ step-library: store_artifacts: path: mapbox-gl-js/test/integration/render-tests/index.html destination: render-tests + - &upload-render-tests-recycle-map + store_artifacts: + path: mapbox-gl-js/test/integration/render-tests/index-recycle-map.html + destination: render-tests jobs: # ------------------------------------------------------------------------------ @@ -344,9 +354,9 @@ jobs: - *build-node - *show-ccache-stats - *save-cache - - *run-node-tests + - *run-node-tests-recycle-map - *publish-node-package - - *upload-render-tests + - *upload-render-tests-recycle-map # ------------------------------------------------------------------------------ linux-clang39-debug: diff --git a/cmake/benchmark-files.cmake b/cmake/benchmark-files.cmake index f17d95941d..9161209128 100644 --- a/cmake/benchmark-files.cmake +++ b/cmake/benchmark-files.cmake @@ -5,6 +5,11 @@ set(MBGL_BENCHMARK_FILES benchmark/api/query.benchmark.cpp benchmark/api/render.benchmark.cpp + # function + benchmark/function/camera_function.benchmark.cpp + benchmark/function/composite_function.benchmark.cpp + benchmark/function/source_function.benchmark.cpp + # include/mbgl benchmark/include/mbgl/benchmark.hpp @@ -18,6 +23,7 @@ set(MBGL_BENCHMARK_FILES # src/mbgl/benchmark benchmark/src/mbgl/benchmark/benchmark.cpp + benchmark/src/mbgl/benchmark/stub_geometry_tile_feature.hpp # util benchmark/util/dtoa.benchmark.cpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 6b88fc9803..49697a5c29 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -113,7 +113,6 @@ set(MBGL_CORE_FILES src/mbgl/map/transform.hpp src/mbgl/map/transform_state.cpp src/mbgl/map/transform_state.hpp - src/mbgl/map/update.hpp src/mbgl/map/zoom_history.hpp # math @@ -550,6 +549,7 @@ set(MBGL_CORE_FILES include/mbgl/util/run_loop.hpp include/mbgl/util/size.hpp include/mbgl/util/string.hpp + include/mbgl/util/thread.hpp include/mbgl/util/tileset.hpp include/mbgl/util/timer.hpp include/mbgl/util/traits.hpp @@ -606,7 +606,6 @@ set(MBGL_CORE_FILES src/mbgl/util/stopwatch.cpp src/mbgl/util/stopwatch.hpp src/mbgl/util/string.cpp - src/mbgl/util/thread.hpp src/mbgl/util/thread_local.hpp src/mbgl/util/throttler.cpp src/mbgl/util/throttler.hpp diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 7d6678dc93..4108725776 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -42,7 +42,8 @@ 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 (std::exception_ptr)>; - void renderStill(StillImageCallback callback); + void renderStill(StillImageCallback); + void renderStill(const CameraOptions&, MapDebugOptions, StillImageCallback); // Triggers a repaint. void triggerRepaint(); diff --git a/include/mbgl/storage/offline.hpp b/include/mbgl/storage/offline.hpp index 818cfe2ba5..afb2fa1e81 100644 --- a/include/mbgl/storage/offline.hpp +++ b/include/mbgl/storage/offline.hpp @@ -31,12 +31,14 @@ public: /* Private */ std::vector<CanonicalTileID> tileCover(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const; - + unsigned long tileCount(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const; const std::string styleURL; const LatLngBounds bounds; const double minZoom; const double maxZoom; const float pixelRatio; +private: + Range<uint8_t> coveringZoomRange(SourceType, uint16_t tileSize, const Range<uint8_t>& zoomRange) const; }; /* diff --git a/include/mbgl/style/conversion/make_property_setters.hpp b/include/mbgl/style/conversion/make_property_setters.hpp index ef96f534a9..59b0e7be32 100644 --- a/include/mbgl/style/conversion/make_property_setters.hpp +++ b/include/mbgl/style/conversion/make_property_setters.hpp @@ -52,9 +52,9 @@ auto makeLayoutPropertySetters() { result["text-field"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>; result["text-font"] = &setProperty<V, SymbolLayer, PropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>; result["text-size"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>; - result["text-max-width"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxWidth>; + result["text-max-width"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>; result["text-line-height"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>; - result["text-letter-spacing"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLetterSpacing>; + result["text-letter-spacing"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>; result["text-justify"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>; result["text-anchor"] = &setProperty<V, SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>; result["text-max-angle"] = &setProperty<V, SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>; diff --git a/include/mbgl/style/layers/symbol_layer.hpp b/include/mbgl/style/layers/symbol_layer.hpp index 2d5123573f..a72baa0b4e 100644 --- a/include/mbgl/style/layers/symbol_layer.hpp +++ b/include/mbgl/style/layers/symbol_layer.hpp @@ -126,17 +126,17 @@ public: DataDrivenPropertyValue<float> getTextSize() const; void setTextSize(DataDrivenPropertyValue<float>); - static PropertyValue<float> getDefaultTextMaxWidth(); - PropertyValue<float> getTextMaxWidth() const; - void setTextMaxWidth(PropertyValue<float>); + static DataDrivenPropertyValue<float> getDefaultTextMaxWidth(); + DataDrivenPropertyValue<float> getTextMaxWidth() const; + void setTextMaxWidth(DataDrivenPropertyValue<float>); static PropertyValue<float> getDefaultTextLineHeight(); PropertyValue<float> getTextLineHeight() const; void setTextLineHeight(PropertyValue<float>); - static PropertyValue<float> getDefaultTextLetterSpacing(); - PropertyValue<float> getTextLetterSpacing() const; - void setTextLetterSpacing(PropertyValue<float>); + static DataDrivenPropertyValue<float> getDefaultTextLetterSpacing(); + DataDrivenPropertyValue<float> getTextLetterSpacing() const; + void setTextLetterSpacing(DataDrivenPropertyValue<float>); static DataDrivenPropertyValue<TextJustifyType> getDefaultTextJustify(); DataDrivenPropertyValue<TextJustifyType> getTextJustify() const; diff --git a/include/mbgl/util/constants.hpp b/include/mbgl/util/constants.hpp index eb5c201793..d5e55065c4 100644 --- a/include/mbgl/util/constants.hpp +++ b/include/mbgl/util/constants.hpp @@ -61,7 +61,6 @@ extern const bool tileParseWarnings; extern const bool styleParseWarnings; extern const bool spriteWarnings; extern const bool renderWarnings; -extern const bool renderTree; extern const bool labelTextMissingWarning; extern const bool missingFontStackWarning; extern const bool missingFontFaceWarning; diff --git a/include/mbgl/util/projection.hpp b/include/mbgl/util/projection.hpp index 3cc1146513..f64502c5bc 100644 --- a/include/mbgl/util/projection.hpp +++ b/include/mbgl/util/projection.hpp @@ -75,10 +75,7 @@ public: } static Point<double> project(const LatLng& latLng, double scale) { - return Point<double> { - 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; + return project_(latLng, worldSize(scale)); } static LatLng unproject(const Point<double>& p, double scale, LatLng::WrapMode wrapMode = LatLng::Unwrapped) { @@ -89,6 +86,23 @@ public: wrapMode }; } + + // Project lat, lon to point in a zoom-dependent world size + static Point<double> project(const LatLng& point, uint8_t zoom, uint16_t tileSize) { + const double t2z = tileSize * std::pow(2, zoom); + Point<double> pt = project_(point, t2z); + // Flip y coordinate + auto x = std::round(std::min(pt.x, t2z)); + auto y = std::round(std::min(t2z - pt.y, t2z)); + return { x, y }; + } +private: + static Point<double> project_(const LatLng& latLng, double worldSize) { + return Point<double> { + 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 / util::DEGREES_MAX; + } }; } // namespace mbgl diff --git a/src/mbgl/util/thread.hpp b/include/mbgl/util/thread.hpp index 572f46080e..672eebf6db 100644 --- a/src/mbgl/util/thread.hpp +++ b/include/mbgl/util/thread.hpp @@ -61,8 +61,6 @@ public: } ~Thread() override { - MBGL_VERIFY_THREAD(tid); - if (paused) { resume(); } diff --git a/mapbox-gl-js b/mapbox-gl-js -Subproject 5f4d86d2762db83c62f7e9eeb362f3fa3a8f217 +Subproject 991ab7d1a190a5a54e1f8f54e179dd302ecae49 diff --git a/package.json b/package.json index 2f665e680a..be1e8663f4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "license": "BSD-2-Clause", "dependencies": { "nan": "^2.4.0", - "node-pre-gyp": "^0.6.36" + "node-pre-gyp": "^0.6.36", + "npm-run-all": "^4.0.2" }, "devDependencies": { "aws-sdk": "^2.3.5", @@ -37,7 +38,9 @@ "install": "node-pre-gyp install --fallback-to-build=false || make node", "test": "tape platform/node/test/js/**/*.test.js", "test-memory": "node --expose-gc platform/node/test/memory.test.js", - "test-suite": "node platform/node/test/render.test.js && node platform/node/test/query.test.js" + "test-suite": "run-s test-render test-query", + "test-render": "node platform/node/test/render.test.js", + "test-query": "node platform/node/test/query.test.js" }, "gypfile": true, "binary": { diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index 025097e756..c96587fce6 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -119,9 +119,10 @@ android { } lintOptions { + disable 'MissingTranslation', 'TypographyQuotes' baseline file("lint-baseline-local.xml") checkAllWarnings true - warningsAsErrors true + warningsAsErrors false } testOptions { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java index fe12dd46c4..0f4b81fc39 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/offline/OfflineRegionStatus.java @@ -80,7 +80,7 @@ public class OfflineRegionStatus { * @return true if download is complete, false if not */ public boolean isComplete() { - return (completedResourceCount == requiredResourceCount); + return (completedResourceCount == requiredResourceCount) && downloadState == OfflineRegion.STATE_INACTIVE; } /** diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java new file mode 100644 index 0000000000..5db5f5f4b9 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/snapshotter/MapSnapshotter.java @@ -0,0 +1,259 @@ +package com.mapbox.mapboxsdk.snapshotter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; + +import com.mapbox.mapboxsdk.R; +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.storage.FileSource; + +/** + * The map snapshotter creates a bitmap of the map, rendered + * off the UI thread. The snapshotter itself must be used on + * the UI thread (for access to the main looper) + */ +@UiThread +public class MapSnapshotter { + + /** + * Can be used to get notified of errors + * in snapshot generation + * + * @see MapSnapshotter#start(MapboxMap.SnapshotReadyCallback, ErrorHandler) + */ + public static interface ErrorHandler { + + /** + * Called on error. Snapshotting will not + * continue + * + * @param error the error message + */ + void onError(String error); + } + + private static final int LOGO_MARGIN_PX = 4; + + // Holds the pointer to JNI NativeMapView + private long nativePtr = 0; + + private final Context context; + private MapboxMap.SnapshotReadyCallback callback; + private ErrorHandler errorHandler; + + /** + * MapSnapshotter options + */ + public static class Options { + private int pixelRatio = 1; + private int width; + private int height; + private String styleUrl = Style.MAPBOX_STREETS; + private LatLngBounds region; + private CameraPosition cameraPosition; + + /** + * @param width the width of the image + * @param height the height of the image + */ + public Options(int width, int height) { + this.width = width; + this.height = height; + } + + /** + * @param url The style URL to use + * @return the mutated {@link Options} + */ + public Options withStyle(String url) { + this.styleUrl = url; + return this; + } + + /** + * @param region the region to show in the snapshot. + * This is applied after the camera position + * @return the mutated {@link Options} + */ + public Options withRegion(LatLngBounds region) { + this.region = region; + return this; + } + + /** + * @param pixelRatio the pixel ratio to use (default: 1) + * @return the mutated {@link Options} + */ + public Options withPixelRatio(int pixelRatio) { + this.pixelRatio = pixelRatio; + return this; + } + + /** + * @param cameraPosition The camera position to use, + * the {@link CameraPosition#target} is overridden + * by region if set in conjunction. + * @return the mutated {@link Options} + */ + public Options withCameraPosition(CameraPosition cameraPosition) { + this.cameraPosition = cameraPosition; + return this; + } + + /** + * @return the width of the image + */ + public int getWidth() { + return width; + } + + /** + * @return the height of the image + */ + public int getHeight() { + return height; + } + + /** + * @return the pixel ratio + */ + public int getPixelRatio() { + return pixelRatio; + } + + /** + * @return the region + */ + @Nullable + public LatLngBounds getRegion() { + return region; + } + + /** + * @return the style url + */ + public String getStyleUrl() { + return styleUrl; + } + + /** + * @return the camera position + */ + @Nullable + public CameraPosition getCameraPosition() { + return cameraPosition; + } + } + + /** + * Creates the Map snapshotter, but doesn't start rendering or + * loading yet. + * + * @param context the Context that is or contains the Application context + * @param options the options to use for the snapshot + */ + public MapSnapshotter(@NonNull Context context, @NonNull Options options) { + this.context = context.getApplicationContext(); + FileSource fileSource = FileSource.getInstance(context); + String programCacheDir = context.getCacheDir().getAbsolutePath(); + + nativeInitialize(this, fileSource, options.pixelRatio, options.width, + options.height, options.styleUrl, options.region, options.cameraPosition, + programCacheDir); + } + + /** + * Starts loading and rendering the snapshot. The callback will be fired + * on the calling thread. + * + * @param callback the callback to use when the snapshot is ready + */ + public void start(@NonNull MapboxMap.SnapshotReadyCallback callback) { + this.start(callback, null); + } + + /** + * Starts loading and rendering the snapshot. The callbacks will be fired + * on the calling thread. + * + * @param callback the callback to use when the snapshot is ready + * @param errorHandler the error handler to use on snapshot errors + */ + public void start(@NonNull MapboxMap.SnapshotReadyCallback callback, ErrorHandler errorHandler) { + if (this.callback != null) { + throw new IllegalStateException("Snapshotter was already started"); + } + + this.callback = callback; + nativeStart(); + } + + /** + * Must be called in on the thread + * the object was created on. + */ + public void cancel() { + callback = null; + nativeCancel(); + } + + protected void addOverlay(Bitmap original) { + float margin = context.getResources().getDisplayMetrics().density * LOGO_MARGIN_PX; + Canvas canvas = new Canvas(original); + Bitmap logo = BitmapFactory.decodeResource(context.getResources(), R.drawable.mapbox_logo_icon, null); + canvas.drawBitmap(logo, margin, original.getHeight() - (logo.getHeight() + margin), null); + } + + /** + * Called by JNI peer when snapshot is ready. + * Always called on the origin (main) thread. + * + * @param bitmap the generated snapshot + */ + protected void onSnapshotReady(Bitmap bitmap) { + if (callback != null) { + addOverlay(bitmap); + callback.onSnapshotReady(bitmap); + reset(); + } + } + + /** + * Called by JNI peer when snapshot has failed. + * Always called on the origin (main) thread. + * + * @param reason the exception string + */ + protected void onSnapshotFailed(String reason) { + if (errorHandler != null) { + errorHandler.onError(reason); + reset(); + } + } + + protected void reset() { + callback = null; + errorHandler = null; + } + + protected native void nativeInitialize(MapSnapshotter mapSnapshotter, + FileSource fileSource, float pixelRatio, + int width, int height, String styleUrl, + LatLngBounds region, CameraPosition position, + String programCacheDir); + + protected native void nativeStart(); + + protected native void nativeCancel(); + + @Override + protected native void finalize() throws Throwable; +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index 3e90605a92..d4ddbe48ef 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -2057,11 +2057,11 @@ public class PropertyFactory { /** * The maximum line width for text wrapping. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for Float + * @param <T> the function input type + * @param function a wrapper function for Float * @return property wrapper around a Float function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> textMaxWidth(CameraFunction<Z, Float> function) { + public static <T> PropertyValue<Function<T, Float>> textMaxWidth(Function<T, Float> function) { return new LayoutPropertyValue<>("text-max-width", function); } @@ -2103,11 +2103,11 @@ public class PropertyFactory { /** * Text tracking amount. * - * @param <Z> the zoom parameter type - * @param function a wrapper {@link CameraFunction} for Float + * @param <T> the function input type + * @param function a wrapper function for Float * @return property wrapper around a Float function */ - public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> textLetterSpacing(CameraFunction<Z, Float> function) { + public static <T> PropertyValue<Function<T, Float>> textLetterSpacing(Function<T, Float> function) { return new LayoutPropertyValue<>("text-letter-spacing", function); } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml b/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000000..48d90c3324 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/res/values-fr/strings.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="mapbox_compassContentDescription">Boussole. Activer pour rétablir l\'orientation de la carte vers le nord.</string> + <string name="mapbox_attributionsIconContentDescription">Icone d\'attribution. Activer pour montrer le dialogue d\'attribution.</string> + <string name="mapbox_myLocationViewContentDescription">Vue de géolocalisation. Ceci affiche votre localisation sur la carte.</string> + <string name="mapbox_mapActionDescription">Affichage d\'une carte créée avec Mapbox. Faites la glisser en traînant deux doigts. Zoomez ou dézoomez en écartant ou rapprochant deux doigts.</string> + <string name="mapbox_attributionsDialogTitle">SDK Mapbox pour Android</string> + <string name="mapbox_attributionTelemetryTitle">Faire de meilleures cartes Mapbox</string> + <string name="mapbox_attributionTelemetryMessage">Vous aidez à améliorer les cartes OpenStreetMap et Mapbox en contribuant des données d\'utilisation anonymes.</string> + <string name="mapbox_attributionTelemetryPositive">D\'accord</string> + <string name="mapbox_attributionTelemetryNegative">Pas d\'accord</string> + <string name="mapbox_attributionTelemetryNeutral">Plus d\'informations</string> + <string name="mapbox_offline_error_region_definition_invalid">Le cadre OfflineRegionDefinition pour définir la région de navigation ne tient pas dans les limites du monde : %s</string> + + </resources> diff --git a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties index 029d25e046..3cfc7d5fdc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties +++ b/platform/android/MapboxGLAndroidSDK/src/main/resources/fabric/com.mapbox.mapboxsdk.mapbox-android-sdk.properties @@ -1,3 +1,3 @@ fabric-identifier=com.mapbox.mapboxsdk.mapbox-android-sdk -fabric-version=5.1.0 +fabric-version=5.1.2 fabric-build-type=binary diff --git a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle index 360cb36067..d7b71beefd 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/build.gradle +++ b/platform/android/MapboxGLAndroidSDKTestApp/build.gradle @@ -28,7 +28,8 @@ android { baseline file("lint-baseline-local.xml") checkAllWarnings true warningsAsErrors true - disable 'MissingTranslation', 'GoogleAppIndexingWarning', 'UnpackedNativeCode', 'IconDipSize' + disable 'MissingTranslation', 'GoogleAppIndexingWarning', 'UnpackedNativeCode', 'IconDipSize', 'TypographyQuotes' + abortOnError false } buildTypes { diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java index 1c9faeb9ea..f8248ae4a7 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/SymbolLayerTest.java @@ -1847,6 +1847,139 @@ public class SymbolLayerTest extends BaseActivityTest { } @Test + public void testTextMaxWidthAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-max-width"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textMaxWidth(property("FeaturePropertyA", Stops.<Float>identity())) + ); + + // Verify + assertNotNull(layer.getTextMaxWidth()); + assertNotNull(layer.getTextMaxWidth().getFunction()); + assertEquals(SourceFunction.class, layer.getTextMaxWidth().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextMaxWidth().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getTextMaxWidth().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testTextMaxWidthAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-max-width"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textMaxWidth( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, textMaxWidth(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getTextMaxWidth()); + assertNotNull(layer.getTextMaxWidth().getFunction()); + assertEquals(SourceFunction.class, layer.getTextMaxWidth().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextMaxWidth().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getTextMaxWidth().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testTextMaxWidthAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-max-width"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textMaxWidth( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, textMaxWidth(0.3f)) + ) + ).withDefaultValue(textMaxWidth(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getTextMaxWidth()); + assertNotNull(layer.getTextMaxWidth().getFunction()); + assertEquals(SourceFunction.class, layer.getTextMaxWidth().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextMaxWidth().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getTextMaxWidth().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getTextMaxWidth().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getTextMaxWidth().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getTextMaxWidth().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testTextMaxWidthAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-max-width"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textMaxWidth( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, textMaxWidth(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(textMaxWidth(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getTextMaxWidth()); + assertNotNull(layer.getTextMaxWidth().getFunction()); + assertEquals(CompositeFunction.class, layer.getTextMaxWidth().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getTextMaxWidth().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getTextMaxWidth().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getTextMaxWidth().getFunction().getStops()).size()); + + ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getTextMaxWidth().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test public void testTextLineHeightAsConstant() { validateTestSetup(); setupLayer(); @@ -1945,6 +2078,139 @@ public class SymbolLayerTest extends BaseActivityTest { } @Test + public void testTextLetterSpacingAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-letter-spacing"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textLetterSpacing(property("FeaturePropertyA", Stops.<Float>identity())) + ); + + // Verify + assertNotNull(layer.getTextLetterSpacing()); + assertNotNull(layer.getTextLetterSpacing().getFunction()); + assertEquals(SourceFunction.class, layer.getTextLetterSpacing().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextLetterSpacing().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getTextLetterSpacing().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testTextLetterSpacingAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-letter-spacing"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textLetterSpacing( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, textLetterSpacing(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getTextLetterSpacing()); + assertNotNull(layer.getTextLetterSpacing().getFunction()); + assertEquals(SourceFunction.class, layer.getTextLetterSpacing().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextLetterSpacing().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getTextLetterSpacing().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testTextLetterSpacingAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-letter-spacing"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textLetterSpacing( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, textLetterSpacing(0.3f)) + ) + ).withDefaultValue(textLetterSpacing(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getTextLetterSpacing()); + assertNotNull(layer.getTextLetterSpacing().getFunction()); + assertEquals(SourceFunction.class, layer.getTextLetterSpacing().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getTextLetterSpacing().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getTextLetterSpacing().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getTextLetterSpacing().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getTextLetterSpacing().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getTextLetterSpacing().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testTextLetterSpacingAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("text-letter-spacing"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + textLetterSpacing( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, textLetterSpacing(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(textLetterSpacing(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getTextLetterSpacing()); + assertNotNull(layer.getTextLetterSpacing().getFunction()); + assertEquals(CompositeFunction.class, layer.getTextLetterSpacing().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getTextLetterSpacing().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getTextLetterSpacing().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getTextLetterSpacing().getFunction().getStops()).size()); + + ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getTextLetterSpacing().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test public void testTextJustifyAsConstant() { validateTestSetup(); setupLayer(); diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 5dc322a530..24de706bdb 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -358,6 +358,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> </activity> + <activity android:name=".activity.snapshot.MapSnapshotterActivity" + android:description="@string/description_map_snapshotter" + android:label="@string/activity_map_snapshotter"> + <meta-data + android:name="@string/category" + android:value="@string/category_imagegenerator"/> + </activity> <activity android:name=".activity.maplayout.DoubleMapActivity" android:description="@string/description_doublemap" diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java index a909bb7151..480e48437a 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java @@ -46,6 +46,15 @@ public class QueryRenderedFeaturesBoxHighlightActivity extends AppCompatActivity @Override public void onMapReady(final MapboxMap mapboxMap) { QueryRenderedFeaturesBoxHighlightActivity.this.mapboxMap = mapboxMap; + + // Add layer / source + final GeoJsonSource source = new GeoJsonSource("highlighted-shapes-source"); + mapboxMap.addSource(source); + mapboxMap.addLayer( + new FillLayer("highlighted-shapes-layer", "highlighted-shapes-source") + .withProperties(fillColor(Color.RED)) + ); + selectionBox.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -62,17 +71,8 @@ public class QueryRenderedFeaturesBoxHighlightActivity extends AppCompatActivity String.format("%s features in box", features.size()), Toast.LENGTH_SHORT).show(); - // remove layer / source if already added - mapboxMap.removeSource("highlighted-shapes-source"); - mapboxMap.removeLayer("highlighted-shapes-layer"); - - // Add layer / source - mapboxMap.addSource( - new GeoJsonSource("highlighted-shapes-source", - FeatureCollection.fromFeatures(features)) - ); - mapboxMap.addLayer(new FillLayer("highlighted-shapes-layer", "highlighted-shapes-source") - .withProperties(fillColor(Color.RED))); + // Update source data + source.setGeoJson(FeatureCollection.fromFeatures(features)); } }); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java new file mode 100644 index 0000000000..6b1cb920fc --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/snapshot/MapSnapshotterActivity.java @@ -0,0 +1,131 @@ +package com.mapbox.mapboxsdk.testapp.activity.snapshot; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.ViewTreeObserver; +import android.widget.GridLayout; +import android.widget.ImageView; + +import com.mapbox.mapboxsdk.camera.CameraPosition; +import com.mapbox.mapboxsdk.constants.Style; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter; +import com.mapbox.mapboxsdk.testapp.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import timber.log.Timber; + +/** + * Test activity showing how to use a the {@link com.mapbox.mapboxsdk.snapshotter.MapSnapshotter} + */ +public class MapSnapshotterActivity extends AppCompatActivity { + + private GridLayout grid; + private List<MapSnapshotter> snapshotters = new ArrayList<>(); + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_map_snapshotter); + + // Find the grid view and start snapshotting as soon + // as the view is measured + grid = (GridLayout) findViewById(R.id.snapshot_grid); + grid.getViewTreeObserver() + .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + //noinspection deprecation + grid.getViewTreeObserver().removeGlobalOnLayoutListener(this); + addSnapshots(); + } + }); + } + + private void addSnapshots() { + Timber.i("Creating snapshotters"); + + for (int row = 0; row < grid.getRowCount(); row++) { + for (int column = 0; column < grid.getColumnCount(); column++) { + startSnapShot(row, column); + } + } + } + + private void startSnapShot(final int row, final int column) { + + // Define the dimensions + MapSnapshotter.Options options = new MapSnapshotter.Options( + grid.getMeasuredWidth() / grid.getColumnCount(), + grid.getMeasuredHeight() / grid.getRowCount() + ) + // Optionally the pixel ratio + .withPixelRatio(1) + + // Optionally the style + .withStyle((column + row) % 2 == 0 ? Style.TRAFFIC_DAY : Style.DARK); + + // Optionally the visible region + if (row % 2 == 0) { + options.withRegion(new LatLngBounds.Builder() + .include(new LatLng(randomInRange(-80, 80), randomInRange(-160, 160))) + .include(new LatLng(randomInRange(-80, 80), randomInRange(-160, 160))) + .build() + ); + } + + // Optionally the camera options + if (column % 2 == 0) { + options.withCameraPosition(new CameraPosition.Builder() + .target(options.getRegion() != null + ? options.getRegion().getCenter() + : new LatLng(randomInRange(-80, 80), randomInRange(-160, 160))) + .bearing(randomInRange(0, 360)) + .tilt(randomInRange(0, 60)) + .zoom(randomInRange(0, 20)) + .build() + ); + } + + MapSnapshotter snapshotter = new MapSnapshotter(MapSnapshotterActivity.this, options); + + snapshotter.start(new MapboxMap.SnapshotReadyCallback() { + @Override + public void onSnapshotReady(Bitmap snapshot) { + Timber.i("Got the snapshot"); + ImageView imageView = new ImageView(MapSnapshotterActivity.this); + imageView.setImageBitmap(snapshot); + grid.addView( + imageView, + new GridLayout.LayoutParams(GridLayout.spec(row), GridLayout.spec(column)) + ); + } + }); + snapshotters.add(snapshotter); + } + + @Override + public void onPause() { + super.onPause(); + + // Make sure to stop the snapshotters on pause + for (MapSnapshotter snapshotter : snapshotters) { + snapshotter.cancel(); + } + snapshotters.clear(); + } + + private static Random random = new Random(); + + public static float randomInRange(float min, float max) { + return (random.nextFloat() * (max - min)) + min; + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml new file mode 100644 index 0000000000..cf8bfa24b5 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable/ic_check_box.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M19,5v14H5V5h14m0,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z"/> +</vector> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_snapshotter.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_snapshotter.xml new file mode 100644 index 0000000000..30ad494dca --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_map_snapshotter.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <GridLayout + android:id="@+id/snapshot_grid" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:columnCount="3" + android:orientation="horizontal" + android:rowCount="3"/> + +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 0fbf9754c5..b1f354aad5 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -63,6 +63,7 @@ <string name="activity_building_fill_extrusion_layer">Building layer</string> <string name="activity_animated_image_source">Animated Image Source</string> <string name="activity_bottom_sheet">Bottom sheet</string> + <string name="activity_map_snapshotter">Map Snapshotter</string> <!--Description--> <string name="description_user_location_tracking">Tracks the location of the user</string> @@ -125,6 +126,7 @@ <string name="description_building_fill_extrusion_layer">Shows how to show 3D extruded buildings</string> <string name="description_animated_image_source">Shows how to animate georeferenced images</string> <string name="description_bottom_sheet">Show 2 MapView on screen with a bottom sheet</string> + <string name="description_map_snapshotter">Show a static bitmap taken with the MapSnapshotter</string> <!--Categories--> <string name="category">category</string> diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 390e4842f4..dc6b1c80a6 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -75,6 +75,18 @@ macro(mbgl_platform_core) # Rendering PRIVATE platform/android/src/android_renderer_frontend.cpp PRIVATE platform/android/src/android_renderer_frontend.hpp + + # Snapshots + PRIVATE platform/default/mbgl/gl/headless_backend.cpp + PRIVATE platform/default/mbgl/gl/headless_backend.hpp + PRIVATE platform/default/mbgl/gl/headless_frontend.cpp + PRIVATE platform/default/mbgl/gl/headless_frontend.hpp + PRIVATE platform/default/mbgl/map/map_snapshotter.cpp + PRIVATE platform/default/mbgl/map/map_snapshotter.hpp + PRIVATE platform/linux/src/headless_backend_egl.cpp + PRIVATE platform/linux/src/headless_display_egl.cpp + PRIVATE platform/android/src/snapshotter/map_snapshotter.cpp + PRIVATE platform/android/src/snapshotter/map_snapshotter.hpp ) target_include_directories(mbgl-core diff --git a/platform/android/dependencies.gradle b/platform/android/dependencies.gradle index b938767b3d..5307f46e58 100644 --- a/platform/android/dependencies.gradle +++ b/platform/android/dependencies.gradle @@ -7,7 +7,7 @@ ext { versionCode = 11 versionName = "5.0.0" - mapboxServicesVersion = "2.2.1" + mapboxServicesVersion = "2.2.3" supportLibVersion = "25.3.1" espressoVersion = '2.2.2' testRunnerVersion = '0.5' diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index db8dd1dbdf..f7d1e4afbc 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -47,6 +47,7 @@ #include "style/layers/layers.hpp" #include "style/sources/sources.hpp" #include "style/light.hpp" +#include "snapshotter/map_snapshotter.hpp" namespace mbgl { namespace android { @@ -177,6 +178,9 @@ void registerNatives(JavaVM *vm) { OfflineTilePyramidRegionDefinition::registerNative(env); OfflineRegionError::registerNative(env); OfflineRegionStatus::registerNative(env); + + // Snapshotter + MapSnapshotter::registerNative(env); } } // namespace android diff --git a/platform/android/src/jni/generic_global_ref_deleter.hpp b/platform/android/src/jni/generic_global_ref_deleter.hpp index 4e53e0a0ce..7239e361a7 100644 --- a/platform/android/src/jni/generic_global_ref_deleter.hpp +++ b/platform/android/src/jni/generic_global_ref_deleter.hpp @@ -18,5 +18,34 @@ struct GenericGlobalRefDeleter { } }; + +template < class TagType > +class GenericWeakObjectRefDeleter; + +template < class TagType = jni::ObjectTag > +using GenericUniqueWeakObject = std::unique_ptr< const jni::Object<TagType>, GenericWeakObjectRefDeleter<TagType> >; + +template < class TagType > +class GenericWeakObjectRefDeleter +{ +public: + using pointer = jni::PointerToValue< jni::Object<TagType> >; + + void operator()(pointer p) const + { + if (p) + { + auto env = AttachEnv(); + env->DeleteWeakGlobalRef(jni::Unwrap(p->Get())); + } + } +}; + +template < class TagType > +GenericUniqueWeakObject<TagType> SeizeGenericWeakRef(JNIEnv&, jni::Object<TagType>&& object) +{ + return GenericUniqueWeakObject<TagType>(jni::PointerToValue<jni::Object<TagType>>(std::move(object)), GenericWeakObjectRefDeleter<TagType>()); +}; + } // namespace android } // namespace mbgl diff --git a/platform/android/src/map/camera_position.cpp b/platform/android/src/map/camera_position.cpp index d6f2cb83e8..1fc5f9789f 100644 --- a/platform/android/src/map/camera_position.cpp +++ b/platform/android/src/map/camera_position.cpp @@ -27,6 +27,24 @@ jni::Object<CameraPosition> CameraPosition::New(jni::JNIEnv &env, mbgl::CameraOp return CameraPosition::javaClass.New(env, constructor, LatLng::New(env, center), options.zoom.value_or(0), tilt_degrees, bearing_degrees); } +mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env, jni::Object<CameraPosition> position) { + static auto bearing = CameraPosition::javaClass.GetField<jni::jdouble>(env, "bearing"); + static auto target = CameraPosition::javaClass.GetField<jni::Object<LatLng>>(env, "target"); + static auto tilt = CameraPosition::javaClass.GetField<jni::jdouble>(env, "tilt"); + static auto zoom = CameraPosition::javaClass.GetField<jni::jdouble>(env, "zoom"); + + auto center = LatLng::getLatLng(env, position.Get(env, target)); + + return mbgl::CameraOptions { + center, + {}, + {}, + position.Get(env, zoom), + position.Get(env, bearing) * util::DEG2RAD, + position.Get(env, tilt) + }; +} + void CameraPosition::registerNative(jni::JNIEnv &env) { // Lookup the class CameraPosition::javaClass = *jni::Class<CameraPosition>::Find(env).NewGlobalRef(env).release(); diff --git a/platform/android/src/map/camera_position.hpp b/platform/android/src/map/camera_position.hpp index b9f1646cc9..4eee8be758 100644 --- a/platform/android/src/map/camera_position.hpp +++ b/platform/android/src/map/camera_position.hpp @@ -15,6 +15,8 @@ public: static jni::Object<CameraPosition> New(jni::JNIEnv&, mbgl::CameraOptions); + static mbgl::CameraOptions getCameraOptions(jni::JNIEnv&, jni::Object<CameraPosition>); + static jni::Class<CameraPosition> javaClass; static void registerNative(jni::JNIEnv&); diff --git a/platform/android/src/snapshotter/map_snapshotter.cpp b/platform/android/src/snapshotter/map_snapshotter.cpp new file mode 100644 index 0000000000..d64218d11a --- /dev/null +++ b/platform/android/src/snapshotter/map_snapshotter.cpp @@ -0,0 +1,110 @@ +#include "map_snapshotter.hpp" + +#include <mbgl/renderer/renderer.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/shared_thread_pool.hpp> +#include <mbgl/util/logging.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/actor/scheduler.hpp> + +#include "../attach_env.hpp" +#include "../bitmap.hpp" + +namespace mbgl { +namespace android { + +MapSnapshotter::MapSnapshotter(jni::JNIEnv& _env, + jni::Object<MapSnapshotter> _obj, + jni::Object<FileSource> jFileSource, + jni::jfloat _pixelRatio, + jni::jint width, + jni::jint height, + jni::String styleURL, + jni::Object<LatLngBounds> region, + jni::Object<CameraPosition> position, + jni::String _programCacheDir) + : javaPeer(SeizeGenericWeakRef(_env, jni::Object<MapSnapshotter>(jni::NewWeakGlobalRef(_env, _obj.Get()).release()))) + , pixelRatio(_pixelRatio) + , threadPool(sharedThreadPool()) { + + // Get a reference to the JavaVM for callbacks + if (_env.GetJavaVM(&vm) < 0) { + _env.ExceptionDescribe(); + return; + } + + auto& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource); + auto size = mbgl::Size { static_cast<uint32_t>(width), static_cast<uint32_t>(height) }; + auto cameraOptions = position ? CameraPosition::getCameraOptions(_env, position) : CameraOptions(); + optional<mbgl::LatLngBounds> bounds; + if (region) { + bounds = LatLngBounds::getLatLngBounds(_env, region); + } + + // Create the core snapshotter + snapshotter = std::make_unique<mbgl::MapSnapshotter>(fileSource, + *threadPool, + jni::Make<std::string>(_env, styleURL), + size, + pixelRatio, + cameraOptions, + bounds, + jni::Make<std::string>(_env, _programCacheDir)); + +} + +MapSnapshotter::~MapSnapshotter() = default; + +void MapSnapshotter::start(JNIEnv&) { + MBGL_VERIFY_THREAD(tid); + + snapshotCallback = std::make_unique<Actor<mbgl::MapSnapshotter::Callback>>(*Scheduler::GetCurrent(), [this](std::exception_ptr err, PremultipliedImage image) { + MBGL_VERIFY_THREAD(tid); + android::UniqueEnv _env = android::AttachEnv(); + + if (err) { + // error handler callback + static auto onSnapshotFailed = javaClass.GetMethod<void (jni::String)>(*_env, "onSnapshotFailed"); + javaPeer->Call(*_env, onSnapshotFailed, jni::Make<jni::String>(*_env, util::toString(err))); + } else { + // Create the bitmap + auto bitmap = Bitmap::CreateBitmap(*_env, std::move(image)); + + // invoke callback + static auto onSnapshotReady = javaClass.GetMethod<void (jni::Object<Bitmap>)>(*_env, "onSnapshotReady"); + javaPeer->Call(*_env, onSnapshotReady, bitmap); + } + }); + + snapshotter->snapshot(snapshotCallback->self()); +} + +void MapSnapshotter::cancel(JNIEnv&) { + MBGL_VERIFY_THREAD(tid); + + snapshotCallback.reset(); + snapshotter.reset(); +} + +// Static methods // + +jni::Class<MapSnapshotter> MapSnapshotter::javaClass; + +void MapSnapshotter::registerNative(jni::JNIEnv& env) { + // Lookup the class + MapSnapshotter::javaClass = *jni::Class<MapSnapshotter>::Find(env).NewGlobalRef(env).release(); + +#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name) + + // Register the peer + jni::RegisterNativePeer<MapSnapshotter>(env, MapSnapshotter::javaClass, "nativePtr", + std::make_unique<MapSnapshotter, JNIEnv&, jni::Object<MapSnapshotter>, jni::Object<FileSource>, jni::jfloat, jni::jint, jni::jint, jni::String, jni::Object<LatLngBounds>, jni::Object<CameraPosition>, jni::String>, + "nativeInitialize", + "finalize", + METHOD(&MapSnapshotter::start, "nativeStart"), + METHOD(&MapSnapshotter::cancel, "nativeCancel") + ); +} + +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/android/src/snapshotter/map_snapshotter.hpp b/platform/android/src/snapshotter/map_snapshotter.hpp new file mode 100644 index 0000000000..093f589c05 --- /dev/null +++ b/platform/android/src/snapshotter/map_snapshotter.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include <mbgl/map/map_snapshotter.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/util.hpp> + +#include "../file_source.hpp" +#include "../geometry/lat_lng_bounds.hpp" +#include "../map/camera_position.hpp" + +#include <jni/jni.hpp> +#include "../jni/generic_global_ref_deleter.hpp" + +#include <memory> + +namespace mbgl { +namespace android { + +class SnapshotterRendererFrontend; + +class MapSnapshotter { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/snapshotter/MapSnapshotter"; }; + + static jni::Class<MapSnapshotter> javaClass; + + static void registerNative(jni::JNIEnv&); + + MapSnapshotter(jni::JNIEnv&, + jni::Object<MapSnapshotter>, + jni::Object<FileSource>, + jni::jfloat pixelRatio, + jni::jint width, + jni::jint height, + jni::String styleURL, + jni::Object<LatLngBounds> region, + jni::Object<CameraPosition> position, + jni::String programCacheDir); + + ~MapSnapshotter(); + + void start(JNIEnv&); + + void cancel(JNIEnv&); + +private: + MBGL_STORE_THREAD(tid); + + JavaVM *vm = nullptr; + GenericUniqueWeakObject<MapSnapshotter> javaPeer; + + float pixelRatio; + + std::shared_ptr<mbgl::ThreadPool> threadPool; + std::unique_ptr<Actor<mbgl::MapSnapshotter::Callback>> snapshotCallback; + std::unique_ptr<mbgl::MapSnapshotter> snapshotter; +}; + +} // namespace android +} // namespace mbgl
\ No newline at end of file diff --git a/platform/darwin/docs/guides/Working with GeoJSON Data.md b/platform/darwin/docs/guides/Working with GeoJSON Data.md index 57aaa3855d..f3b3dc0918 100644 --- a/platform/darwin/docs/guides/Working with GeoJSON Data.md +++ b/platform/darwin/docs/guides/Working with GeoJSON Data.md @@ -81,8 +81,10 @@ Linear ring | `MGLPolygon.coordinates`, `MGLPolygon.interiorPolygons` A `Feature` object in GeoJSON corresponds to an instance of an `MGLShape` subclass conforming to the `MGLFeature` protocol. There is a distinct `MGLFeature`-conforming class for each type of geometry that a GeoJSON feature -can contain. This allows features to be used as shapes where convenient. For -example, some features can be added to a map view as annotations. +can contain. This allows features to be used as raw shapes where convenient. For +example, some features can be added to a map view as annotations. Note that +identifiers and attributes will not be available for feature querying when a +feature is used as an annotation. In contrast to the GeoJSON standard, it is possible for `MGLShape` subclasses other than `MGLPointAnnotation` to straddle the antimeridian. diff --git a/platform/darwin/resources/fr.lproj/Foundation.strings b/platform/darwin/resources/fr.lproj/Foundation.strings new file mode 100644 index 0000000000..d2f6c1f6df --- /dev/null +++ b/platform/darwin/resources/fr.lproj/Foundation.strings @@ -0,0 +1,291 @@ +/* Clock position format, long: {hours} o’clock */ +"CLOCK_FMT_LONG" = "%@ heures"; + +/* Clock position format, medium: {hours} o’clock */ +"CLOCK_FMT_MEDIUM" = "%@ heures"; + +/* Clock position format, short: {hours}:00 */ +"CLOCK_FMT_SHORT" = "%@h 00"; + +/* East, long */ +"COMPASS_E_LONG" = "est"; + +/* East, short */ +"COMPASS_E_SHORT" = "E"; + +/* East by north, long */ +"COMPASS_EbN_LONG" = "est par nord"; + +/* East by north, short */ +"COMPASS_EbN_SHORT" = "EpN"; + +/* East by south, long */ +"COMPASS_EbS_LONG" = "est par sud"; + +/* East by south, short */ +"COMPASS_EbS_SHORT" = "EpS"; + +/* East-northeast, long */ +"COMPASS_ENE_LONG" = "est-nord-est"; + +/* East-northeast, short */ +"COMPASS_ENE_SHORT" = "ENE"; + +/* East-southeast, long */ +"COMPASS_ESE_LONG" = "est-sud-est"; + +/* East-southeast, short */ +"COMPASS_ESE_SHORT" = "ESE"; + +/* North, long */ +"COMPASS_N_LONG" = "nord"; + +/* North, short */ +"COMPASS_N_SHORT" = "N"; + +/* North by east, long */ +"COMPASS_NbE_LONG" = "nord par est"; + +/* North by east, short */ +"COMPASS_NbE_SHORT" = "NpE"; + +/* North by west, long */ +"COMPASS_NbW_LONG" = "nord par ouest"; + +/* North by west, short */ +"COMPASS_NbW_SHORT" = "NpO"; + +/* Northeast, long */ +"COMPASS_NE_LONG" = "nord-est"; + +/* Northeast, short */ +"COMPASS_NE_SHORT" = "NE"; + +/* Northeast by east, long */ +"COMPASS_NEbE_LONG" = "nord-est par est"; + +/* Northeast by east, short */ +"COMPASS_NEbE_SHORT" = "NEpE"; + +/* Northeast by north, long */ +"COMPASS_NEbN_LONG" = "nord-est par nord"; + +/* Northeast by north, short */ +"COMPASS_NEbN_SHORT" = "NEpN"; + +/* North-northeast, long */ +"COMPASS_NNE_LONG" = "nord-nord-est"; + +/* North-northeast, short */ +"COMPASS_NNE_SHORT" = "NNE"; + +/* North-northwest, long */ +"COMPASS_NNW_LONG" = "nord-nord-ouest"; + +/* North-northwest, short */ +"COMPASS_NNW_SHORT" = "NNO"; + +/* Northwest, long */ +"COMPASS_NW_LONG" = "nord-ouest"; + +/* Northwest, short */ +"COMPASS_NW_SHORT" = "NO"; + +/* Northwest by north, long */ +"COMPASS_NWbN_LONG" = "nord-ouest par nord"; + +/* Northwest by north, short */ +"COMPASS_NWbN_SHORT" = "NOpN"; + +/* Northwest by west, long */ +"COMPASS_NWbW_LONG" = "nord-ouest par ouest"; + +/* Northwest by west, short */ +"COMPASS_NWbW_SHORT" = "NOpO"; + +/* South, long */ +"COMPASS_S_LONG" = "sud"; + +/* South, short */ +"COMPASS_S_SHORT" = "S"; + +/* South by east, long */ +"COMPASS_SbE_LONG" = "sud par est"; + +/* South by east, short */ +"COMPASS_SbE_SHORT" = "SpE"; + +/* South by west, long */ +"COMPASS_SbW_LONG" = "sud par ouest"; + +/* South by west, short */ +"COMPASS_SbW_SHORT" = "SpO"; + +/* Southeast, long */ +"COMPASS_SE_LONG" = "sud-est"; + +/* Southeast, short */ +"COMPASS_SE_SHORT" = "SE"; + +/* Southeast by east, long */ +"COMPASS_SEbE_LONG" = "sud-est par est"; + +/* Southeast by east, short */ +"COMPASS_SEbE_SHORT" = "SEpE"; + +/* Southeast by south, long */ +"COMPASS_SEbS_LONG" = "sud-est par sud"; + +/* Southeast by south, short */ +"COMPASS_SEbS_SHORT" = "SEpS"; + +/* South-southeast, long */ +"COMPASS_SSE_LONG" = "sud-sud-est"; + +/* South-southeast, short */ +"COMPASS_SSE_SHORT" = "SSE"; + +/* South-southwest, long */ +"COMPASS_SSW_LONG" = "sud-sud-ouest"; + +/* South-southwest, short */ +"COMPASS_SSW_SHORT" = "SSO"; + +/* Southwest, long */ +"COMPASS_SW_LONG" = "sud-ouest"; + +/* Southwest, short */ +"COMPASS_SW_SHORT" = "SO"; + +/* Southwest by south, long */ +"COMPASS_SWbS_LONG" = "sud-ouest par sud"; + +/* Southwest by south, short */ +"COMPASS_SWbS_SHORT" = "SOpS"; + +/* Southwest by west, long */ +"COMPASS_SWbW_LONG" = "sud-ouest par ouest"; + +/* Southwest by west, short */ +"COMPASS_SWbW_SHORT" = "SOpO"; + +/* West, long */ +"COMPASS_W_LONG" = "ouest"; + +/* West, short */ +"COMPASS_W_SHORT" = "O"; + +/* West by north, long */ +"COMPASS_WbN_LONG" = "ouest par nord"; + +/* West by north, short */ +"COMPASS_WbN_SHORT" = "OpN"; + +/* West by south, long */ +"COMPASS_WbS_LONG" = "ouest par sud"; + +/* West by south, short */ +"COMPASS_WbS_SHORT" = "OpS"; + +/* West-northwest, long */ +"COMPASS_WNW_LONG" = "ouest-nord-ouest"; + +/* West-northwest, short */ +"COMPASS_WNW_SHORT" = "ONO"; + +/* West-southwest, long */ +"COMPASS_WSW_LONG" = "ouest-sud-ouest"; + +/* West-southwest, short */ +"COMPASS_WSW_SHORT" = "OSO"; + +/* Degrees format, long */ +"COORD_DEG_LONG" = "%d degré(s)"; + +/* Degrees format, medium: {degrees} */ +"COORD_DEG_MEDIUM" = "%d°"; + +/* Degrees format, short: {degrees} */ +"COORD_DEG_SHORT" = "%d°"; + +/* Coordinate format, long: {degrees}{minutes} */ +"COORD_DM_LONG" = "%1$@ et %2$@"; + +/* Coordinate format, medium: {degrees}{minutes} */ +"COORD_DM_MEDIUM" = "%1$@%2$@"; + +/* Coordinate format, short: {degrees}{minutes} */ +"COORD_DM_SHORT" = "%1$@%2$@"; + +/* Coordinate format, long: {degrees}{minutes}{seconds} */ +"COORD_DMS_LONG" = "%1$@, %2$@ et %3$@"; + +/* Coordinate format, medium: {degrees}{minutes}{seconds} */ +"COORD_DMS_MEDIUM" = "%1$@%2$@%3$@"; + +/* Coordinate format, short: {degrees}{minutes}{seconds} */ +"COORD_DMS_SHORT" = "%1$@%2$@%3$@"; + +/* East longitude format, long: {longitude} */ +"COORD_E_LONG" = "%@ est"; + +/* East longitude format, medium: {longitude} */ +"COORD_E_MEDIUM" = "%@ est"; + +/* East longitude format, short: {longitude} */ +"COORD_E_SHORT" = "%@E"; + +/* Coordinate pair format, long: {latitude}, {longitude} */ +"COORD_FMT_LONG" = "%1$@, %2$@"; + +/* Coordinate pair format, medium: {latitude}, {longitude} */ +"COORD_FMT_MEDIUM" = "%1$@, %2$@"; + +/* Coordinate pair format, short: {latitude}, {longitude} */ +"COORD_FMT_SHORT" = "%1$@, %2$@"; + +/* Minutes format, long */ +"COORD_MIN_LONG" = "%d minute(s)"; + +/* Minutes format, medium: {minutes} */ +"COORD_MIN_MEDIUM" = "%d′"; + +/* Minutes format, short: {minutes} */ +"COORD_MIN_SHORT" = "%d′"; + +/* North latitude format, long: {latitude} */ +"COORD_N_LONG" = "%@ nord"; + +/* North latitude format, medium: {latitude} */ +"COORD_N_MEDIUM" = "%@ nord"; + +/* North latitude format, short: {latitude} */ +"COORD_N_SHORT" = "%@N"; + +/* South latitude format, long: {latitude} */ +"COORD_S_LONG" = "%@ sud"; + +/* South latitude format, medium: {latitude} */ +"COORD_S_MEDIUM" = "%@ sud"; + +/* South latitude format, short: {latitude} */ +"COORD_S_SHORT" = "%@S"; + +/* Seconds format, long */ +"COORD_SEC_LONG" = "%d seconde(s)"; + +/* Seconds format, medium: {seconds} */ +"COORD_SEC_MEDIUM" = "%d″"; + +/* Seconds format, short: {seconds} */ +"COORD_SEC_SHORT" = "%d″"; + +/* West longitude format, long: {longitude} */ +"COORD_W_LONG" = "%@ ouest"; + +/* West longitude format, medium: {longitude} */ +"COORD_W_MEDIUM" = "%@ ouest"; + +/* West longitude format, short: {longitude} */ +"COORD_W_SHORT" = "%@O"; + diff --git a/platform/darwin/src/MGLFeature.h b/platform/darwin/src/MGLFeature.h index 491c89b608..a13821cf96 100644 --- a/platform/darwin/src/MGLFeature.h +++ b/platform/darwin/src/MGLFeature.h @@ -18,15 +18,23 @@ NS_ASSUME_NONNULL_BEGIN You can add custom data to display on the map by creating feature objects and adding them to an `MGLShapeSource` using the `-[MGLShapeSource initWithIdentifier:shape:options:]` method or - `MGLShapeSource.shape` property. Similarly, you can add `MGLPointFeature`, - `MGLPolylineFeature`, and `MGLPolygonFeature` objects to the map as annotations - using `-[MGLMapView addAnnotations:]` and related methods. + `MGLShapeSource.shape` property. In addition to adding data to the map, you can also extract data from the map: `-[MGLMapView visibleFeaturesAtPoint:]` and related methods return feature objects that correspond to features in the source. This enables you to inspect the properties of features in vector tiles loaded by `MGLVectorSource` objects. You also reuse these feature objects as overlay annotations. + + While it is possible to add `MGLFeature`-conforming objects to the map as + annotations using `-[MGLMapView addAnnotations:]` and related methods, doing so + has trade-offs: + + - Features added as annotations will not have `identifier` or `attributes` + properties when used with feature querying. + + - Features added as annotations become interactive. Taps and selection can be + handled in `-[MGLMapViewDelegate mapView:didSelectAnnotation:]`. */ @protocol MGLFeature <MGLAnnotation> diff --git a/platform/darwin/src/MGLFeature.mm b/platform/darwin/src/MGLFeature.mm index e169ee19bb..84f1a1ff25 100644 --- a/platform/darwin/src/MGLFeature.mm +++ b/platform/darwin/src/MGLFeature.mm @@ -42,6 +42,15 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, coordinate = %f, %f, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + self.coordinate.latitude, self.coordinate.longitude, + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPolylineFeature () @@ -68,6 +77,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)[self pointCount], + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPolygonFeature () @@ -94,6 +113,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)[self pointCount], + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLPointCollectionFeature () @@ -146,6 +175,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)self.polylines.count, + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLMultiPolygonFeature () @@ -172,6 +211,16 @@ MGL_DEFINE_FEATURE_IS_EQUAL(); return mbglFeature({[self geometryObject]}, identifier, self.attributes); } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; identifier = %@, count = %lu, bounds = %@, attributes = %@>", + NSStringFromClass([self class]), (void *)self, + self.identifier ? [NSString stringWithFormat:@"\"%@\"", self.identifier] : self.identifier, + (unsigned long)self.polygons.count, + MGLStringFromCoordinateBounds(self.overlayBounds), + self.attributes.count ? self.attributes : @"none"]; +} + @end @interface MGLShapeCollectionFeature () diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h new file mode 100644 index 0000000000..a2a4f1b331 --- /dev/null +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -0,0 +1,105 @@ +#import <Foundation/Foundation.h> +#import "MGLTypes.h" +#import "MGLGeometry.h" +#import "MGLMapCamera.h" + +NS_ASSUME_NONNULL_BEGIN + +MGL_EXPORT +/** + The options to use when creating images with the `MGLMapsnapshotter`. + */ +@interface MGLMapSnapshotOptions : NSObject + +/** + Creates a set of options with the minimum required information + @param styleURL the style url to use + @param camera the camera settings + @param size the image size + */ +- (instancetype)initWithStyleURL:(NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; + +#pragma mark - Configuring the map + +/** + The style URL for these options. + */ +@property (nonatomic, readonly) NSURL* styleURL; + +/** + The zoom. Default is 0. + */ +@property (nonatomic) double zoom; + +/** + The `MGLMapcamera` options to use. + */ +@property (nonatomic) MGLMapCamera* camera; + +/** + A region to capture. Overrides the center coordinate + in the mapCamera options if set + */ +@property (nonatomic) MGLCoordinateBounds region; + +#pragma mark - Configuring the image + +/** + The size of the output image. Minimum is 64x64 + */ +@property (nonatomic, readonly) CGSize size; + +/** + The scale of the output image. Defaults to the main screen scale. + Minimum is 1. + */ +@property (nonatomic) CGFloat scale; + +@end + +/** + A block to processes the result or error of a snapshot request. + + The result will be either an `MGLImage` or a `NSError` + + @param snapshot The image that was generated or `nil` if an error occurred. + @param error The eror that occured or `nil` when succesful. + */ +typedef void (^MGLMapSnapshotCompletionHandler)(MGLImage* _Nullable snapshot, NSError* _Nullable error); + +/** + A utility object for capturing map-based images. + */ +MGL_EXPORT +@interface MGLMapSnapshotter : NSObject + +- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; + +/** + Starts the snapshot creation and executes the specified block with the result. + + @param completionHandler The block to handle the result in. + */ +- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completionHandler; + +/** + Starts the snapshot creation and executes the specified block with the result on the specified queue. + + @param queue The queue to handle the result on. + @param completionHandler The block to handle the result in. + */ +- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completionHandler; + +/** + Cancels the snapshot creation request, if any. + */ +- (void)cancel; + +/** + Indicates whether as snapshot is currently being generated. + */ +@property (nonatomic, readonly, getter=isLoading) BOOL loading; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm new file mode 100644 index 0000000000..c81fd39c4a --- /dev/null +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -0,0 +1,163 @@ +#import "MGLMapSnapshotter.h" + +#import <mbgl/actor/actor.hpp> +#import <mbgl/actor/scheduler.hpp> +#import <mbgl/util/geo.hpp> +#import <mbgl/map/map_snapshotter.hpp> +#import <mbgl/map/camera.hpp> +#import <mbgl/storage/default_file_source.hpp> +#import <mbgl/util/default_thread_pool.hpp> +#import <mbgl/util/string.hpp> +#import <mbgl/util/shared_thread_pool.hpp> + +#import "MGLOfflineStorage_Private.h" +#import "MGLGeometry_Private.h" +#import "NSBundle+MGLAdditions.h" + +#if TARGET_OS_IPHONE +#import "UIImage+MGLAdditions.h" +#else +#import "NSImage+MGLAdditions.h" +#endif + +@implementation MGLMapSnapshotOptions + +- (instancetype _Nonnull)initWithStyleURL:(NSURL* _Nonnull)styleURL camera:(MGLMapCamera*)camera size:(CGSize) size; +{ + self = [super init]; + if (self) { + _styleURL = styleURL; + _size = size; + _camera = camera; +#if TARGET_OS_IPHONE + _scale = [UIScreen mainScreen].scale; +#else + _scale = [NSScreen mainScreen].backingScaleFactor; +#endif + + } + return self; +} + +@end + +@implementation MGLMapSnapshotter { + + std::shared_ptr<mbgl::ThreadPool> mbglThreadPool; + std::unique_ptr<mbgl::MapSnapshotter> mbglMapSnapshotter; + std::unique_ptr<mbgl::Actor<mbgl::MapSnapshotter::Callback>> snapshotCallback; +} + +- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; +{ + self = [super init]; + if (self) { + _loading = false; + + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; + mbglThreadPool = mbgl::sharedThreadPool(); + + std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]); + + // Size; taking into account the minimum texture size for OpenGL ES + mbgl::Size size = { + static_cast<uint32_t>(MAX(options.size.width, 64)), + static_cast<uint32_t>(MAX(options.size.height, 64)) + }; + + float pixelRatio = MAX(options.scale, 1); + + // Camera options + mbgl::CameraOptions cameraOptions; + if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { + cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); + } + cameraOptions.angle = MAX(0, options.camera.heading) * mbgl::util::DEG2RAD; + cameraOptions.zoom = MAX(0, options.zoom); + cameraOptions.pitch = MAX(0, options.camera.pitch); + + // Region + mbgl::optional<mbgl::LatLngBounds> region; + if (!MGLCoordinateBoundsIsEmpty(options.region)) { + region = MGLLatLngBoundsFromCoordinateBounds(options.region); + } + + // Create the snapshotter + mbglMapSnapshotter = std::make_unique<mbgl::MapSnapshotter>(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, region); + } + return self; +} + +- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; +{ + [self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; +} + +- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; +{ + if ([self isLoading]) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Already started this snapshotter"}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + dispatch_async(queue, ^{ + completion(nil, error); + }); + return; + } + + _loading = true; + + dispatch_async(queue, ^{ + snapshotCallback = std::make_unique<mbgl::Actor<mbgl::MapSnapshotter::Callback>>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image) { + _loading = false; + if (mbglError) { + NSString *description = @(mbgl::util::toString(mbglError).c_str()); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(nil, error); + }); + } else { + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; + + // Process image watermark in a work queue + dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(workQueue, ^{ +#if TARGET_OS_IPHONE + UIImage *logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + + UIGraphicsBeginImageContext(mglImage.size); + + [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; + [logoImage drawInRect:CGRectMake(8, mglImage.size.height - (8 + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; + UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); +#else + NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; + NSImage *compositedImage = mglImage; + + [compositedImage lockFocus]; + [logoImage drawInRect:CGRectMake(8, 8, logoImage.size.width,logoImage.size.height)]; + [compositedImage unlockFocus]; +#endif + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(compositedImage, nil); + }); + }); + } + }); + mbglMapSnapshotter->snapshot(snapshotCallback->self()); + }); +} + +- (void)cancel; +{ + snapshotCallback.reset(); + mbglMapSnapshotter.reset(); +} + +@end diff --git a/platform/darwin/src/MGLPolygon.mm b/platform/darwin/src/MGLPolygon.mm index d966ff13ce..e7843224e9 100644 --- a/platform/darwin/src/MGLPolygon.mm +++ b/platform/darwin/src/MGLPolygon.mm @@ -200,4 +200,14 @@ @"coordinates": coordinates}; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, + (unsigned long)self.polygons.count, + MGLStringFromCoordinateBounds(self.overlayBounds)]; +} + @end diff --git a/platform/darwin/src/MGLPolyline.h b/platform/darwin/src/MGLPolyline.h index b3db0fd39f..e46baa91cc 100644 --- a/platform/darwin/src/MGLPolyline.h +++ b/platform/darwin/src/MGLPolyline.h @@ -33,8 +33,18 @@ NS_ASSUME_NONNULL_BEGIN `MGLPolygon` object. To group multiple polylines together in one shape, use an `MGLMultiPolyline` or `MGLShapeCollection` object. - To make the polyline straddle the antimeridian, specify some longitudes less - than −180 degrees or greater than 180 degrees. + To make the polyline go across the antimeridian or international date line, + specify some longitudes less than −180 degrees or greater than 180 degrees. + For example, a polyline that stretches from Tokyo to San Francisco would have + coordinates of (35.68476, -220.24257) and (37.78428, -122.41310). + + ```swift + let coordinates = [ + CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257), + CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310) + ] + let polyline = MGLPolyline(coordinates: coordinates, count: UInt(coordinates.count)) + ``` A polyline is known as a <a href="https://tools.ietf.org/html/rfc7946#section-3.1.4">LineString</a> diff --git a/platform/darwin/src/MGLPolyline.mm b/platform/darwin/src/MGLPolyline.mm index fd75dc2795..0e371a4dda 100644 --- a/platform/darwin/src/MGLPolyline.mm +++ b/platform/darwin/src/MGLPolyline.mm @@ -201,4 +201,14 @@ @"coordinates": coordinates}; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; title = %@, subtitle: = %@, count = %lu; bounds = %@>", + NSStringFromClass([self class]), (void *)self, + self.title ? [NSString stringWithFormat:@"\"%@\"", self.title] : self.title, + self.subtitle ? [NSString stringWithFormat:@"\"%@\"", self.subtitle] : self.subtitle, + (unsigned long)self.polylines.count, + MGLStringFromCoordinateBounds(self.overlayBounds)]; +} + @end diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index f02fc98ded..571cbdcc62 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -98,8 +98,8 @@ const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLSh } std::vector<mbgl::Feature> features; - if (self.style) { - features = self.style.mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter }); + if (self.mapView) { + features = self.mapView.renderer->querySourceFeatures(self.rawSource->getID(), { {}, optionalFilter }); } return MGLFeaturesFromMBGLFeatures(features); } diff --git a/platform/darwin/src/MGLSource.mm b/platform/darwin/src/MGLSource.mm index ee012f4d66..6d57e14e8c 100644 --- a/platform/darwin/src/MGLSource.mm +++ b/platform/darwin/src/MGLSource.mm @@ -1,7 +1,9 @@ #import "MGLSource_Private.h" #import "MGLStyle_Private.h" +#import "MGLMapView_Private.h" #include <mbgl/style/style.hpp> +#include <mbgl/map/map.hpp> #include <mbgl/style/source.hpp> @interface MGLSource () @@ -10,7 +12,7 @@ // special internal source types like mbgl::AnnotationSource. @property (nonatomic, readonly) mbgl::style::Source *rawSource; -@property (nonatomic, readonly, weak) MGLStyle *style; +@property (nonatomic, readonly, weak) MGLMapView *mapView; @end @@ -27,37 +29,38 @@ return self; } -- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource { +- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource mapView:(MGLMapView *)mapView { NSString *identifier = @(rawSource->getID().c_str()); if (self = [self initWithIdentifier:identifier]) { _rawSource = rawSource; _rawSource->peer = SourceWrapper { self }; + _mapView = mapView; } return self; } - (instancetype)initWithPendingSource:(std::unique_ptr<mbgl::style::Source>)pendingSource { - if (self = [self initWithRawSource:pendingSource.get()]) { + if (self = [self initWithRawSource:pendingSource.get() mapView:nil]) { _pendingSource = std::move(pendingSource); } return self; } -- (void)addToStyle:(MGLStyle *)style { +- (void)addToMapView:(MGLMapView *)mapView { if (_pendingSource == nullptr) { [NSException raise:@"MGLRedundantSourceException" format:@"This instance %@ was already added to %@. Adding the same source instance " \ - "to the style more than once is invalid.", self, style]; + "to the style more than once is invalid.", self, mapView.style]; } - - _style = style; - style.rawStyle->addSource(std::move(_pendingSource)); + + _mapView = mapView; + _mapView.style.rawStyle->addSource(std::move(_pendingSource)); } -- (void)removeFromStyle:(MGLStyle *)style { - if (self.rawSource == style.rawStyle->getSource(self.identifier.UTF8String)) { - _pendingSource = style.rawStyle->removeSource(self.identifier.UTF8String); - _style = nil; +- (void)removeFromMapView:(MGLMapView *)mapView { + if (self.rawSource == mapView.style.rawStyle->getSource(self.identifier.UTF8String)) { + _pendingSource = mapView.style.rawStyle->removeSource(self.identifier.UTF8String); + _mapView = nil; } } diff --git a/platform/darwin/src/MGLSource_Private.h b/platform/darwin/src/MGLSource_Private.h index ba78973279..d7d1f66641 100644 --- a/platform/darwin/src/MGLSource_Private.h +++ b/platform/darwin/src/MGLSource_Private.h @@ -18,7 +18,7 @@ struct SourceWrapper { __weak MGLSource *source; }; -@class MGLStyle; +@class MGLMapView; @interface MGLSource (Private) @@ -26,7 +26,7 @@ struct SourceWrapper { Initializes and returns a source with a raw pointer to the backing store, associated with a style. */ -- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource; +- (instancetype)initWithRawSource:(mbgl::style::Source *)rawSource mapView:(nullable MGLMapView *)mapView; /** Initializes and returns a source with an owning pointer to the backing store, @@ -44,33 +44,30 @@ struct SourceWrapper { @property (nonatomic, readonly) mbgl::style::Source *rawSource; /** - The style which currently contains the source. - - If the source is not currently part of a style, this property is + The map view whose style currently contains the source. + If the source is not currently part of any map view’s style, this property is set to `nil`. */ -@property (nonatomic, readonly, weak) MGLStyle *style; +@property (nonatomic, readonly, weak) MGLMapView *mapView; /** - Adds the mbgl source that this object represents to the style. - + Adds the mbgl source that this object represents to the mbgl map. Once a mbgl source is added, ownership of the object is transferred to the - `mbgl::Style` and this object no longer has an active unique_ptr reference to the + `mbgl::Map` and this object no longer has an active unique_ptr reference to the `mbgl::Source`. If this object's mbgl source is in that state, the mbgl source can still be changed but the changes will not be visible until the `MGLSource` - is added back to the style via `-[MGLStyle addSource:]` and styled with a + is added back to the map via `-[MGLStyle addSource:]` and styled with a `MGLLayer`. */ -- (void)addToStyle:(MGLStyle *)style; +- (void)addToMapView:(MGLMapView *)mapView; /** - Removes the mbgl source that this object represents from the style. - + Removes the mbgl source that this object represents from the mbgl map. When a mbgl source is removed, ownership of the object is transferred back to the `MGLSource` instance and the unique_ptr reference is valid again. It is safe to add the source back to the style after it is removed. */ -- (void)removeFromStyle:(MGLStyle *)style; +- (void)removeFromMapView:(MGLMapView *)mapView; @end diff --git a/platform/darwin/src/MGLStyle.h b/platform/darwin/src/MGLStyle.h index 6fb4a6cc6b..98be70be9c 100644 --- a/platform/darwin/src/MGLStyle.h +++ b/platform/darwin/src/MGLStyle.h @@ -557,6 +557,21 @@ MGL_EXPORT */ @property (nonatomic, strong) MGLLight *light; +#pragma mark Localizing Map Content + +/** + A Boolean value that determines whether the style attempts to localize labels in + the style into the system’s preferred language. + + When this property is enabled, the style automatically modifies the text property + of any symbol style layer whose source is the + <a href="https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview">Mapbox + Streets source</a>. On iOS, the user can set the system’s preferred language in + Settings, General Settings, Language & Region. On macOS, the user can set the + system’s preferred language in the Language & Region pane of System Preferences. + */ +@property (nonatomic) BOOL localizesLabels; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 2365641f02..94f199fd21 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -17,6 +17,7 @@ #import "MGLLight_Private.h" #import "MGLTileSource_Private.h" #import "MGLVectorSource.h" +#import "MGLVectorSource+MGLAdditions.h" #import "MGLRasterSource.h" #import "MGLShapeSource.h" #import "MGLImageSource.h" @@ -49,12 +50,35 @@ #import "NSImage+MGLAdditions.h" #endif +/** + Model class for localization changes. + */ +@interface MGLTextLanguage: NSObject +@property (strong, nonatomic) NSString *originalTextField; +@property (strong, nonatomic) NSString *updatedTextField; + +- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField; + +@end + +@implementation MGLTextLanguage +- (instancetype)initWithTextLanguage:(NSString *)originalTextField updatedTextField:(NSString *)updatedTextField +{ + if (self = [super init]) { + _originalTextField = originalTextField; + _updatedTextField = updatedTextField; + } + return self; +} +@end + @interface MGLStyle() @property (nonatomic, readonly, weak) MGLMapView *mapView; @property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (readonly, copy, nullable) NSURL *URL; @property (nonatomic, readwrite, strong) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLOpenGLStyleLayer *) *openGLLayers; +@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, NS_DICTIONARY_OF(NSObject *, MGLTextLanguage *) *) *localizedLayersByIdentifier; @end @@ -119,6 +143,7 @@ static NSURL *MGLStyleURL_emerald; _mapView = mapView; _rawStyle = rawStyle; _openGLLayers = [NSMutableDictionary dictionary]; + _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; } return self; } @@ -164,6 +189,7 @@ static NSURL *MGLStyleURL_emerald; - (MGLSource *)sourceWithIdentifier:(NSString *)identifier { auto rawSource = self.rawStyle->getSource(identifier.UTF8String); + return rawSource ? [self sourceFromMBGLSource:rawSource] : nil; } @@ -175,15 +201,15 @@ static NSURL *MGLStyleURL_emerald; // TODO: Fill in options specific to the respective source classes // https://github.com/mapbox/mapbox-gl-native/issues/6584 if (auto vectorSource = rawSource->as<mbgl::style::VectorSource>()) { - return [[MGLVectorSource alloc] initWithRawSource:vectorSource]; + return [[MGLVectorSource alloc] initWithRawSource:vectorSource mapView:self.mapView]; } else if (auto geoJSONSource = rawSource->as<mbgl::style::GeoJSONSource>()) { - return [[MGLShapeSource alloc] initWithRawSource:geoJSONSource]; + return [[MGLShapeSource alloc] initWithRawSource:geoJSONSource mapView:self.mapView]; } else if (auto rasterSource = rawSource->as<mbgl::style::RasterSource>()) { - return [[MGLRasterSource alloc] initWithRawSource:rasterSource]; + return [[MGLRasterSource alloc] initWithRawSource:rasterSource mapView:self.mapView]; } else if (auto imageSource = rawSource->as<mbgl::style::ImageSource>()) { - return [[MGLImageSource alloc] initWithRawSource:imageSource]; + return [[MGLImageSource alloc] initWithRawSource:imageSource mapView:self.mapView]; } else { - return [[MGLSource alloc] initWithRawSource:rawSource]; + return [[MGLSource alloc] initWithRawSource:rawSource mapView:self.mapView]; } } @@ -197,7 +223,7 @@ static NSURL *MGLStyleURL_emerald; } try { - [source addToStyle:self]; + [source addToMapView:self.mapView]; } catch (std::runtime_error & err) { [NSException raise:@"MGLRedundantSourceIdentifierException" format:@"%s", err.what()]; } @@ -211,7 +237,7 @@ static NSURL *MGLStyleURL_emerald; @"Make sure the source was created as a member of a concrete subclass of MGLSource.", source]; } - [source removeFromStyle:self]; + [source removeFromMapView:self.mapView]; } - (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor { @@ -585,4 +611,115 @@ static NSURL *MGLStyleURL_emerald; self.URL ? [NSString stringWithFormat:@"\"%@\"", self.URL] : self.URL]; } +#pragma mark Style language preferences + +- (void)setLocalizesLabels:(BOOL)localizesLabels +{ + if (_localizesLabels != localizesLabels) { + _localizesLabels = localizesLabels; + } else { + return; + } + + if (_localizesLabels) { + NSString *preferredLanguage = [MGLVectorSource preferredMapboxStreetsLanguage]; + NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary]; + for (MGLSymbolStyleLayer *layer in self.layers) { + if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) { + continue; + } + + MGLVectorSource *source = (MGLVectorSource *)[self sourceWithIdentifier:layer.sourceIdentifier]; + if (![source isKindOfClass:[MGLVectorSource class]] || !source.mapboxStreets) { + continue; + } + + NSDictionary *localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier]; + if (!localizedKeysByKey) { + localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier] = [source localizedKeysByKeyForPreferredLanguage:preferredLanguage]; + } + + NSString *(^stringByLocalizingString)(NSString *) = ^ NSString * (NSString *string) { + NSMutableString *localizedString = string.mutableCopy; + [localizedKeysByKey enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull localizedKey, BOOL * _Nonnull stop) { + NSAssert([key isKindOfClass:[NSString class]], @"key is not a string"); + NSAssert([localizedKey isKindOfClass:[NSString class]], @"localizedKey is not a string"); + [localizedString replaceOccurrencesOfString:[NSString stringWithFormat:@"{%@}", key] + withString:[NSString stringWithFormat:@"{%@}", localizedKey] + options:0 + range:NSMakeRange(0, localizedString.length)]; + }]; + return localizedString; + }; + + if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { + NSString *textField = [(MGLConstantStyleValue<NSString *> *)layer.text rawValue]; + NSString *localizingString = stringByLocalizingString(textField); + if (![textField isEqualToString:localizingString]) { + MGLTextLanguage *textLanguage = [[MGLTextLanguage alloc] initWithTextLanguage:textField + updatedTextField:localizingString]; + [self.localizedLayersByIdentifier setObject:@{ textField : textLanguage } forKey:layer.identifier]; + layer.text = [MGLStyleValue<NSString *> valueWithRawValue:localizingString]; + } + } + else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { + MGLCameraStyleFunction *function = (MGLCameraStyleFunction<NSString *> *)layer.text; + NSMutableDictionary *stops = function.stops.mutableCopy; + NSMutableDictionary *cameraStops = [NSMutableDictionary dictionary]; + [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue<NSString *> *stop, BOOL *done) { + NSString *textField = stop.rawValue; + NSString *localizingString = stringByLocalizingString(textField); + if (![textField isEqualToString:localizingString]) { + MGLTextLanguage *textLanguage = [[MGLTextLanguage alloc] initWithTextLanguage:textField + updatedTextField:localizingString]; + [cameraStops setObject:textLanguage forKey:zoomLevel]; + stops[zoomLevel] = [MGLStyleValue<NSString *> valueWithRawValue:localizingString]; + } + + }]; + if (cameraStops.count > 0) { + [self.localizedLayersByIdentifier setObject:cameraStops forKey:layer.identifier]; + } + function.stops = stops; + layer.text = function; + } + } + } else { + + [self.localizedLayersByIdentifier enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSDictionary<NSObject *, MGLTextLanguage *> *textFields, BOOL *done) { + MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:identifier]; + + if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { + NSString *textField = [(MGLConstantStyleValue<NSString *> *)layer.text rawValue]; + [textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *originalLanguage, MGLTextLanguage *textLanguage, BOOL *done) { + if ([textLanguage.updatedTextField isEqualToString:textField]) { + layer.text = [MGLStyleValue<NSString *> valueWithRawValue:textLanguage.originalTextField]; + } + }]; + + } + else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { + MGLCameraStyleFunction *function = (MGLCameraStyleFunction<NSString *> *)layer.text; + NSMutableDictionary *stops = function.stops.mutableCopy; + [textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *zoomKey, MGLTextLanguage *textLanguage, BOOL *done) { + if ([zoomKey isKindOfClass:[NSNumber class]]) { + NSNumber *zoomLevel = (NSNumber*)zoomKey; + MGLConstantStyleValue<NSString *> *stop = [stops objectForKey:zoomLevel]; + NSString *textField = stop.rawValue; + if ([textLanguage.updatedTextField isEqualToString:textField]) { + stops[zoomLevel] = [MGLStyleValue<NSString *> valueWithRawValue:textLanguage.originalTextField]; + } + } + }]; + + function.stops = stops; + layer.text = function; + } + + }]; + + self.localizedLayersByIdentifier = [NSMutableDictionary dictionary]; + } +} + @end diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index bc39df5b16..ffb95dfc73 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -857,6 +857,15 @@ MGL_EXPORT * `MGLCameraStyleFunction` with an interpolation mode of: * `MGLInterpolationModeExponential` * `MGLInterpolationModeInterval` + * `MGLSourceStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` + * `MGLInterpolationModeIdentity` + * `MGLCompositeStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` */ @property (nonatomic, null_resettable) MGLStyleValue<NSNumber *> *maximumTextWidth; @@ -1162,6 +1171,15 @@ MGL_EXPORT * `MGLCameraStyleFunction` with an interpolation mode of: * `MGLInterpolationModeExponential` * `MGLInterpolationModeInterval` + * `MGLSourceStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` + * `MGLInterpolationModeIdentity` + * `MGLCompositeStyleFunction` with an interpolation mode of: + * `MGLInterpolationModeExponential` + * `MGLInterpolationModeInterval` + * `MGLInterpolationModeCategorical` */ @property (nonatomic, null_resettable) MGLStyleValue<NSNumber *> *textLetterSpacing; diff --git a/platform/darwin/src/MGLSymbolStyleLayer.mm b/platform/darwin/src/MGLSymbolStyleLayer.mm index 7e8b0b247b..1990c82669 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.mm +++ b/platform/darwin/src/MGLSymbolStyleLayer.mm @@ -485,7 +485,7 @@ namespace mbgl { - (void)setMaximumTextWidth:(MGLStyleValue<NSNumber *> *)maximumTextWidth { MGLAssertStyleLayerIsValid(); - auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toInterpolatablePropertyValue(maximumTextWidth); + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenPropertyValue(maximumTextWidth); self.rawLayer->setTextMaxWidth(mbglValue); } @@ -494,9 +494,9 @@ namespace mbgl { auto propertyValue = self.rawLayer->getTextMaxWidth(); if (propertyValue.isUndefined()) { - return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(self.rawLayer->getDefaultTextMaxWidth()); + return MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenStyleValue(self.rawLayer->getDefaultTextMaxWidth()); } - return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(propertyValue); + return MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenStyleValue(propertyValue); } - (void)setTextMaxWidth:(MGLStyleValue<NSNumber *> *)textMaxWidth { @@ -728,7 +728,7 @@ namespace mbgl { - (void)setTextLetterSpacing:(MGLStyleValue<NSNumber *> *)textLetterSpacing { MGLAssertStyleLayerIsValid(); - auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toInterpolatablePropertyValue(textLetterSpacing); + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenPropertyValue(textLetterSpacing); self.rawLayer->setTextLetterSpacing(mbglValue); } @@ -737,9 +737,9 @@ namespace mbgl { auto propertyValue = self.rawLayer->getTextLetterSpacing(); if (propertyValue.isUndefined()) { - return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(self.rawLayer->getDefaultTextLetterSpacing()); + return MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenStyleValue(self.rawLayer->getDefaultTextLetterSpacing()); } - return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(propertyValue); + return MGLStyleValueTransformer<float, NSNumber *>().toDataDrivenStyleValue(propertyValue); } - (void)setTextLineHeight:(MGLStyleValue<NSNumber *> *)textLineHeight { diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.h b/platform/darwin/src/MGLVectorSource+MGLAdditions.h index 1e25ee5a60..43b0aba747 100644 --- a/platform/macos/app/MGLVectorSource+MBXAdditions.h +++ b/platform/darwin/src/MGLVectorSource+MGLAdditions.h @@ -2,7 +2,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface MGLVectorSource (MBXAdditions) +@interface MGLVectorSource (MGLAdditions) + (NSString *)preferredMapboxStreetsLanguage; diff --git a/platform/macos/app/MGLVectorSource+MBXAdditions.m b/platform/darwin/src/MGLVectorSource+MGLAdditions.m index 323bc74366..a305388117 100644 --- a/platform/macos/app/MGLVectorSource+MBXAdditions.m +++ b/platform/darwin/src/MGLVectorSource+MGLAdditions.m @@ -1,6 +1,6 @@ -#import "MGLVectorSource+MBXAdditions.h" +#import "MGLVectorSource+MGLAdditions.h" -@implementation MGLVectorSource (MBXAdditions) +@implementation MGLVectorSource (MGLAdditions) + (NS_SET_OF(NSString *) *)mapboxStreetsLanguages { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview diff --git a/platform/darwin/src/MGLVectorSource.mm b/platform/darwin/src/MGLVectorSource.mm index 431e0c250c..b1bda56f2d 100644 --- a/platform/darwin/src/MGLVectorSource.mm +++ b/platform/darwin/src/MGLVectorSource.mm @@ -64,8 +64,8 @@ } std::vector<mbgl::Feature> features; - if (self.style) { - features = self.style.mapView.renderer->querySourceFeatures(self.rawSource->getID(), { optionalSourceLayerIDs, optionalFilter }); + if (self.mapView) { + features = self.mapView.renderer->querySourceFeatures(self.rawSource->getID(), { optionalSourceLayerIDs, optionalFilter }); } return MGLFeaturesFromMBGLFeatures(features); } diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index ae72b35d82..42c656f203 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -103,6 +103,18 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.source(withIdentifier: "pois")) } + + func testMGLPolyline() { + //#-example-code + let coordinates = [ + CLLocationCoordinate2D(latitude: 35.68476, longitude: -220.24257), + CLLocationCoordinate2D(latitude: 37.78428, longitude: -122.41310) + ] + let polyline = MGLPolyline(coordinates: coordinates, count: UInt(coordinates.count)) + //#-end-example-code + + XCTAssertNotNil(polyline) + } func testMGLImageSource() { //#-example-code diff --git a/platform/darwin/test/MGLSymbolStyleLayerTests.mm b/platform/darwin/test/MGLSymbolStyleLayerTests.mm index abbaef9159..1ac86dd402 100644 --- a/platform/darwin/test/MGLSymbolStyleLayerTests.mm +++ b/platform/darwin/test/MGLSymbolStyleLayerTests.mm @@ -736,7 +736,7 @@ MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; layer.maximumTextWidth = constantStyleValue; - mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, @"Setting maximumTextWidth to a constant value should update text-max-width."); XCTAssertEqualObjects(layer.maximumTextWidth, constantStyleValue, @@ -753,6 +753,29 @@ XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, @"maximumTextWidth should round-trip camera functions."); + functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; + layer.maximumTextWidth = functionStyleValue; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, + @"Setting maximumTextWidth to a source function should update text-max-width."); + XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, + @"maximumTextWidth should round-trip source functions."); + + functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; + layer.maximumTextWidth = functionStyleValue; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getTextMaxWidth(), propertyValue, + @"Setting maximumTextWidth to a composite function should update text-max-width."); + XCTAssertEqualObjects(layer.maximumTextWidth, functionStyleValue, + @"maximumTextWidth should round-trip composite functions."); layer.maximumTextWidth = nil; @@ -760,11 +783,6 @@ @"Unsetting maximumTextWidth should return text-max-width to the default value."); XCTAssertEqualObjects(layer.maximumTextWidth, defaultStyleValue, @"maximumTextWidth should return the default value after being unset."); - - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumTextWidth = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.maximumTextWidth = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); } // symbol-avoid-edges @@ -1168,7 +1186,7 @@ MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff]; layer.textLetterSpacing = constantStyleValue; - mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, @"Setting textLetterSpacing to a constant value should update text-letter-spacing."); XCTAssertEqualObjects(layer.textLetterSpacing, constantStyleValue, @@ -1185,6 +1203,29 @@ XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, @"textLetterSpacing should round-trip camera functions."); + functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential sourceStops:@{@18: constantStyleValue} attributeName:@"keyName" options:nil]; + layer.textLetterSpacing = functionStyleValue; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, + @"Setting textLetterSpacing to a source function should update text-letter-spacing."); + XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, + @"textLetterSpacing should round-trip source functions."); + + functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeExponential compositeStops:@{@10: @{@18: constantStyleValue}} attributeName:@"keyName" options:nil]; + layer.textLetterSpacing = functionStyleValue; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getTextLetterSpacing(), propertyValue, + @"Setting textLetterSpacing to a composite function should update text-letter-spacing."); + XCTAssertEqualObjects(layer.textLetterSpacing, functionStyleValue, + @"textLetterSpacing should round-trip composite functions."); layer.textLetterSpacing = nil; @@ -1192,11 +1233,6 @@ @"Unsetting textLetterSpacing should return text-letter-spacing to the default value."); XCTAssertEqualObjects(layer.textLetterSpacing, defaultStyleValue, @"textLetterSpacing should return the default value after being unset."); - - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textLetterSpacing = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); - functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil]; - XCTAssertThrowsSpecificNamed(layer.textLetterSpacing = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it"); } // text-line-height diff --git a/platform/default/mbgl/gl/headless_frontend.cpp b/platform/default/mbgl/gl/headless_frontend.cpp index ad03706be7..5d2932258a 100644 --- a/platform/default/mbgl/gl/headless_frontend.cpp +++ b/platform/default/mbgl/gl/headless_frontend.cpp @@ -5,11 +5,11 @@ namespace mbgl { -HeadlessFrontend::HeadlessFrontend(float pixelRatio_, FileSource& fileSource, Scheduler& scheduler) - : HeadlessFrontend({ 256, 256 }, pixelRatio_, fileSource, scheduler) { +HeadlessFrontend::HeadlessFrontend(float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir) + : HeadlessFrontend({ 256, 256 }, pixelRatio_, fileSource, scheduler, programCacheDir) { } -HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fileSource, Scheduler& scheduler) +HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fileSource, Scheduler& scheduler, const optional<std::string> programCacheDir) : size(size_), pixelRatio(pixelRatio_), backend({ static_cast<uint32_t>(size.width * pixelRatio), @@ -20,7 +20,7 @@ HeadlessFrontend::HeadlessFrontend(Size size_, float pixelRatio_, FileSource& fi renderer->render(*updateParameters); } }), - renderer(std::make_unique<Renderer>(backend, pixelRatio, fileSource, scheduler)) { + renderer(std::make_unique<Renderer>(backend, pixelRatio, fileSource, scheduler, GLContextMode::Unique, programCacheDir)) { } HeadlessFrontend::~HeadlessFrontend() = default; diff --git a/platform/default/mbgl/gl/headless_frontend.hpp b/platform/default/mbgl/gl/headless_frontend.hpp index 18d0d2527b..33503bc13b 100644 --- a/platform/default/mbgl/gl/headless_frontend.hpp +++ b/platform/default/mbgl/gl/headless_frontend.hpp @@ -3,6 +3,7 @@ #include <mbgl/renderer/renderer_frontend.hpp> #include <mbgl/gl/headless_backend.hpp> #include <mbgl/util/async_task.hpp> +#include <mbgl/util/optional.hpp> #include <memory> @@ -16,8 +17,8 @@ class Map; class HeadlessFrontend : public RendererFrontend { public: - HeadlessFrontend(float pixelRatio_, FileSource&, Scheduler&); - HeadlessFrontend(Size, float pixelRatio_, FileSource&, Scheduler&); + HeadlessFrontend(float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}); + HeadlessFrontend(Size, float pixelRatio_, FileSource&, Scheduler&, const optional<std::string> programCacheDir = {}); ~HeadlessFrontend() override; void reset() override; diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp new file mode 100644 index 0000000000..95c46344fe --- /dev/null +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -0,0 +1,76 @@ +#include <mbgl/map/map_snapshotter.hpp> + +#include <mbgl/actor/actor_ref.hpp> +#include <mbgl/gl/headless_frontend.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/style/style.hpp> +#include <mbgl/util/event.hpp> + +namespace mbgl { + +class MapSnapshotter::Impl { +public: + Impl(FileSource&, + Scheduler&, + const std::string& styleURL, + const Size&, + const float pixelRatio, + const CameraOptions&, + const optional<LatLngBounds> region, + const optional<std::string> programCacheDir); + + void snapshot(ActorRef<MapSnapshotter::Callback>); + +private: + HeadlessFrontend frontend; + Map map; +}; + +MapSnapshotter::Impl::Impl(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size& size, + const float pixelRatio, + const CameraOptions& cameraOptions, + const optional<LatLngBounds> region, + const optional<std::string> programCacheDir) + : frontend(size, pixelRatio, fileSource, scheduler, programCacheDir) + , map(frontend, MapObserver::nullObserver(), size, pixelRatio, fileSource, scheduler, MapMode::Still) { + + map.getStyle().loadURL(styleURL); + + map.jumpTo(cameraOptions); + + // Set region, if specified + if (region) { + mbgl::EdgeInsets insets = { 0, 0, 0, 0 }; + std::vector<LatLng> latLngs = { region->southwest(), region->northeast() }; + map.jumpTo(map.cameraForLatLngs(latLngs, insets)); + } +} + +void MapSnapshotter::Impl::snapshot(ActorRef<MapSnapshotter::Callback> callback) { + map.renderStill([this, callback = std::move(callback)] (std::exception_ptr error) mutable { + callback.invoke(&MapSnapshotter::Callback::operator(), error, error ? PremultipliedImage() : frontend.readStillImage()); + }); +} + +MapSnapshotter::MapSnapshotter(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size& size, + const float pixelRatio, + const CameraOptions& cameraOptions, + const optional<LatLngBounds> region, + const optional<std::string> programCacheDir) + : impl(std::make_unique<util::Thread<MapSnapshotter::Impl>>("Map Snapshotter", fileSource, scheduler, styleURL, size, pixelRatio, cameraOptions, region, programCacheDir)) { +} + +MapSnapshotter::~MapSnapshotter() = default; + +void MapSnapshotter::snapshot(ActorRef<MapSnapshotter::Callback> callback) { + impl->actor().invoke(&Impl::snapshot, std::move(callback)); +} + +} // namespace mbgl diff --git a/platform/default/mbgl/map/map_snapshotter.hpp b/platform/default/mbgl/map/map_snapshotter.hpp new file mode 100644 index 0000000000..0afa848fd8 --- /dev/null +++ b/platform/default/mbgl/map/map_snapshotter.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include <mbgl/util/image.hpp> +#include <mbgl/util/thread.hpp> +#include <mbgl/util/optional.hpp> + +#include <exception> +#include <string> +#include <functional> + +namespace mbgl { + +template<class> class ActorRef; +struct CameraOptions; +class FileSource; +class Size; +class LatLngBounds; + +class MapSnapshotter { +public: + MapSnapshotter(FileSource& fileSource, + Scheduler& scheduler, + const std::string& styleURL, + const Size&, + const float pixelRatio, + const CameraOptions&, + const optional<LatLngBounds> region, + const optional<std::string> cacheDir = {}); + + ~MapSnapshotter(); + + using Callback = std::function<void (std::exception_ptr, PremultipliedImage)>; + void snapshot(ActorRef<Callback>); + +private: + class Impl; + std::unique_ptr<util::Thread<Impl>> impl; +}; + +} // namespace mbgl diff --git a/platform/default/mbgl/storage/offline.cpp b/platform/default/mbgl/storage/offline.cpp index fd2d47819b..644684c8a6 100644 --- a/platform/default/mbgl/storage/offline.cpp +++ b/platform/default/mbgl/storage/offline.cpp @@ -1,6 +1,7 @@ #include <mbgl/storage/offline.hpp> #include <mbgl/util/tile_cover.hpp> #include <mbgl/util/tileset.hpp> +#include <mbgl/util/projection.hpp> #include <rapidjson/document.h> #include <rapidjson/stringbuffer.h> @@ -24,17 +25,11 @@ OfflineTilePyramidRegionDefinition::OfflineTilePyramidRegionDefinition( } std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { - double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); - double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); - - assert(minZ >= 0); - assert(maxZ >= 0); - assert(minZ < std::numeric_limits<uint8_t>::max()); - assert(maxZ < std::numeric_limits<uint8_t>::max()); + const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); std::vector<CanonicalTileID> result; - for (uint8_t z = minZ; z <= maxZ; z++) { + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { for (const auto& tile : util::tileCover(bounds, z)) { result.emplace_back(tile.canonical); } @@ -43,6 +38,28 @@ std::vector<CanonicalTileID> OfflineTilePyramidRegionDefinition::tileCover(Sourc return result; } +unsigned long OfflineTilePyramidRegionDefinition::tileCount(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { + + const Range<uint8_t> clampedZoomRange = coveringZoomRange(type, tileSize, zoomRange); + unsigned long result = 0;; + for (uint8_t z = clampedZoomRange.min; z <= clampedZoomRange.max; z++) { + result += util::tileCount(bounds, z, tileSize); + } + + return result; +} + +Range<uint8_t> OfflineTilePyramidRegionDefinition::coveringZoomRange(SourceType type, uint16_t tileSize, const Range<uint8_t>& zoomRange) const { + double minZ = std::max<double>(util::coveringZoomLevel(minZoom, type, tileSize), zoomRange.min); + double maxZ = std::min<double>(util::coveringZoomLevel(maxZoom, type, tileSize), zoomRange.max); + + assert(minZ >= 0); + assert(maxZ >= 0); + assert(minZ < std::numeric_limits<uint8_t>::max()); + assert(maxZ < std::numeric_limits<uint8_t>::max()); + return { static_cast<uint8_t>(minZ), static_cast<uint8_t>(maxZ) }; +} + OfflineRegionDefinition decodeOfflineRegionDefinition(const std::string& region) { rapidjson::GenericDocument<rapidjson::UTF8<>, rapidjson::CrtAllocator> doc; doc.Parse<0>(region.c_str()); diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index 7f0001f64b..ff61114888 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -80,7 +80,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { auto handleTiledSource = [&] (const variant<std::string, Tileset>& urlOrTileset, const uint16_t tileSize) { if (urlOrTileset.is<Tileset>()) { result.requiredResourceCount += - definition.tileCover(type, tileSize, urlOrTileset.get<Tileset>().zoomRange).size(); + definition.tileCount(type, tileSize, urlOrTileset.get<Tileset>().zoomRange); } else { result.requiredResourceCount += 1; const auto& url = urlOrTileset.get<std::string>(); @@ -90,7 +90,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { optional<Tileset> tileset = style::conversion::convertJSON<Tileset>(*sourceResponse->data, error); if (tileset) { result.requiredResourceCount += - definition.tileCover(type, tileSize, (*tileset).zoomRange).size(); + definition.tileCount(type, tileSize, (*tileset).zoomRange); } } else { result.requiredResourceCountIsPrecise = false; diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 9d6259d313..5c74dcf223 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -14,7 +14,22 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) -## 3.6.0 +## 3.6.2 + +* Added an `MGLStyle.localizesLabels` property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +* Added an additional camera method to MGLMapView that accepts an edge padding parameter. ([#9651](https://github.com/mapbox/mapbox-gl-native/pull/9651)) +* Fixed an issue with the scaling of the user location annotation’s horizontal accuracy indicator. ([#9721](https://github.com/mapbox/mapbox-gl-native/pull/9721)) +* Fixed an issue that caused `-[MGLShapeSource featuresMatchingPredicate:]` and `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` to always return an empty array. ([#9784](https://github.com/mapbox/mapbox-gl-native/pull/9784)) + +## 3.6.1 - July 28, 2017 + +* Reduced the size of the dynamic framework by optimizing symbol visibility. ([#7604](https://github.com/mapbox/mapbox-gl-native/pull/7604)) +* Fixed an issue where the attribution button would have its custom tint color reset when the map view received a tint color change notification, such as when an alert controller was presented. ([#9598](https://github.com/mapbox/mapbox-gl-native/pull/9598)) +* Improved the behavior of zoom gestures when the map reaches the minimum zoom limit. ([#9626](https://github.com/mapbox/mapbox-gl-native/pull/9626)) +* Fixed an issue where tilt gesture was triggered with two fingers aligned vertically and panning down. ([#9571](https://github.com/mapbox/mapbox-gl-native/pull/9571)) +* Bitcode symbol maps (.bcsymbolmap files) are now included with the dynamic framework. ([#9613](https://github.com/mapbox/mapbox-gl-native/pull/9613)) + +## 3.6.0 - June 29, 2017 ### Packaging diff --git a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec index c7b14ead3b..b5f9af2e61 100644 --- a/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec +++ b/platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.6.0-alpha.1' + version = '3.6.2' m.name = 'Mapbox-iOS-SDK-nightly-dynamic' m.version = "#{version}-nightly" diff --git a/platform/ios/Mapbox-iOS-SDK-symbols.podspec b/platform/ios/Mapbox-iOS-SDK-symbols.podspec index d2a686f1fb..0fcd335071 100644 --- a/platform/ios/Mapbox-iOS-SDK-symbols.podspec +++ b/platform/ios/Mapbox-iOS-SDK-symbols.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.6.0' + version = '3.6.2' m.name = 'Mapbox-iOS-SDK-symbols' m.version = "#{version}-symbols" diff --git a/platform/ios/Mapbox-iOS-SDK.podspec b/platform/ios/Mapbox-iOS-SDK.podspec index 55e8791b4c..0224048ff5 100644 --- a/platform/ios/Mapbox-iOS-SDK.podspec +++ b/platform/ios/Mapbox-iOS-SDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |m| - version = '3.6.0' + version = '3.6.2' m.name = 'Mapbox-iOS-SDK' m.version = version diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json b/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json index 1eeddba9b9..228b81a818 100644 --- a/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json +++ b/platform/ios/app/Assets.xcassets/settings.imageset/Contents.json @@ -2,24 +2,11 @@ "images" : [ { "idiom" : "universal", - "filename" : "settings.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "settings@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" + "filename" : "settings.pdf" } ], "info" : { "version" : 1, "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" } }
\ No newline at end of file diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf b/platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf Binary files differnew file mode 100644 index 0000000000..46aa7443f0 --- /dev/null +++ b/platform/ios/app/Assets.xcassets/settings.imageset/settings.pdf diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/settings.png b/platform/ios/app/Assets.xcassets/settings.imageset/settings.png Binary files differdeleted file mode 100644 index 5d7643eef5..0000000000 --- a/platform/ios/app/Assets.xcassets/settings.imageset/settings.png +++ /dev/null diff --git a/platform/ios/app/Assets.xcassets/settings.imageset/settings@2x.png b/platform/ios/app/Assets.xcassets/settings.imageset/settings@2x.png Binary files differdeleted file mode 100644 index 2bb9f0ebad..0000000000 --- a/platform/ios/app/Assets.xcassets/settings.imageset/settings@2x.png +++ /dev/null diff --git a/platform/ios/app/Default-568h@2x.png b/platform/ios/app/Default-568h@2x.png Binary files differdeleted file mode 100644 index 0891b7aabf..0000000000 --- a/platform/ios/app/Default-568h@2x.png +++ /dev/null diff --git a/platform/ios/app/Info.plist b/platform/ios/app/Info.plist index d5b6825422..167e66fa09 100644 --- a/platform/ios/app/Info.plist +++ b/platform/ios/app/Info.plist @@ -51,5 +51,16 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> + <key>UIApplicationShortcutItems</key> + <array> + <dict> + <key>UIApplicationShortcutItemTitle</key> + <string>Settings</string> + <key>UIApplicationShortcutItemType</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER).settings</string> + <key>UIApplicationShortcutItemIconFile</key> + <string>settings</string> + </dict> + </array> </dict> </plist> diff --git a/platform/ios/app/LaunchScreen.storyboard b/platform/ios/app/LaunchScreen.storyboard index 323bd43177..299e186886 100644 --- a/platform/ios/app/LaunchScreen.storyboard +++ b/platform/ios/app/LaunchScreen.storyboard @@ -1,8 +1,12 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM"> +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> + <device id="retina4_7" orientation="portrait"> + <adaptation id="fullscreen"/> + </device> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--View Controller--> @@ -14,9 +18,9 @@ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/> </layoutGuides> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> - <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </view> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> diff --git a/platform/ios/app/MBXAppDelegate.m b/platform/ios/app/MBXAppDelegate.m index c2834bfa7f..1934f4912b 100644 --- a/platform/ios/app/MBXAppDelegate.m +++ b/platform/ios/app/MBXAppDelegate.m @@ -26,4 +26,22 @@ NSString * const MBXMapboxAccessTokenDefaultsKey = @"MBXMapboxAccessToken"; return YES; } +#pragma mark - Quick actions + +- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler { + completionHandler([self handleShortcut:shortcutItem]); +} + +- (BOOL)handleShortcut:(UIApplicationShortcutItem *)shortcut { + if ([[shortcut.type componentsSeparatedByString:@"."].lastObject isEqual:@"settings"]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; + }); + + return YES; + } + + return NO; +} + @end diff --git a/platform/ios/app/MBXSnapshotsViewController.h b/platform/ios/app/MBXSnapshotsViewController.h new file mode 100644 index 0000000000..f791602e98 --- /dev/null +++ b/platform/ios/app/MBXSnapshotsViewController.h @@ -0,0 +1,5 @@ +#import <UIKit/UIKit.h> + +@interface MBXSnapshotsViewController : UIViewController + +@end diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m new file mode 100644 index 0000000000..d26479f085 --- /dev/null +++ b/platform/ios/app/MBXSnapshotsViewController.m @@ -0,0 +1,66 @@ +#import "MBXSnapshotsViewController.h" + +#import <Mapbox/Mapbox.h> + +@interface MBXSnapshotsViewController () + +// Top row +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewTL; +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewTM; +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewTR; + +// Bottom row +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewBL; +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewBM; +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageViewBR; + +@end + +@implementation MBXSnapshotsViewController { + // Top row + MGLMapSnapshotter* snapshotterTL; + MGLMapSnapshotter* snapshotterTM; + MGLMapSnapshotter* snapshotterTR; + + // Bottom row + MGLMapSnapshotter* snapshotterBL; + MGLMapSnapshotter* snapshotterBM; + MGLMapSnapshotter* snapshotterBR; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Start snapshotters + snapshotterTL = [self startSnapshotterForImageView:_snapshotImageViewTL coordinates:CLLocationCoordinate2DMake(37.7184, -122.4365)]; + snapshotterTM = [self startSnapshotterForImageView:_snapshotImageViewTM coordinates:CLLocationCoordinate2DMake(38.8936, -77.0146)]; + snapshotterTR = [self startSnapshotterForImageView:_snapshotImageViewTR coordinates:CLLocationCoordinate2DMake(-13.1356, -74.2442)]; + + snapshotterBL = [self startSnapshotterForImageView:_snapshotImageViewBL coordinates:CLLocationCoordinate2DMake(52.5072, 13.4247)]; + snapshotterBM = [self startSnapshotterForImageView:_snapshotImageViewBM coordinates:CLLocationCoordinate2DMake(60.2118, 24.6754)]; + snapshotterBR = [self startSnapshotterForImageView:_snapshotImageViewBR coordinates:CLLocationCoordinate2DMake(31.2780, 121.4286)]; +} + +- (MGLMapSnapshotter*) startSnapshotterForImageView:(UIImageView*) imageView coordinates:(CLLocationCoordinate2D) coordinates { + // Create snapshot options + MGLMapCamera* mapCamera = [[MGLMapCamera alloc] init]; + mapCamera.pitch = 20; + mapCamera.centerCoordinate = coordinates; + MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:[NSURL URLWithString:@"mapbox://styles/mapbox/traffic-day-v2"] camera:mapCamera size:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)]; + options.zoom = 10; + + // Create and start the snapshotter + MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; + [snapshotter startWithCompletionHandler: ^(UIImage *image, NSError *error) { + if (error) { + NSLog(@"Could not load snapshot: %@", [error localizedDescription]); + } else { + imageView.image = image; + } + }]; + + return snapshotter; +} + + +@end diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 29c5c65012..e7825fac9d 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -34,7 +34,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsCoreRenderingRows) { MBXSettingsCoreRenderingTimestamps, MBXSettingsCoreRenderingCollisionBoxes, MBXSettingsCoreRenderingOverdrawVisualization, - MBXSettingsCoreRenderingToggleTwoMaps, }; typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { @@ -48,6 +47,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsAnnotationsRows) { MBXSettingsAnnotationsTestShapes, MBXSettingsAnnotationsCustomCallout, MBXSettingsAnnotationsQueryAnnotations, + MBXSettingsAnnotationsCustomUserDot, MBXSettingsAnnotationsRemoveAnnotations, }; @@ -73,7 +73,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingVectorSource, MBXSettingsRuntimeStylingRasterSource, MBXSettingsRuntimeStylingImageSource, - MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, }; @@ -81,9 +80,11 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { MBXSettingsMiscellaneousShowReuseQueueStats = 0, MBXSettingsMiscellaneousWorldTour, - MBXSettingsMiscellaneousCustomUserDot, MBXSettingsMiscellaneousShowZoomLevel, MBXSettingsMiscellaneousScrollView, + MBXSettingsMiscellaneousToggleTwoMaps, + MBXSettingsMiscellaneousCountryLabels, + MBXSettingsMiscellaneousShowSnapshots, MBXSettingsMiscellaneousPrintLogFile, MBXSettingsMiscellaneousDeleteLogFile, }; @@ -305,8 +306,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { (debugMask & MGLMapDebugCollisionBoxesMask ? @"Hide" :@"Show")], [NSString stringWithFormat:@"%@ Overdraw Visualization", (debugMask & MGLMapDebugOverdrawVisualizationMask ? @"Hide" :@"Show")], - [NSString stringWithFormat:@"%@ Second Map", - ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], ]]; break; case MBXSettingsAnnotations: @@ -321,6 +320,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Add Test Shapes", @"Add Point With Custom Callout", @"Query Annotations", + [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], @"Remove Annotations", ]]; break; @@ -347,7 +347,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @"Style Vector Source", @"Style Raster Source", @"Style Image Source", - [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", ]]; @@ -356,9 +355,11 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [settingsTitles addObjectsFromArray:@[ [NSString stringWithFormat:@"%@ Reuse Queue Stats", (_reuseQueueStatsEnabled ? @"Hide" :@"Show")], @"Start World Tour", - [NSString stringWithFormat:@"%@ Custom User Dot", (_customUserLocationAnnnotationEnabled ? @"Disable" : @"Enable")], [NSString stringWithFormat:@"%@ Zoom Level", (_showZoomLevelEnabled ? @"Hide" :@"Show")], @"Embedded Map View", + [NSString stringWithFormat:@"%@ Second Map", ([self.view viewWithTag:2] == nil ? @"Show" : @"Hide")], + [NSString stringWithFormat:@"Show Labels in %@", (_usingLocaleBasedCountryLabels ? @"Default Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], + @"Show Snapshots" ]]; if (self.debugLoggingEnabled) @@ -403,81 +404,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { case MBXSettingsCoreRenderingOverdrawVisualization: self.mapView.debugMask ^= MGLMapDebugOverdrawVisualizationMask; break; - case MBXSettingsCoreRenderingToggleTwoMaps: - if ([self.view viewWithTag:2] == nil) { - MGLMapView *secondMapView = [[MGLMapView alloc] initWithFrame: - CGRectMake(0, self.view.bounds.size.height / 2, - self.view.bounds.size.width, self.view.bounds.size.height / 2)]; - secondMapView.translatesAutoresizingMaskIntoConstraints = false; - secondMapView.tag = 2; - for (NSLayoutConstraint *constraint in self.view.constraints) - { - if ((constraint.firstItem == self.mapView && constraint.firstAttribute == NSLayoutAttributeBottom) || - (constraint.secondItem == self.mapView && constraint.secondAttribute == NSLayoutAttributeBottom)) - { - [self.view removeConstraint:constraint]; - break; - } - } - [self.view addSubview:secondMapView]; - [self.view addConstraints:@[ - [NSLayoutConstraint constraintWithItem:self.mapView - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterY - multiplier:1 - constant:0], - [NSLayoutConstraint constraintWithItem:secondMapView - attribute:NSLayoutAttributeCenterX - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterX - multiplier:1 - constant:0], - [NSLayoutConstraint constraintWithItem:secondMapView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeWidth - multiplier:1 - constant:0], - [NSLayoutConstraint constraintWithItem:secondMapView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterY - multiplier:1 - constant:0], - [NSLayoutConstraint constraintWithItem:secondMapView - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - multiplier:1 - constant:0], - ]]; - } else { - NSMutableArray *constraintsToRemove = [NSMutableArray array]; - MGLMapView *secondMapView = (MGLMapView *)[self.view viewWithTag:2]; - for (NSLayoutConstraint *constraint in self.view.constraints) - { - if (constraint.firstItem == secondMapView || constraint.secondItem == secondMapView) - { - [constraintsToRemove addObject:constraint]; - } - } - [self.view removeConstraints:constraintsToRemove]; - [secondMapView removeFromSuperview]; - [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.bottomLayoutGuide - attribute:NSLayoutAttributeTop - multiplier:1 - constant:0]]; - } - break; default: NSAssert(NO, @"All core rendering setting rows should be implemented"); break; @@ -515,6 +441,9 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { break; case MBXSettingsAnnotationsQueryAnnotations: [self testQueryPointAnnotations]; + case MBXSettingsAnnotationsCustomUserDot: + break; + [self toggleCustomUserDot]; break; case MBXSettingsAnnotationsRemoveAnnotations: [self.mapView removeAnnotations:self.mapView.annotations]; @@ -590,9 +519,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { case MBXSettingsRuntimeStylingImageSource: [self styleImageSource]; break; - case MBXSettingsRuntimeStylingCountryLabels: - [self styleCountryLabelsLanguage]; - break; case MBXSettingsRuntimeStylingRouteLine: [self styleRouteLine]; break; @@ -607,12 +533,12 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { case MBXSettingsMiscellaneous: switch (indexPath.row) { + case MBXSettingsMiscellaneousCountryLabels: + [self styleCountryLabelsLanguage]; + break; case MBXSettingsMiscellaneousWorldTour: [self startWorldTour]; break; - case MBXSettingsMiscellaneousCustomUserDot: - [self toggleCustomUserDot]; - break; case MBXSettingsMiscellaneousPrintLogFile: [self printTelemetryLogFile]; break; @@ -640,6 +566,86 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self.navigationController pushViewController:embeddedMapViewController animated:YES]; break; } + case MBXSettingsMiscellaneousToggleTwoMaps: + if ([self.view viewWithTag:2] == nil) { + MGLMapView *secondMapView = [[MGLMapView alloc] initWithFrame: + CGRectMake(0, self.view.bounds.size.height / 2, + self.view.bounds.size.width, self.view.bounds.size.height / 2)]; + secondMapView.translatesAutoresizingMaskIntoConstraints = false; + secondMapView.tag = 2; + for (NSLayoutConstraint *constraint in self.view.constraints) + { + if ((constraint.firstItem == self.mapView && constraint.firstAttribute == NSLayoutAttributeBottom) || + (constraint.secondItem == self.mapView && constraint.secondAttribute == NSLayoutAttributeBottom)) + { + [self.view removeConstraint:constraint]; + break; + } + } + [self.view addSubview:secondMapView]; + [self.view addConstraints:@[ + [NSLayoutConstraint constraintWithItem:self.mapView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1 + constant:0], + [NSLayoutConstraint constraintWithItem:secondMapView + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:0], + [NSLayoutConstraint constraintWithItem:secondMapView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeWidth + multiplier:1 + constant:0], + [NSLayoutConstraint constraintWithItem:secondMapView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1 + constant:0], + [NSLayoutConstraint constraintWithItem:secondMapView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.bottomLayoutGuide + attribute:NSLayoutAttributeTop + multiplier:1 + constant:0], + ]]; + } else { + NSMutableArray *constraintsToRemove = [NSMutableArray array]; + MGLMapView *secondMapView = (MGLMapView *)[self.view viewWithTag:2]; + for (NSLayoutConstraint *constraint in self.view.constraints) + { + if (constraint.firstItem == secondMapView || constraint.secondItem == secondMapView) + { + [constraintsToRemove addObject:constraint]; + } + } + [self.view removeConstraints:constraintsToRemove]; + [secondMapView removeFromSuperview]; + [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.bottomLayoutGuide + attribute:NSLayoutAttributeTop + multiplier:1 + constant:0]]; + } + break; + case MBXSettingsMiscellaneousShowSnapshots: + { + [self performSegueWithIdentifier:@"ShowSnapshots" sender:nil]; + break; + } default: NSAssert(NO, @"All miscellaneous setting rows should be implemented"); break; @@ -1303,8 +1309,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleRasterSource { - NSArray *tileURLTemplates = @[@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}.jpg"]; - MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:tileURLTemplates options:@{ + NSString *tileURL = [NSString stringWithFormat:@"https://stamen-tiles.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}%@.jpg", UIScreen.mainScreen.nativeScale > 1 ? @"@2x" : @""]; + MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"style-raster-source-id" tileURLTemplates:@[tileURL] options:@{ MGLTileSourceOptionTileSize: @256, }]; [self.mapView.style addSource:rasterSource]; @@ -1348,12 +1354,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { -(void)styleCountryLabelsLanguage { - NSArray<NSString *> *labelLayers = @[ - @"country-label-lg", - @"country-label-md", - @"country-label-sm", - ]; - [self styleLabelLanguageForLayersNamed:labelLayers]; + _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; + self.mapView.style.localizesLabels = _usingLocaleBasedCountryLabels; } - (void)styleRouteLine @@ -1436,39 +1438,6 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self.mapView.style addLayer:fillStyleLayer]; } -- (void)styleLabelLanguageForLayersNamed:(NSArray<NSString *> *)layers -{ - _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; - NSString *bestLanguageForUser = [NSString stringWithFormat:@"{name_%@}", [self bestLanguageForUser]]; - NSString *language = _usingLocaleBasedCountryLabels ? bestLanguageForUser : @"{name}"; - - for (NSString *layerName in layers) { - MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:layerName]; - - if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { - if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { - MGLConstantStyleValue *label = (MGLConstantStyleValue<NSString *> *)layer.text; - if ([label.rawValue hasPrefix:@"{name"]) { - layer.text = [MGLStyleValue valueWithRawValue:language]; - } - } - else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { - MGLCameraStyleFunction *function = (MGLCameraStyleFunction<NSString *> *)layer.text; - NSMutableDictionary *stops = function.stops.mutableCopy; - [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue<NSString *> *stop, BOOL *done) { - if ([stop.rawValue hasPrefix:@"{name"]) { - stops[zoomLevel] = [MGLStyleValue<NSString *> valueWithRawValue:language]; - } - }]; - function.stops = stops; - layer.text = function; - } - } else { - NSLog(@"%@ is not a symbol style layer", layerName); - } - } -} - - (NSString *)bestLanguageForUser { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview @@ -1686,7 +1655,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [sender setAccessibilityValue:nextAccessibilityValue]; } -#pragma mark - Map Delegate +#pragma mark - MGLMapViewDelegate - (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation { diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard index 40198146ab..c7bcd6e0f0 100644 --- a/platform/ios/app/Main.storyboard +++ b/platform/ios/app/Main.storyboard @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff"> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="PSe-Ot-7Ff"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> <capability name="Navigation items with more than one left or right bar item" minToolsVersion="7.0"/> @@ -96,6 +96,7 @@ <connections> <outlet property="hudLabel" destination="58y-pX-YyB" id="MEh-ir-3IH"/> <outlet property="mapView" destination="kNe-zV-9ha" id="VNR-WO-1q4"/> + <segue destination="zvf-Qd-4Ru" kind="show" identifier="ShowSnapshots" id="hzX-Jp-UJq"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="AAd-8J-9UU" userLabel="First Responder" sceneMemberID="firstResponder"/> @@ -352,6 +353,81 @@ </objects> <point key="canvasLocation" x="594.39999999999998" y="1083.5082458770617"/> </scene> + <!--Snapshots View Controller--> + <scene sceneID="Ooh-2U-4Bz"> + <objects> + <viewController id="zvf-Qd-4Ru" customClass="MBXSnapshotsViewController" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="ZLI-ej-4Bs"/> + <viewControllerLayoutGuide type="bottom" id="fiS-dq-r4S"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="Jxm-v6-zI0"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="miZ-Fw-EWq" userLabel="Image View TL"> + <rect key="frame" x="0.0" y="64" width="125" height="301.5"/> + </imageView> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="XuN-T4-Z83" userLabel="Image View TM"> + <rect key="frame" x="125" y="64" width="125" height="301.5"/> + </imageView> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="ykR-Ku-i9l" userLabel="Image View TR"> + <rect key="frame" x="250" y="64" width="125" height="301.5"/> + </imageView> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="TL0-V8-T2F" userLabel="Image View BL"> + <rect key="frame" x="0.0" y="365.5" width="125" height="301.5"/> + </imageView> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="eMy-JU-rq4" userLabel="Image View BM"> + <rect key="frame" x="125" y="365.5" width="125" height="301.5"/> + </imageView> + <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zT0-3J-0xw" userLabel="Image View BR"> + <rect key="frame" x="250" y="365.5" width="125" height="301.5"/> + </imageView> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <constraints> + <constraint firstItem="eMy-JU-rq4" firstAttribute="leading" secondItem="TL0-V8-T2F" secondAttribute="trailing" id="0xP-ii-cyV"/> + <constraint firstItem="eMy-JU-rq4" firstAttribute="top" secondItem="XuN-T4-Z83" secondAttribute="bottom" id="1HV-Tp-mUB"/> + <constraint firstItem="TL0-V8-T2F" firstAttribute="leading" secondItem="Jxm-v6-zI0" secondAttribute="leading" id="3fH-bn-5ND"/> + <constraint firstItem="miZ-Fw-EWq" firstAttribute="leading" secondItem="Jxm-v6-zI0" secondAttribute="leading" id="4yV-CW-c5n"/> + <constraint firstItem="fiS-dq-r4S" firstAttribute="top" secondItem="eMy-JU-rq4" secondAttribute="bottom" id="57P-Qo-M11"/> + <constraint firstItem="ykR-Ku-i9l" firstAttribute="top" secondItem="ZLI-ej-4Bs" secondAttribute="bottom" id="ARo-Nk-uVV"/> + <constraint firstAttribute="trailing" secondItem="ykR-Ku-i9l" secondAttribute="trailing" id="BRi-93-PGb"/> + <constraint firstItem="eMy-JU-rq4" firstAttribute="height" secondItem="miZ-Fw-EWq" secondAttribute="height" id="FqJ-zb-pkb"/> + <constraint firstItem="TL0-V8-T2F" firstAttribute="height" secondItem="miZ-Fw-EWq" secondAttribute="height" id="GrM-9L-dba"/> + <constraint firstItem="XuN-T4-Z83" firstAttribute="height" secondItem="miZ-Fw-EWq" secondAttribute="height" id="HSd-2T-Kz7"/> + <constraint firstAttribute="trailing" secondItem="zT0-3J-0xw" secondAttribute="trailing" id="HaC-la-079"/> + <constraint firstItem="fiS-dq-r4S" firstAttribute="top" secondItem="TL0-V8-T2F" secondAttribute="bottom" id="JgE-s8-RAh"/> + <constraint firstItem="zT0-3J-0xw" firstAttribute="top" secondItem="ykR-Ku-i9l" secondAttribute="bottom" id="KQm-ue-i3z"/> + <constraint firstItem="zT0-3J-0xw" firstAttribute="width" secondItem="miZ-Fw-EWq" secondAttribute="width" id="LUI-BF-66V"/> + <constraint firstItem="fiS-dq-r4S" firstAttribute="top" secondItem="zT0-3J-0xw" secondAttribute="bottom" id="MAe-3N-78O"/> + <constraint firstItem="TL0-V8-T2F" firstAttribute="width" secondItem="miZ-Fw-EWq" secondAttribute="width" id="OvH-2m-yli"/> + <constraint firstItem="XuN-T4-Z83" firstAttribute="top" secondItem="ZLI-ej-4Bs" secondAttribute="bottom" id="bzY-6Y-K80"/> + <constraint firstItem="XuN-T4-Z83" firstAttribute="leading" secondItem="miZ-Fw-EWq" secondAttribute="trailing" id="jhf-gz-4UF"/> + <constraint firstItem="eMy-JU-rq4" firstAttribute="width" secondItem="miZ-Fw-EWq" secondAttribute="width" id="l3m-tf-b1h"/> + <constraint firstItem="ykR-Ku-i9l" firstAttribute="leading" secondItem="XuN-T4-Z83" secondAttribute="trailing" id="oEV-Yi-iLs"/> + <constraint firstItem="TL0-V8-T2F" firstAttribute="top" secondItem="miZ-Fw-EWq" secondAttribute="bottom" id="oLW-zh-Fnk"/> + <constraint firstItem="miZ-Fw-EWq" firstAttribute="top" secondItem="ZLI-ej-4Bs" secondAttribute="bottom" id="qpD-mN-wfP"/> + <constraint firstItem="ykR-Ku-i9l" firstAttribute="height" secondItem="miZ-Fw-EWq" secondAttribute="height" id="sP4-HJ-Vgk"/> + <constraint firstItem="XuN-T4-Z83" firstAttribute="width" secondItem="miZ-Fw-EWq" secondAttribute="width" id="sTw-zD-Jid"/> + <constraint firstItem="zT0-3J-0xw" firstAttribute="height" secondItem="miZ-Fw-EWq" secondAttribute="height" id="t0u-eQ-Ail"/> + <constraint firstItem="ykR-Ku-i9l" firstAttribute="width" secondItem="miZ-Fw-EWq" secondAttribute="width" id="uQU-pB-kvq"/> + <constraint firstItem="zT0-3J-0xw" firstAttribute="leading" secondItem="eMy-JU-rq4" secondAttribute="trailing" id="w8M-MN-cmx"/> + </constraints> + </view> + <connections> + <outlet property="snapshotImageViewBL" destination="TL0-V8-T2F" id="e6C-dB-kHm"/> + <outlet property="snapshotImageViewBM" destination="eMy-JU-rq4" id="zeR-3U-EbH"/> + <outlet property="snapshotImageViewBR" destination="zT0-3J-0xw" id="6YR-lR-ela"/> + <outlet property="snapshotImageViewTL" destination="miZ-Fw-EWq" id="2Jj-kh-3Zw"/> + <outlet property="snapshotImageViewTM" destination="XuN-T4-Z83" id="MXY-7F-jB2"/> + <outlet property="snapshotImageViewTR" destination="ykR-Ku-i9l" id="aEL-Sg-RIW"/> + </connections> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="5xV-Ua-pqK" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="1365.5999999999999" y="1083.5082458770617"/> + </scene> </scenes> <resources> <image name="TrackingLocationOffMask.png" width="23" height="23"/> diff --git a/platform/ios/config.cmake b/platform/ios/config.cmake index 7ea1dddef7..4e873e73ea 100644 --- a/platform/ios/config.cmake +++ b/platform/ios/config.cmake @@ -57,6 +57,10 @@ macro(mbgl_platform_core) PRIVATE platform/default/mbgl/gl/headless_display.cpp PRIVATE platform/default/mbgl/gl/headless_display.hpp + # Snapshotting + PRIVATE platform/default/mbgl/map/map_snapshotter.cpp + PRIVATE platform/default/mbgl/map/map_snapshotter.hpp + # Thread pool PRIVATE platform/default/mbgl/util/shared_thread_pool.cpp PRIVATE platform/default/mbgl/util/shared_thread_pool.hpp diff --git a/platform/ios/docs/doc-README.md b/platform/ios/docs/doc-README.md index 6c2693cbbf..ebad32e04c 100644 --- a/platform/ios/docs/doc-README.md +++ b/platform/ios/docs/doc-README.md @@ -6,4 +6,4 @@ The Mapbox iOS SDK is an open-source framework for embedding interactive map vie For setup information, check out the [Mapbox iOS SDK homepage](https://www.mapbox.com/ios-sdk/). For detailed usage instructions, read “[First steps with the Mapbox iOS SDK](https://www.mapbox.com/help/first-steps-ios-sdk/)” and consult the [online examples](https://www.mapbox.com/ios-sdk/examples/). A [full changelog](https://github.com/mapbox/mapbox-gl-native/blob/master/platform/ios/CHANGELOG.md) is also available. -If you have any questions, please [contact our support team](https://www.mapbox.com/contact/). We welcome your [bug reports and feature requests](https://github.com/mapbox/mapbox-gl-native/issues/). +If you have any questions, please see [our help page](https://www.mapbox.com/help/). We welcome your [bug reports, feature requests, and contributions](https://github.com/mapbox/mapbox-gl-native/issues/). diff --git a/platform/ios/docs/pod-README.md b/platform/ios/docs/pod-README.md index 0a7edc5a41..2e5a78841c 100644 --- a/platform/ios/docs/pod-README.md +++ b/platform/ios/docs/pod-README.md @@ -96,4 +96,4 @@ class ViewController: UIViewController { Full API documentation is included in this package, within the `documentation` folder. For more details, read “[First steps with the Mapbox iOS SDK](https://www.mapbox.com/help/first-steps-ios-sdk/)” and consult the [online examples](https://www.mapbox.com/ios-sdk/examples/). -If you have any questions, please [contact our support team](https://www.mapbox.com/contact/). We welcome your [bug reports and feature requests](https://github.com/mapbox/mapbox-gl-native/issues/). +If you have any questions, please see [our help page](https://www.mapbox.com/help/). We welcome your [bug reports, feature requests, and contributions](https://github.com/mapbox/mapbox-gl-native/issues/). diff --git a/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings b/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings new file mode 100644 index 0000000000..3d761f2b97 --- /dev/null +++ b/platform/ios/framework/Settings.bundle/hu.lproj/Root.strings @@ -0,0 +1,3 @@ +"TELEMETRY_GROUP_TITLE" = "Adatvédelmi beállítások"; +"TELEMETRY_SWITCH_TITLE" = "Mapbox Telemetria"; +"TELEMETRY_GROUP_FOOTER" = "Ez a beállítás megengedi az alkalmazásnak, hogy névtelen helyzeti és használati adatokat osszon meg a Mapbox-szal."; diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index f83aa50509..060fb45d81 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -22,6 +22,10 @@ 1F7454971ECD450D00021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454941ECD450D00021D39 /* MGLLight_Private.h */; }; 1F7454A91ED08AB400021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */; }; 1F95931D1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */; }; + 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */; }; + 1FB7DAB01F2A4DC200410606 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */; }; + 1FB7DAB11F2A4DC800410606 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */; }; + 1FB7DAB21F2A4DC900410606 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */; }; 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578181DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578111DAA7D690050F07E /* UIImage+MGLAdditions.h */; }; 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 30E578121DAA7D690050F07E /* UIImage+MGLAdditions.mm */; }; @@ -149,7 +153,6 @@ 400533011DB0862B0069F638 /* NSArray+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 400532FF1DB0862B0069F638 /* NSArray+MGLAdditions.h */; }; 400533021DB0862B0069F638 /* NSArray+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 400533001DB0862B0069F638 /* NSArray+MGLAdditions.mm */; }; 400533031DB086490069F638 /* NSArray+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 400533001DB0862B0069F638 /* NSArray+MGLAdditions.mm */; }; - 4018B1C71CDC287F00F666AF /* MGLAnnotationView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.mm */; }; 4018B1C81CDC287F00F666AF /* MGLAnnotationView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.mm */; }; 4018B1C91CDC288A00F666AF /* MGLAnnotationView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C31CDC277F00F666AF /* MGLAnnotationView_Private.h */; }; 4018B1CA1CDC288E00F666AF /* MGLAnnotationView.h in Headers */ = {isa = PBXBuildFile; fileRef = 4018B1C51CDC277F00F666AF /* MGLAnnotationView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -213,6 +216,12 @@ 7E016D861D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; }; 7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */; }; 920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */; }; + 927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */; }; + 927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 927FBD011F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */; }; + 927FBD021F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */; }; + 929EFFAB1F56DCD4003A77D5 /* MGLAnnotationView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4018B1C41CDC277F00F666AF /* MGLAnnotationView.mm */; }; 92F2C3ED1F0E3C3A00268EC0 /* MGLRendererFrontend.h in Headers */ = {isa = PBXBuildFile; fileRef = 92F2C3EC1F0E3C3A00268EC0 /* MGLRendererFrontend.h */; }; 960D0C361ECF5AAF008E151F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 960D0C351ECF5AAF008E151F /* Images.xcassets */; }; 960D0C371ECF5AAF008E151F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 960D0C351ECF5AAF008E151F /* Images.xcassets */; }; @@ -237,7 +246,6 @@ DA1DC9971CB6E046006E619F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9961CB6E046006E619F /* main.m */; }; DA1DC9991CB6E054006E619F /* MBXAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9981CB6E054006E619F /* MBXAppDelegate.m */; }; DA1DC99B1CB6E064006E619F /* MBXViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC99A1CB6E064006E619F /* MBXViewController.m */; }; - DA1DC99D1CB6E076006E619F /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */; }; DA1DC99F1CB6E088006E619F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA1DC99E1CB6E088006E619F /* Assets.xcassets */; }; DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1F8F3C1EBD287B00367E42 /* MGLDocumentationGuideTests.swift */; }; DA2207BF1DC0805F0002F84D /* MGLStyleValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */; }; @@ -558,6 +566,8 @@ 1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; }; 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = "<group>"; }; 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = "<group>"; }; + 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MGLAdditions.h"; sourceTree = "<group>"; }; + 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MGLAdditions.m"; sourceTree = "<group>"; }; 20DABE861DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Foundation.strings"; sourceTree = "<group>"; }; 20DABE881DF78148007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; }; 20DABE8A1DF78149007AC5FF /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Root.strings"; sourceTree = "<group>"; }; @@ -684,6 +694,10 @@ 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLPolygon+MGLAdditions.h"; sourceTree = "<group>"; }; 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLPolygon+MGLAdditions.m"; sourceTree = "<group>"; }; 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = "<group>"; }; + 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = "<group>"; }; + 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = "<group>"; }; + 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; }; + 927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; }; 92F2C3EC1F0E3C3A00268EC0 /* MGLRendererFrontend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRendererFrontend.h; sourceTree = "<group>"; }; 960D0C351ECF5AAF008E151F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLSDKUpdateChecker.h; sourceTree = "<group>"; }; @@ -724,7 +738,6 @@ DA1DC9961CB6E046006E619F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; DA1DC9981CB6E054006E619F /* MBXAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXAppDelegate.m; sourceTree = "<group>"; }; DA1DC99A1CB6E064006E619F /* MBXViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXViewController.m; sourceTree = "<group>"; }; - DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; }; DA1DC99E1CB6E088006E619F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; DA1F8F3C1EBD287B00367E42 /* MGLDocumentationGuideTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationGuideTests.swift; path = ../../darwin/test/MGLDocumentationGuideTests.swift; sourceTree = "<group>"; }; DA2207BE1DC0805F0002F84D /* MGLStyleValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLStyleValueTests.swift; path = ../../darwin/test/MGLStyleValueTests.swift; sourceTree = "<group>"; }; @@ -915,6 +928,7 @@ DABCABBF1CB80717000A7C39 /* locations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = locations.cpp; sourceTree = "<group>"; }; DABCABC01CB80717000A7C39 /* locations.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = locations.hpp; sourceTree = "<group>"; }; DAC49C621CD07D74009E1AA3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; + DACCD9C81F1F473700BB09A1 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Root.strings; sourceTree = "<group>"; }; DAD165691CF41981001FF4B9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = "<group>"; }; DAD1656A1CF41981001FF4B9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = "<group>"; }; DAD1656B1CF41981001FF4B9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = "<group>"; }; @@ -1164,6 +1178,20 @@ name = Sources; sourceTree = "<group>"; }; + 9604FC341F313A5E003EEA02 /* Fixtures */ = { + isa = PBXGroup; + children = ( + 353BAEF51D646370009A8DA9 /* amsterdam.geojson */, + DA1DC96C1CB6C6CE006E619F /* points.geojson */, + DA1DC96D1CB6C6CE006E619F /* polyline.geojson */, + DA1DC96F1CB6C6CE006E619F /* threestates.geojson */, + DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */, + DD4823731D94AE6C00EB71B7 /* line_filter_style.json */, + DD4823741D94AE6C00EB71B7 /* numeric_filter_style.json */, + ); + name = Fixtures; + sourceTree = "<group>"; + }; DA1DC9411CB6C1C2006E619F = { isa = PBXGroup; children = ( @@ -1205,6 +1233,8 @@ 354B839B1D2E9B48005D9406 /* MBXUserLocationAnnotationView.m */, DA1DC9681CB6C6B7006E619F /* MBXOfflinePacksTableViewController.h */, DA1DC9691CB6C6B7006E619F /* MBXOfflinePacksTableViewController.m */, + 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */, + 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */, DA1DC9531CB6C1C2006E619F /* MBXViewController.h */, DA1DC99A1CB6E064006E619F /* MBXViewController.m */, 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */, @@ -1212,16 +1242,9 @@ DA821D051CCC6D59007508D4 /* Main.storyboard */, DA821D041CCC6D59007508D4 /* LaunchScreen.storyboard */, DA1DC99E1CB6E088006E619F /* Assets.xcassets */, - DA1DC96C1CB6C6CE006E619F /* points.geojson */, - DA1DC96D1CB6C6CE006E619F /* polyline.geojson */, - DA1DC96F1CB6C6CE006E619F /* threestates.geojson */, - 353BAEF51D646370009A8DA9 /* amsterdam.geojson */, - DD4823721D94AE6C00EB71B7 /* fill_filter_style.json */, - DD4823731D94AE6C00EB71B7 /* line_filter_style.json */, - DD4823741D94AE6C00EB71B7 /* numeric_filter_style.json */, DA1DC95E1CB6C1C2006E619F /* Info.plist */, - DA1DC99C1CB6E076006E619F /* Default-568h@2x.png */, 96E027251E57C76E004B8E66 /* Localizable.strings */, + 9604FC341F313A5E003EEA02 /* Fixtures */, DA1DC94D1CB6C1C2006E619F /* Supporting Files */, ); name = "Demo App"; @@ -1335,6 +1358,8 @@ 558DE79F1E5615E400C7916D /* MGLFoundation.mm */, DA8847E21CBAFA5100AB86E3 /* MGLMapCamera.h */, DA8848031CBAFA6200AB86E3 /* MGLMapCamera.mm */, + 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */, + 927FBCFE1F4DB05500F8BF1F /* MGLMapSnapshotter.mm */, DD0902A41DB18F1B00C5BDCE /* MGLNetworkConfiguration.h */, DD0902A21DB18DE700C5BDCE /* MGLNetworkConfiguration.m */, 92F2C3EC1F0E3C3A00268EC0 /* MGLRendererFrontend.h */, @@ -1554,6 +1579,8 @@ DAD165831CF4CFED001FF4B9 /* Categories */ = { isa = PBXGroup; children = ( + 1FDD9D6D1F26936400252B09 /* MGLVectorSource+MGLAdditions.h */, + 1FDD9D6E1F26936400252B09 /* MGLVectorSource+MGLAdditions.m */, 7E016D821D9E890300A29A21 /* MGLPolygon+MGLAdditions.h */, 7E016D831D9E890300A29A21 /* MGLPolygon+MGLAdditions.m */, 7E016D7C1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h */, @@ -1659,6 +1686,7 @@ 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, + 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, 3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, @@ -1709,6 +1737,7 @@ 3566C7661D4A77BA008152BC /* MGLShapeSource.h in Headers */, 35CE61821D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, 35B82BF81D6C5F8400B1B721 /* NSPredicate+MGLAdditions.h in Headers */, + 927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */, DA35A29E1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */, DAF0D8181DFE6B2800B28378 /* MGLAttributionInfo_Private.h in Headers */, DAAF722B1DA903C700312FA4 /* MGLStyleValue.h in Headers */, @@ -1785,6 +1814,7 @@ 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, + 927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */, 3566C7721D4A9198008152BC /* MGLSource_Private.h in Headers */, 353933FF1D3FB7DD003F57D7 /* MGLSymbolStyleLayer.h in Headers */, DAAF722E1DA903C700312FA4 /* MGLStyleValue_Private.h in Headers */, @@ -1793,7 +1823,6 @@ DABFB8621CBE99E500D62B32 /* MGLOfflinePack.h in Headers */, DAD1656D1CF41981001FF4B9 /* MGLFeature.h in Headers */, DA17BE311CC4BDAA00402C41 /* MGLMapView_Private.h in Headers */, - 92F2C3EE1F0E3DC600268EC0 /* MGLRendererFrontend.h in Headers */, DABFB86C1CBE99E500D62B32 /* MGLTypes.h in Headers */, DABFB8691CBE99E500D62B32 /* MGLShape.h in Headers */, 9620BB391E69FE1700705A1D /* MGLSDKUpdateChecker.h in Headers */, @@ -1840,6 +1869,7 @@ 35136D4D1D4277FC00C20EFD /* MGLSource.h in Headers */, DA35A2BC1CCA9A6900E826B2 /* MGLClockDirectionFormatter.h in Headers */, 35D13AC41D3D19DD00AFB4E0 /* MGLFillStyleLayer.h in Headers */, + 1FB7DAB01F2A4DC200410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DABFB86E1CBE9A0F00D62B32 /* MGLCalloutView.h in Headers */, 1F7454971ECD450D00021D39 /* MGLLight_Private.h in Headers */, DABFB8601CBE99E500D62B32 /* MGLMapCamera.h in Headers */, @@ -2066,7 +2096,6 @@ 353BAEF61D646370009A8DA9 /* amsterdam.geojson in Resources */, DA1DC9711CB6C6CE006E619F /* polyline.geojson in Resources */, DD4823761D94AE6C00EB71B7 /* line_filter_style.json in Resources */, - DA1DC99D1CB6E076006E619F /* Default-568h@2x.png in Resources */, DA821D071CCC6D59007508D4 /* Main.storyboard in Resources */, DA1DC9731CB6C6CE006E619F /* threestates.geojson in Resources */, DA821D061CCC6D59007508D4 /* LaunchScreen.storyboard in Resources */, @@ -2155,6 +2184,7 @@ DA1DC9991CB6E054006E619F /* MBXAppDelegate.m in Sources */, DA1DC96B1CB6C6B7006E619F /* MBXOfflinePacksTableViewController.m in Sources */, DA1DC96A1CB6C6B7006E619F /* MBXCustomCalloutView.m in Sources */, + 927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */, DA1DC99B1CB6E064006E619F /* MBXViewController.m in Sources */, 40FDA76B1CCAAA6800442548 /* MBXAnnotationView.m in Sources */, 632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */, @@ -2218,11 +2248,13 @@ 9620BB3A1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */, 354B83981D2E873E005D9406 /* MGLUserLocationAnnotationView.m in Sources */, DA88485D1CBAFB9800AB86E3 /* MGLFaux3DUserLocationAnnotationView.m in Sources */, + 1FB7DAB11F2A4DC800410606 /* MGLVectorSource+MGLAdditions.m in Sources */, DAD165701CF41981001FF4B9 /* MGLFeature.mm in Sources */, 30E578191DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C11CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, DA8848541CBAFB9800AB86E3 /* MGLCompactCalloutView.m in Sources */, DA8848251CBAFA6200AB86E3 /* MGLPointAnnotation.mm in Sources */, + 929EFFAB1F56DCD4003A77D5 /* MGLAnnotationView.mm in Sources */, 35136D3C1D42272500C20EFD /* MGLCircleStyleLayer.mm in Sources */, DD9BE4F81EB263C50079A3AF /* UIViewController+MGLAdditions.m in Sources */, 350098DE1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.mm in Sources */, @@ -2234,6 +2266,7 @@ DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */, DA88482D1CBAFA6200AB86E3 /* NSBundle+MGLAdditions.m in Sources */, DA88485B1CBAFB9800AB86E3 /* MGLUserLocation.m in Sources */, + 927FBD011F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */, 350098BD1D480108004B2AF0 /* MGLVectorSource.mm in Sources */, 3566C76E1D4A8DFA008152BC /* MGLRasterSource.mm in Sources */, DA88488C1CBB037E00AB86E3 /* SMCalloutView.m in Sources */, @@ -2264,7 +2297,6 @@ 3510FFF21D6D9D8C00F413B2 /* NSExpression+MGLAdditions.mm in Sources */, DA88481F1CBAFA6200AB86E3 /* MGLMultiPoint.mm in Sources */, DA88482B1CBAFA6200AB86E3 /* MGLTypes.m in Sources */, - 4018B1C71CDC287F00F666AF /* MGLAnnotationView.mm in Sources */, FA68F14D1E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm in Sources */, 404C26E41D89B877000AA13D /* MGLTileSource.mm in Sources */, 355AE0011E9281DA00F3939D /* MGLScaleBar.mm in Sources */, @@ -2301,6 +2333,7 @@ 9620BB3B1E69FE1700705A1D /* MGLSDKUpdateChecker.mm in Sources */, DAA4E4221CBB730400178DFB /* MGLPointAnnotation.mm in Sources */, DAED38661D62D0FC00D7640F /* NSURL+MGLAdditions.m in Sources */, + 1FB7DAB21F2A4DC900410606 /* MGLVectorSource+MGLAdditions.m in Sources */, DAD165711CF41981001FF4B9 /* MGLFeature.mm in Sources */, 30E5781A1DAA855E0050F07E /* UIImage+MGLAdditions.mm in Sources */, 40EDA1C21CFE0E0500D9EA68 /* MGLAnnotationContainerView.m in Sources */, @@ -2317,6 +2350,7 @@ DA00FC911D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */, DAA4E4201CBB730400178DFB /* MGLOfflinePack.mm in Sources */, DAA4E4331CBB730400178DFB /* MGLUserLocation.m in Sources */, + 927FBD021F4DB05500F8BF1F /* MGLMapSnapshotter.mm in Sources */, 350098BE1D480108004B2AF0 /* MGLVectorSource.mm in Sources */, 3566C76F1D4A8DFA008152BC /* MGLRasterSource.mm in Sources */, DAA4E4351CBB730400178DFB /* SMCalloutView.m in Sources */, @@ -2464,6 +2498,7 @@ DA618B1E1E688A3700CB7F44 /* ca */, DA618B2C1E68933600CB7F44 /* fi */, DAE8CCAE1E6E8C76009B5CB0 /* nl */, + DACCD9C81F1F473700BB09A1 /* hu */, ); name = Root.strings; sourceTree = "<group>"; diff --git a/platform/ios/resources/fr.lproj/Localizable.strings b/platform/ios/resources/fr.lproj/Localizable.strings index 075042c695..7fc85568c7 100644 --- a/platform/ios/resources/fr.lproj/Localizable.strings +++ b/platform/ios/resources/fr.lproj/Localizable.strings @@ -10,6 +10,9 @@ /* No comment provided by engineer. */ "CANCEL" = "Annuler"; +/* Accessibility hint for closing the selected annotation’s callout view and returning to the map */ +"CLOSE_CALLOUT_A11Y_HINT" = "Retour à la carte"; + /* Accessibility hint */ "COMPASS_A11Y_HINT" = "Tourne la carte vers le nord"; @@ -31,6 +34,12 @@ /* Accessibility label */ "INFO_A11Y_LABEL" = "À propos de cette carte"; +/* User-friendly error description */ +"LOAD_MAP_FAILED_DESC" = "La carte n’a pas pu être chargée car une erreur inconnue est survenue."; + +/* User-friendly error description */ +"LOAD_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style ne peut pas être chargé."; + /* Accessibility label */ "LOGO_A11Y_LABEL" = "Mapbox"; @@ -40,9 +49,18 @@ /* Map accessibility value */ "MAP_A11Y_VALUE" = "Zoom %1$dx\n%2$ld annotation(s) visible(s)"; +/* User-friendly error description */ +"PARSE_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style est corrompu."; + /* Action sheet title */ "SDK_NAME" = "Mapbox iOS SDK"; +/* Developer-only SDK update notification; {latest version, in format x.x.x} */ +"SDK_UPDATE_AVAILABLE" = "La version %@ du SDK Mapbox pour iOS est maintenant disponible :"; + +/* User-friendly error description */ +"STYLE_NOT_FOUND_DESC" = "La carte n’a pas pu être chargée car le style n’a pas été trouvé ou est incompatible."; + /* Telemetry prompt message */ "TELEMETRY_DISABLED_MSG" = "Vous pouvez contribuer à OpenStreetMap et Mapbox en partageant des données d’utilisation anonymes."; diff --git a/platform/ios/scripts/package.sh b/platform/ios/scripts/package.sh index e4403c4652..a5e2f87e20 100755 --- a/platform/ios/scripts/package.sh +++ b/platform/ios/scripts/package.sh @@ -21,33 +21,17 @@ elif [[ ${FORMAT} == "dynamic" ]]; then BUILD_STATIC=false fi -SELF_CONTAINED=${SELF_CONTAINED:-} -STATIC_BUNDLE_DIR= -if [[ ${SELF_CONTAINED} ]]; then - STATIC_BUNDLE_DIR="${OUTPUT}/static/${NAME}.framework" -else - STATIC_BUNDLE_DIR="${OUTPUT}/static" -fi - -STATIC_SETTINGS_DIR= -if [[ ${SELF_CONTAINED} ]]; then - STATIC_SETTINGS_DIR="${OUTPUT}/static/${NAME}.framework" -else - STATIC_SETTINGS_DIR="${OUTPUT}" -fi - SDK=iphonesimulator if [[ ${BUILD_FOR_DEVICE} == true ]]; then SDK=iphoneos fi IOS_SDK_VERSION=`xcrun --sdk ${SDK} --show-sdk-version` -echo "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${SYMBOLS}; self-contained static framework: ${SELF_CONTAINED:-NO}" - function step { >&2 echo -e "\033[1m\033[36m* $@\033[0m"; } function finish { >&2 echo -en "\033[0m"; } trap finish EXIT +step "Configuring ${FORMAT:-dynamic and static} ${BUILDTYPE} framework for ${SDK}; symbols: ${SYMBOLS}" rm -rf ${OUTPUT} if [[ ${BUILD_STATIC} == true ]]; then @@ -69,7 +53,7 @@ PROJ_VERSION=$(git rev-list --count HEAD) SEM_VERSION=$( git describe --tags --match=ios-v*.*.* --abbrev=0 | sed 's/^ios-v//' ) SHORT_VERSION=${SEM_VERSION%-*} -step "Building targets (build ${PROJ_VERSION}, version ${SEM_VERSION})…" +step "Building targets (build ${PROJ_VERSION}, version ${SEM_VERSION})" SCHEME='dynamic' if [[ ${BUILD_DYNAMIC} == true && ${BUILD_STATIC} == true ]]; then @@ -78,6 +62,7 @@ elif [[ ${BUILD_STATIC} == true ]]; then SCHEME='static' fi +step "Building for iOS Simulator using scheme ${SCHEME}" xcodebuild \ CURRENT_PROJECT_VERSION=${PROJ_VERSION} \ CURRENT_SHORT_VERSION=${SHORT_VERSION} \ @@ -92,6 +77,7 @@ xcodebuild \ -jobs ${JOBS} | xcpretty if [[ ${BUILD_FOR_DEVICE} == true ]]; then + step "Building for iOS devices using scheme ${SCHEME}" xcodebuild \ CURRENT_PROJECT_VERSION=${PROJ_VERSION} \ CURRENT_SHORT_VERSION=${SHORT_VERSION} \ @@ -119,7 +105,7 @@ if [[ ${BUILD_FOR_DEVICE} == true ]]; then ${LIBS[@]/#/${PRODUCTS}/${BUILDTYPE}-iphonesimulator/lib} \ `cmake -LA -N ${DERIVED_DATA} | grep MASON_PACKAGE_icu_LIBRARIES | cut -d= -f2` - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/${NAME}.bundle ${STATIC_BUNDLE_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/${NAME}.bundle ${OUTPUT}/static fi if [[ ${BUILD_DYNAMIC} == true ]]; then @@ -149,7 +135,7 @@ if [[ ${BUILD_FOR_DEVICE} == true ]]; then -create -output ${OUTPUT}/dynamic/${NAME}.framework/${NAME} | echo fi - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/Settings.bundle ${STATIC_SETTINGS_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphoneos/Settings.bundle ${OUTPUT} else if [[ ${BUILD_STATIC} == true ]]; then step "Assembling static library for iOS Simulator…" @@ -159,7 +145,7 @@ else ${LIBS[@]/#/${PRODUCTS}/${BUILDTYPE}-iphonesimulator/lib} \ `cmake -LA -N ${DERIVED_DATA} | grep MASON_PACKAGE_icu_LIBRARIES | cut -d= -f2` - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/${NAME}.bundle ${STATIC_BUNDLE_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/${NAME}.bundle ${OUTPUT}/static fi if [[ ${BUILD_DYNAMIC} == true ]]; then @@ -174,7 +160,7 @@ else fi fi - cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/Settings.bundle ${STATIC_SETTINGS_DIR} + cp -rv ${PRODUCTS}/${BUILDTYPE}-iphonesimulator/Settings.bundle ${OUTPUT} fi if [[ ${SYMBOLS} = NO ]]; then @@ -244,13 +230,17 @@ if [[ ${BUILD_STATIC} == true ]]; then fi step "Copying library resources…" -cp -pv LICENSE.md ${STATIC_SETTINGS_DIR} +cp -pv LICENSE.md ${OUTPUT} if [[ ${BUILD_STATIC} == true ]]; then - cp -pv "${STATIC_BUNDLE_DIR}/${NAME}.bundle/Info.plist" "${OUTPUT}/static/${NAME}.framework/Info.plist" + cp -pv "${OUTPUT}/static/${NAME}.bundle/Info.plist" "${OUTPUT}/static/${NAME}.framework/Info.plist" plutil -replace CFBundlePackageType -string FMWK "${OUTPUT}/static/${NAME}.framework/Info.plist" mkdir "${OUTPUT}/static/${NAME}.framework/Modules" cp -pv platform/ios/framework/modulemap "${OUTPUT}/static/${NAME}.framework/Modules/module.modulemap" fi +if [[ ${BUILD_DYNAMIC} == true && ${BUILD_FOR_DEVICE} == true ]]; then + step "Copying bitcode symbol maps…" + find "${PRODUCTS}/${BUILDTYPE}-iphoneos" -name '*.bcsymbolmap' -type f -exec cp -pv {} "${OUTPUT}/dynamic/" \; +fi sed -n -e '/^## /,$p' platform/ios/CHANGELOG.md > "${OUTPUT}/CHANGELOG.md" rm -rf /tmp/mbgl diff --git a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m index 6db9c0db10..5f67f24f4e 100644 --- a/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m +++ b/platform/ios/src/MGLFaux3DUserLocationAnnotationView.m @@ -460,11 +460,8 @@ const CGFloat MGLUserLocationAnnotationArrowSize = MGLUserLocationAnnotationPuck - (CGFloat)calculateAccuracyRingSize { - CGFloat latitudeRadians = MGLRadiansFromDegrees(self.userLocation.coordinate.latitude); - CGFloat metersPerPoint = [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude]; - CGFloat pixelRadius = self.userLocation.location.horizontalAccuracy / cos(latitudeRadians) / metersPerPoint; - - return pixelRadius * 2.0; + // diameter in screen points + return self.userLocation.location.horizontalAccuracy / [self.mapView metersPerPointAtLatitude:self.userLocation.coordinate.latitude] * 2.0; } - (UIImage *)headingIndicatorTintedGradientImage diff --git a/platform/ios/src/MGLMapView.h b/platform/ios/src/MGLMapView.h index 64d2e53d21..c3ffe1983e 100644 --- a/platform/ios/src/MGLMapView.h +++ b/platform/ios/src/MGLMapView.h @@ -778,6 +778,23 @@ MGL_EXPORT IB_DESIGNABLE - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion; /** + Moves the viewpoint to a different location with respect to the map with an + optional transition duration and timing function. + + @param camera The new viewpoint. + @param duration The amount of time, measured in seconds, that the transition + animation should take. Specify `0` to jump to the new viewpoint + instantaneously. + @param function A timing function used for the animation. Set this parameter to + `nil` for a transition that matches most system animations. If the duration + is `0`, this parameter is ignored. + @param edgePadding The minimum padding (in screen points) that would be visible + around the returned camera object if it were set as the receiver’s camera. + @param completion The block to execute after the animation finishes. + */ +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion; + +/** Moves the viewpoint to a different location using a transition animation that evokes powered flight and a default duration based on the length of the flight path. diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 9c3d1d415f..d9d03ea478 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -1116,8 +1116,10 @@ public: - (void)updateTintColorForView:(UIView *)view { - // stop at recursing container & annotation views (#8522) - if ([view isEqual:self.annotationContainerView]) return; + // Don't update: + // - annotation views + // - attribution button (handled automatically) + if ([view isEqual:self.annotationContainerView] || [view isEqual:self.attributionButton]) return; if ([view respondsToSelector:@selector(setTintColor:)]) view.tintColor = self.tintColor; @@ -1250,8 +1252,6 @@ public: { if ( ! self.isZoomEnabled) return; - if (_mbglMap->getZoom() <= _mbglMap->getMinZoom() && pinch.scale < 1) return; - _mbglMap->cancelTransitions(); CGPoint centerPoint = [self anchorPointForGesture:pinch]; @@ -1267,17 +1267,17 @@ public: } else if (pinch.state == UIGestureRecognizerStateChanged) { + // Zoom limiting happens at the core level. CGFloat newScale = self.scale * pinch.scale; - double zoom = log2(newScale); - if (zoom < _mbglMap->getMinZoom()) return; - + double newZoom = log2(newScale); + // Calculates the final camera zoom, has no effect within current map camera. - MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; + MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) { - _mbglMap->setZoom(zoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); + _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); // The gesture recognizer only reports the gesture’s current center // point, so use the previous center point to anchor the transition. // If the number of touches has changed, the remembered center point is @@ -1625,9 +1625,9 @@ public: { CGFloat distance = [quickZoom locationInView:quickZoom.view].y - self.quickZoomStart; - CGFloat newZoom = log2f(self.scale) + (distance / 75); + CGFloat newZoom = MAX(log2f(self.scale) + (distance / 75), _mbglMap->getMinZoom()); - if (newZoom < _mbglMap->getMinZoom()) return; + if (_mbglMap->getZoom() == newZoom) return; CGPoint centerPoint = [self anchorPointForGesture:quickZoom]; @@ -1760,39 +1760,6 @@ public: return [gesture locationInView:gesture.view]; } -- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer -{ - if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) - { - UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; - - if (panGesture.minimumNumberOfTouches == 2) - { - CGPoint velocity = [panGesture velocityInView:panGesture.view]; - double gestureAngle = MGLDegreesFromRadians(atan(velocity.y / velocity.x)); - double horizontalToleranceDegrees = 20.0; - - // cancel if gesture angle is not 90º±20º (more or less vertical) - if ( ! (fabs((fabs(gestureAngle) - 90.0)) < horizontalToleranceDegrees)) - { - return NO; - } - } - } - else if (gestureRecognizer == _singleTapGestureRecognizer) - { - // Gesture will be recognized if it could deselect an annotation - if(!self.selectedAnnotation) - { - id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; - if(!annotation) { - return NO; - } - } - } - return YES; -} - - (void)handleCalloutAccessoryTapGesture:(UITapGestureRecognizer *)tap { if ([self.delegate respondsToSelector:@selector(mapView:annotation:calloutAccessoryControlTapped:)]) @@ -1836,12 +1803,60 @@ public: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, calloutView); } +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) + { + UIPanGestureRecognizer *panGesture = (UIPanGestureRecognizer *)gestureRecognizer; + + if (panGesture.minimumNumberOfTouches == 2) + { + CGPoint west = [panGesture locationOfTouch:0 inView:panGesture.view]; + CGPoint east = [panGesture locationOfTouch:1 inView:panGesture.view]; + + if (west.x > east.x) { + CGPoint swap = west; + west = east; + east = swap; + } + + CLLocationDegrees horizontalToleranceDegrees = 60.0; + if ([self angleBetweenPoints:west east:east] > horizontalToleranceDegrees) { + return NO; + } + + } + } + else if (gestureRecognizer == _singleTapGestureRecognizer) + { + // Gesture will be recognized if it could deselect an annotation + if(!self.selectedAnnotation) + { + id<MGLAnnotation>annotation = [self annotationForGestureRecognizer:(UITapGestureRecognizer*)gestureRecognizer persistingResults:NO]; + if(!annotation) { + return NO; + } + } + } + return YES; +} + - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { NSArray *validSimultaneousGestures = @[ self.pan, self.pinch, self.rotate ]; return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]); } + +- (CLLocationDegrees)angleBetweenPoints:(CGPoint)west east:(CGPoint)east +{ + CGFloat slope = (west.y - east.y) / (west.x - east.x); + + CGFloat angle = atan(fabs(slope)); + CLLocationDegrees degrees = MGLDegreesFromRadians(angle); + + return degrees; +} - (void)trackGestureEvent:(NSString *)gestureID forRecognizer:(UIGestureRecognizer *)recognizer { @@ -2743,6 +2758,10 @@ public: - (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { + [self setCamera:camera withDuration:duration animationTimingFunction:function edgePadding:self.contentInset completionHandler:completion]; +} + +- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function edgePadding:(UIEdgeInsets)edgePadding completionHandler:(nullable void (^)(void))completion { mbgl::AnimationOptions animationOptions; if (duration > 0) { @@ -2771,7 +2790,7 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); - mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:self.contentInset]; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; } diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index abe16cc3ee..9b2c472cf6 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -60,3 +60,4 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "NSValue+MGLAdditions.h" #import "MGLStyleValue.h" #import "MGLAttributionInfo.h" +#import "MGLMapSnapshotter.h" diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h index 6e15e07cb5..3c179d6324 100644 --- a/platform/ios/src/UIImage+MGLAdditions.h +++ b/platform/ios/src/UIImage+MGLAdditions.h @@ -8,6 +8,8 @@ NS_ASSUME_NONNULL_BEGIN - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage; +- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage; + - (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier; - (mbgl::PremultipliedImage)mgl_premultipliedImage; diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index 5e28d18190..7cf1ed9bcc 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -22,6 +22,19 @@ return self; } +- (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::PremultipliedImage&&)mbglImage +{ + CGImageRef image = CGImageFromMGLPremultipliedImage(mbglImage.clone()); + if (!image) { + return nil; + } + + self = [self initWithCGImage:image scale:1.0 orientation:UIImageOrientationUp]; + + CGImageRelease(image); + return self; +} + - (std::unique_ptr<mbgl::style::Image>)mgl_styleImageWithIdentifier:(NSString *)identifier { BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; return std::make_unique<mbgl::style::Image>([identifier UTF8String], diff --git a/platform/linux/config.cmake b/platform/linux/config.cmake index 3aa1fdbbfc..badbde408f 100644 --- a/platform/linux/config.cmake +++ b/platform/linux/config.cmake @@ -8,7 +8,7 @@ mason_use(libpng VERSION 1.6.25) mason_use(libjpeg-turbo VERSION 1.5.0) mason_use(webp VERSION 0.5.1) mason_use(gtest VERSION 1.8.0${MASON_CXXABI_SUFFIX}) -mason_use(benchmark VERSION 1.0.0-1) +mason_use(benchmark VERSION 1.2.0) mason_use(icu VERSION 58.1-min-size) # Link with libuv. This is not part of loop-uv.cmake because loop-uv.cmake is also diff --git a/platform/linux/src/headless_display_egl.cpp b/platform/linux/src/headless_display_egl.cpp index 03c8e16a59..b746211924 100644 --- a/platform/linux/src/headless_display_egl.cpp +++ b/platform/linux/src/headless_display_egl.cpp @@ -32,6 +32,9 @@ HeadlessDisplay::Impl::Impl() { } const EGLint attribs[] = { +#if MBGL_USE_GLES2 + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +#endif EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_NONE }; diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 1f05281d32..8707c0aad7 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -12,6 +12,13 @@ * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) +## 0.5.1 + +This version of the Mapbox macOS SDK corresponds to version 3.6.2 of the Mapbox iOS SDK. + +* Added an `MGLStyle.localizesLabels` property, off by default, that localizes any Mapbox Streets–sourced symbol layer into the user’s preferred language. ([#9582](https://github.com/mapbox/mapbox-gl-native/pull/9582)) +* Fixed an issue that caused `-[MGLShapeSource featuresMatchingPredicate:]` and `-[MGLVectorSource featuresInSourceLayersWithIdentifiers:predicate:]` to always return an empty array. ([#9784](https://github.com/mapbox/mapbox-gl-native/pull/9784)) + ## 0.5.0 This version of the Mapbox macOS SDK corresponds to version 3.6.0 of the Mapbox iOS SDK. diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 9a8cf05c16..9a53ba9d4b 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> <dependencies> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -128,6 +128,12 @@ <action selector="revertDocumentToSaved:" target="-1" id="iJ3-Pv-kwq"/> </connections> </menuItem> + <menuItem title="Save snapshot" id="vjX-0E-kLO"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="takeSnapshot:" target="-1" id="H06-sU-n4U"/> + </connections> + </menuItem> <menuItem isSeparatorItem="YES" id="aJh-i4-bef"/> <menuItem title="Page Setup…" keyEquivalent="P" id="qIS-W8-SiK"> <connections> @@ -654,7 +660,7 @@ CA <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="109" y="131" width="350" height="84"/> - <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/> + <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/> <view key="contentView" id="eA4-n3-qPe"> <rect key="frame" x="0.0" y="0.0" width="350" height="84"/> <autoresizingMask key="autoresizingMask"/> @@ -730,7 +736,7 @@ CA <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="830" y="430" width="400" height="300"/> - <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/> + <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/> <view key="contentView" id="8ha-hw-zOD"> <rect key="frame" x="0.0" y="0.0" width="400" height="300"/> <autoresizingMask key="autoresizingMask"/> @@ -739,7 +745,7 @@ CA <rect key="frame" x="-1" y="20" width="402" height="281"/> <clipView key="contentView" id="J9U-Yx-o2S"> <rect key="frame" x="1" y="0.0" width="400" height="280"/> - <autoresizingMask key="autoresizingMask"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" autosaveColumns="NO" headerView="MAZ-Iq-hBi" id="Ato-Vu-HYT"> <rect key="frame" x="0.0" y="0.0" width="423" height="257"/> diff --git a/platform/macos/app/Base.lproj/MapDocument.xib b/platform/macos/app/Base.lproj/MapDocument.xib index d95f21b2e9..0394f38533 100644 --- a/platform/macos/app/Base.lproj/MapDocument.xib +++ b/platform/macos/app/Base.lproj/MapDocument.xib @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <dependencies> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -48,7 +48,7 @@ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <rect key="contentRect" x="388" y="211" width="642" height="480"/> - <rect key="screenRect" x="0.0" y="0.0" width="1280" height="777"/> + <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/> <view key="contentView" id="TuG-C5-zLS"> <rect key="frame" x="0.0" y="0.0" width="642" height="480"/> <autoresizingMask key="autoresizingMask"/> diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 94bf18dea1..406ff0ca9d 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -3,9 +3,10 @@ #import "AppDelegate.h" #import "LimeGreenStyleLayer.h" #import "DroppedPinAnnotation.h" +#import "MGLMapsnapshotter.h" #import "MGLStyle+MBXAdditions.h" -#import "MGLVectorSource+MBXAdditions.h" +#import "MGLVectorSource+MGLAdditions.h" #import <Mapbox/Mapbox.h> @@ -73,6 +74,9 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio BOOL _isTouringWorld; BOOL _isShowingPolygonAndPolylineAnnotations; BOOL _isShowingAnimatedAnnotation; + + // Snapshotter + MGLMapSnapshotter* snapshotter; } #pragma mark Lifecycle @@ -153,6 +157,67 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio camera.heading, camera.pitch]]; } +#pragma mark File methods + +- (IBAction)takeSnapshot:(id)sender { + MGLMapCamera *camera = self.mapView.camera; + + MGLMapSnapshotOptions* options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:self.mapView.styleURL camera:camera size:self.mapView.bounds.size]; + options.zoom = self.mapView.zoomLevel; + + // Create and start the snapshotter + snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; + [snapshotter startWithCompletionHandler: ^(NSImage *image, NSError *error) { + if (error) { + NSLog(@"Could not load snapshot: %@", [error localizedDescription]); + } else { + NSWindow* window = [[[self windowControllers] objectAtIndex:0] window]; + + NSString* newName = [[@"snapshot" stringByDeletingPathExtension] stringByAppendingPathExtension:@"png"]; + + // Set the default name for the file and show the panel. + NSSavePanel* panel = [NSSavePanel savePanel]; + [panel setNameFieldStringValue:newName]; + [panel beginSheetModalForWindow:window completionHandler:^(NSInteger result){ + if (result == NSFileHandlingPanelOKButton) { + // Write the contents in the new format. + NSURL* fileURL = [panel URL]; + + NSBitmapImageRep *bitmapRep = nil; + for (NSImageRep *imageRep in [image representations]) { + if ([imageRep isKindOfClass:[NSBitmapImageRep class]]){ + bitmapRep = (NSBitmapImageRep *)imageRep; + break; // stop on first bitmap rep we find + } + } + + if (!bitmapRep) { + bitmapRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]]; + + } + + NSString *extension = [[fileURL pathExtension] lowercaseString]; + NSBitmapImageFileType fileType; + if ([extension isEqualToString:@"png"]) { + fileType = NSPNGFileType; + } else if ([extension isEqualToString:@"gif"]) { + fileType = NSGIFFileType; + } else if ([extension isEqualToString:@"jpg"] || [extension isEqualToString:@"jpeg"]) { + fileType = NSJPEGFileType; + } else { + fileType = NSTIFFFileType; + } + + NSData *imageData = [bitmapRep representationUsingType:fileType properties:@{}]; + [imageData writeToURL:fileURL atomically:NO]; + } + }]; + + } + snapshotter = nil; + }]; +} + #pragma mark View methods - (IBAction)showStyle:(id)sender { @@ -344,52 +409,7 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio } - (void)updateLabels { - MGLStyle *style = self.mapView.style; - NSString *preferredLanguage = _isLocalizingLabels ? [MGLVectorSource preferredMapboxStreetsLanguage] : nil; - NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary]; - for (MGLSymbolStyleLayer *layer in style.layers) { - if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) { - continue; - } - - MGLVectorSource *source = (MGLVectorSource *)[style sourceWithIdentifier:layer.sourceIdentifier]; - if (![source isKindOfClass:[MGLVectorSource class]] || !source.mapboxStreets) { - continue; - } - - NSDictionary *localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier]; - if (!localizedKeysByKey) { - localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier] = [source localizedKeysByKeyForPreferredLanguage:preferredLanguage]; - } - - NSString *(^stringByLocalizingString)(NSString *) = ^ NSString * (NSString *string) { - NSMutableString *localizedString = string.mutableCopy; - [localizedKeysByKey enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull localizedKey, BOOL * _Nonnull stop) { - NSAssert([key isKindOfClass:[NSString class]], @"key is not a string"); - NSAssert([localizedKey isKindOfClass:[NSString class]], @"localizedKey is not a string"); - [localizedString replaceOccurrencesOfString:[NSString stringWithFormat:@"{%@}", key] - withString:[NSString stringWithFormat:@"{%@}", localizedKey] - options:0 - range:NSMakeRange(0, localizedString.length)]; - }]; - return localizedString; - }; - - if ([layer.text isKindOfClass:[MGLConstantStyleValue class]]) { - NSString *textField = [(MGLConstantStyleValue<NSString *> *)layer.text rawValue]; - layer.text = [MGLStyleValue<NSString *> valueWithRawValue:stringByLocalizingString(textField)]; - } - else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { - MGLCameraStyleFunction *function = (MGLCameraStyleFunction<NSString *> *)layer.text; - NSMutableDictionary *stops = function.stops.mutableCopy; - [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLConstantStyleValue<NSString *> *stop, BOOL *done) { - NSString *textField = stop.rawValue; - stops[zoomLevel] = [MGLStyleValue<NSString *> valueWithRawValue:stringByLocalizingString(textField)]; - }]; - function.stops = stops; - layer.text = function; - } - } + self.mapView.style.localizesLabels = _isLocalizingLabels; } - (void)applyPendingState { @@ -1008,6 +1028,9 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio if (menuItem.action == @selector(giveFeedback:)) { return YES; } + if (menuItem.action == @selector(takeSnapshot:)) { + return !(snapshotter && [snapshotter isLoading]); + } return NO; } diff --git a/platform/macos/config.cmake b/platform/macos/config.cmake index 86c54b612c..bb2cc9ac1c 100644 --- a/platform/macos/config.cmake +++ b/platform/macos/config.cmake @@ -3,7 +3,7 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 10.10) mason_use(glfw VERSION 2017-07-13-67c9155) mason_use(boost_libprogram_options VERSION 1.62.0) mason_use(gtest VERSION 1.8.0) -mason_use(benchmark VERSION 1.0.0-1) +mason_use(benchmark VERSION 1.2.0) mason_use(icu VERSION 58.1-min-size) include(cmake/loop-darwin.cmake) @@ -53,6 +53,10 @@ macro(mbgl_platform_core) PRIVATE platform/default/mbgl/gl/headless_display.hpp PRIVATE platform/darwin/src/headless_display_cgl.cpp + # Snapshotting + PRIVATE platform/default/mbgl/map/map_snapshotter.cpp + PRIVATE platform/default/mbgl/map/map_snapshotter.hpp + # Thread pool PRIVATE platform/default/mbgl/util/shared_thread_pool.cpp PRIVATE platform/default/mbgl/util/shared_thread_pool.hpp diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 5b4cadbc7c..40bbb07e8b 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 1F7454A51ECFB00300021D39 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454A21ECFB00300021D39 /* MGLLight.mm */; }; 1F7454AB1ED1DDBD00021D39 /* MGLLightTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F7454AA1ED1DDBD00021D39 /* MGLLightTest.mm */; }; 1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */; }; + 1FCDF1421F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */; }; + 1FCDF1431F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */; }; 30E5781B1DAA857E0050F07E /* NSImage+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */; }; 3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */; }; 3508EC651D749D39009B0EE4 /* NSExpression+MGLAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */; }; @@ -79,6 +81,8 @@ 558DE7A61E56161C00C7916D /* MGLFoundation_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 558DE7A41E56161C00C7916D /* MGLFoundation_Private.h */; }; 558DE7A71E56161C00C7916D /* MGLFoundation.mm in Sources */ = {isa = PBXBuildFile; fileRef = 558DE7A51E56161C00C7916D /* MGLFoundation.mm */; }; 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; }; + 92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; }; 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; }; 92F2C3EB1F0E3A1900268EC0 /* MGLRendererFrontend.h in Headers */ = {isa = PBXBuildFile; fileRef = 92F2C3EA1F0E3A1900268EC0 /* MGLRendererFrontend.h */; }; 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E027331E57C9A7004B8E66 /* Localizable.strings */; }; @@ -237,7 +241,6 @@ DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */; }; DAF0D80E1DFE0E5D00B28378 /* MGLPointCollection_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */; }; DAF0D8161DFE6B1800B28378 /* MGLAttributionInfo_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */; }; - DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */; }; DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */; }; DD0902B31DB1AC6400C5BDCE /* MGLNetworkConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */; }; DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */; }; @@ -284,6 +287,8 @@ 1F7454A21ECFB00300021D39 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = "<group>"; }; 1F7454AA1ED1DDBD00021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLightTest.mm; sourceTree = "<group>"; }; 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = "<group>"; }; + 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MGLAdditions.h"; sourceTree = "<group>"; }; + 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MGLAdditions.m"; sourceTree = "<group>"; }; 30E578141DAA7D920050F07E /* NSImage+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+MGLAdditions.h"; path = "src/NSImage+MGLAdditions.h"; sourceTree = SOURCE_ROOT; }; 3508EC621D749D39009B0EE4 /* NSExpression+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSExpression+MGLAdditions.h"; sourceTree = "<group>"; }; 3508EC631D749D39009B0EE4 /* NSExpression+MGLAdditions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSExpression+MGLAdditions.mm"; sourceTree = "<group>"; }; @@ -351,6 +356,8 @@ 55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = "<group>"; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = "<group>"; }; + 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; }; + 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; }; 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = "<group>"; }; 92F2C3EA1F0E3A1900268EC0 /* MGLRendererFrontend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLRendererFrontend.h; sourceTree = "<group>"; }; 966091701E5BBFF700A9A03B /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; }; @@ -483,6 +490,7 @@ DACC22121CF3D3E200D220D9 /* MGLFeature.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature.h; sourceTree = "<group>"; }; DACC22131CF3D3E200D220D9 /* MGLFeature.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLFeature.mm; sourceTree = "<group>"; }; DACC22171CF3D4F700D220D9 /* MGLFeature_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLFeature_Private.h; sourceTree = "<group>"; }; + DACCD9C71F1F443B00BB09A1 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Foundation.strings; sourceTree = "<group>"; }; DAD165721CF4CD7A001FF4B9 /* MGLShapeCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLShapeCollection.h; sourceTree = "<group>"; }; DAD165731CF4CD7A001FF4B9 /* MGLShapeCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLShapeCollection.mm; sourceTree = "<group>"; }; DAE6C2E11CC304F900DB3429 /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = "<group>"; }; @@ -575,8 +583,6 @@ DAEDC4361D606291000224FF /* MGLAttributionButtonTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLAttributionButtonTests.m; sourceTree = "<group>"; }; DAF0D80D1DFE0E5D00B28378 /* MGLPointCollection_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLPointCollection_Private.h; sourceTree = "<group>"; }; DAF0D8151DFE6B1800B28378 /* MGLAttributionInfo_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo_Private.h; sourceTree = "<group>"; }; - DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MGLVectorSource+MBXAdditions.h"; sourceTree = "<group>"; }; - DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MGLVectorSource+MBXAdditions.m"; sourceTree = "<group>"; }; DAFBD0D51E3FA969000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; }; DAFBD0D61E3FA983000CD6BF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Foundation.strings"; sourceTree = "<group>"; }; DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLNetworkConfiguration.m; sourceTree = "<group>"; }; @@ -749,8 +755,6 @@ DA839E9E1CC2E3400062CAFB /* MapDocument.xib */, DACB0C371E18DFFD005DDBEA /* MGLStyle+MBXAdditions.h */, DACB0C381E18DFFD005DDBEA /* MGLStyle+MBXAdditions.m */, - DAF0D81A1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.h */, - DAF0D81B1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m */, DAE6C2E91CC3050F00DB3429 /* OfflinePackNameValueTransformer.h */, DAE6C2EA1CC3050F00DB3429 /* OfflinePackNameValueTransformer.m */, DAA48EFB1D6A4731006A7E36 /* StyleLayerIconTransformer.h */, @@ -930,6 +934,8 @@ DAD1657F1CF4CF50001FF4B9 /* Categories */ = { isa = PBXGroup; children = ( + 1FCDF1401F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h */, + 1FCDF1411F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m */, 408AA8601DAEED3300022900 /* MGLPolygon+MGLAdditions.h */, 408AA85C1DAEED3300022900 /* MGLPolygon+MGLAdditions.m */, 408AA8611DAEED3300022900 /* MGLPolyline+MGLAdditions.h */, @@ -1046,6 +1052,8 @@ 558DE7A51E56161C00C7916D /* MGLFoundation.mm */, DAE6C34D1CC31E0400DB3429 /* MGLMapCamera.h */, DAE6C36E1CC31E2A00DB3429 /* MGLMapCamera.mm */, + 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */, + 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */, DD0902B01DB1AC6400C5BDCE /* MGLNetworkConfiguration.h */, DD0902AF1DB1AC6400C5BDCE /* MGLNetworkConfiguration.m */, 92F2C3EA1F0E3A1900268EC0 /* MGLRendererFrontend.h */, @@ -1109,6 +1117,7 @@ DA8F259C1D51CB000010E6B5 /* MGLStyleValue_Private.h in Headers */, DAE6C35B1CC31E0400DB3429 /* MGLAnnotation.h in Headers */, DAE6C3B61CC31EF300DB3429 /* MGLMapView_Private.h in Headers */, + 92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */, 3527428D1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.h in Headers */, DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */, DAE6C3B21CC31EF300DB3429 /* MGLAttributionButton.h in Headers */, @@ -1166,6 +1175,7 @@ DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */, DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */, DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */, + 1FCDF1421F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.h in Headers */, DAE6C3A61CC31E9400DB3429 /* MGLMapViewDelegate.h in Headers */, DAE6C38B1CC31E2A00DB3429 /* MGLOfflinePack_Private.h in Headers */, 558DE7A61E56161C00C7916D /* MGLFoundation_Private.h in Headers */, @@ -1391,7 +1401,6 @@ DACB0C391E18DFFD005DDBEA /* MGLStyle+MBXAdditions.m in Sources */, DA839E9A1CC2E3400062CAFB /* main.m in Sources */, DA839E971CC2E3400062CAFB /* AppDelegate.m in Sources */, - DAF0D81C1DFF567C00B28378 /* MGLVectorSource+MBXAdditions.m in Sources */, DAE6C2F01CC3050F00DB3429 /* OfflinePackNameValueTransformer.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1401,6 +1410,7 @@ buildActionMask = 2147483647; files = ( 07A019EF1ED665CD00ACD43E /* MGLImageSource.mm in Sources */, + 92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */, 40ABDB561DB0022100372083 /* NSImage+MGLAdditions.mm in Sources */, DAE6C3901CC31E2A00DB3429 /* MGLPointAnnotation.mm in Sources */, DAE6C3981CC31E2A00DB3429 /* NSBundle+MGLAdditions.m in Sources */, @@ -1408,6 +1418,7 @@ 40B77E461DB11BCD003DA2FE /* NSArray+MGLAdditions.mm in Sources */, DAE6C38C1CC31E2A00DB3429 /* MGLOfflinePack.mm in Sources */, 35D65C5B1D65AD5500722C23 /* NSDate+MGLAdditions.mm in Sources */, + 1FCDF1431F2A4F3600A46694 /* MGLVectorSource+MGLAdditions.m in Sources */, DD0902B21DB1AC6400C5BDCE /* MGLNetworkConfiguration.m in Sources */, 1F7454A51ECFB00300021D39 /* MGLLight.mm in Sources */, DAE6C3B11CC31EF300DB3429 /* MGLAnnotationImage.m in Sources */, @@ -1614,6 +1625,7 @@ DA618B171E68876C00CB7F44 /* ca */, DA618B231E6891ED00CB7F44 /* lt */, DAE9E0F21EB7BF39001E8E8B /* es */, + DACCD9C71F1F443B00BB09A1 /* fr */, ); name = Foundation.strings; sourceTree = "<group>"; diff --git a/platform/macos/sdk/fr.lproj/Localizable.strings b/platform/macos/sdk/fr.lproj/Localizable.strings index 34e085ef2b..9d73f23d05 100644 --- a/platform/macos/sdk/fr.lproj/Localizable.strings +++ b/platform/macos/sdk/fr.lproj/Localizable.strings @@ -1,6 +1,18 @@ -/* Accessibility title */ +/* User-friendly error description */ +"LOAD_MAP_FAILED_DESC" = "La carte n’a pas pu être chargée car une erreur inconnue est survenue."; + +/* User-friendly error description */ +"LOAD_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style ne peut pas être chargé."; + +/* Accessibility title */ "MAP_A11Y_TITLE" = "Mapbox"; +/* User-friendly error description */ +"PARSE_STYLE_FAILED_DESC" = "La carte n’a pas pu être chargée car le style est corrompu."; + +/* User-friendly error description */ +"STYLE_NOT_FOUND_DESC" = "La carte n’a pas pu être chargée car le style n’a pas été trouvé ou est incompatible."; + /* Label of Zoom In button */ "ZOOM_IN_LABEL" = "+"; diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index e4ad258b6e..a082a4771e 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -56,3 +56,4 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "NSValue+MGLAdditions.h" #import "MGLStyleValue.h" #import "MGLAttributionInfo.h" +#import "MGLMapSnapshotter.h" diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 7c7082bd09..fcc394bbe5 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -170,6 +170,9 @@ std::string StringifyStyle(v8::Local<v8::Value> styleHandle) { * @function * @name load * @param {string|Object} stylesheet either an object or a JSON representation + * @param {Object} options + * @param {boolean} options.defaultStyleCamera if true, sets the default style + * camera * @returns {undefined} loads stylesheet into map * @throws {Error} if stylesheet is missing or invalid * @example @@ -207,6 +210,21 @@ void NodeMap::Load(const Nan::FunctionCallbackInfo<v8::Value>& info) { return Nan::ThrowError(ex.what()); } + if (info.Length() == 2) { + if (!info[1]->IsObject()) { + return Nan::ThrowTypeError("Second argument must be an options object"); + } + auto options = Nan::To<v8::Object>(info[1]).ToLocalChecked(); + if (Nan::Has(options, Nan::New("defaultStyleCamera").ToLocalChecked()).FromJust()) { + if (!Nan::Get(options, Nan::New("defaultStyleCamera").ToLocalChecked()).ToLocalChecked()->IsBoolean()) { + return Nan::ThrowError("Options object 'defaultStyleCamera' property must be a boolean"); + } + if (Nan::Get(options, Nan::New("cameraMutated").ToLocalChecked()).ToLocalChecked()->BooleanValue()) { + nodeMap->map->jumpTo(nodeMap->map->getStyle().getDefaultCamera()); + } + } + } + nodeMap->loaded = true; info.GetReturnValue().SetUndefined(); @@ -357,28 +375,13 @@ void NodeMap::startRender(NodeMap::RenderOptions options) { frontend->setSize(options.size); map->setSize(options.size); - if (map->getZoom() != options.zoom) { - map->setZoom(options.zoom); - } - - mbgl::LatLng latLng(options.latitude, options.longitude); - if (map->getLatLng() != latLng) { - map->setLatLng(latLng); - } - - if (map->getBearing() != options.bearing) { - map->setBearing(options.bearing); - } - - if (map->getPitch() != options.pitch) { - map->setPitch(options.pitch); - } - - if (map->getDebug() != options.debugOptions) { - map->setDebug(options.debugOptions); - } + mbgl::CameraOptions camera; + camera.center = mbgl::LatLng { options.latitude, options.longitude }; + camera.zoom = options.zoom; + camera.angle = -options.bearing * mbgl::util::DEG2RAD; + camera.pitch = options.pitch * mbgl::util::DEG2RAD; - map->renderStill([this](const std::exception_ptr eptr) { + map->renderStill(camera, options.debugOptions, [this](const std::exception_ptr eptr) { if (eptr) { error = std::move(eptr); uv_async_send(async); diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index 90e0e22e70..a8f5a1c7b1 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -9,10 +9,6 @@ "query-tests/symbol-features-in/tilted-outside": "https://github.com/mapbox/mapbox-gl-native/issues/9435", "query-tests/world-wrapping/box": "skip - needs issue", "query-tests/world-wrapping/point": "skip - needs issue", - "render-tests/circle-pitch-alignment/map-scale-map": "https://github.com/mapbox/mapbox-gl-native/issues/9349", - "render-tests/circle-pitch-alignment/map-scale-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9349", - "render-tests/circle-pitch-alignment/viewport-scale-map": "https://github.com/mapbox/mapbox-gl-native/issues/9349", - "render-tests/circle-pitch-alignment/viewport-scale-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9349", "render-tests/debug/collision-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/3841", "render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/3841", "render-tests/debug/collision-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/3841", @@ -21,6 +17,7 @@ "render-tests/debug/tile": "https://github.com/mapbox/mapbox-gl-native/issues/3841", "render-tests/extent/1024-circle": "needs investigation", "render-tests/extent/1024-symbol": "needs investigation", + "render-tests/fill-extrusion-multiple/multiple": "https://github.com/mapbox/mapbox-gl-native/issues/9894", "render-tests/fill-extrusion-pattern/@2x": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/fill-extrusion-pattern/function-2": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/fill-extrusion-pattern/function": "https://github.com/mapbox/mapbox-gl-js/issues/3327", @@ -39,25 +36,18 @@ "render-tests/line-width/property-function": "https://github.com/mapbox/mapbox-gl-js/issues/3682#issuecomment-264348200", "render-tests/line-join/property-function": "https://github.com/mapbox/mapbox-gl-js/pull/5020", "render-tests/line-join/property-function-dasharray": "https://github.com/mapbox/mapbox-gl-js/pull/5020", + "render-tests/line-opacity/step-curve": "https://github.com/mapbox/mapbox-gl-native/pull/9439", "render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927", - "render-tests/regressions/mapbox-gl-js#2929": "skip - needs issue", - "render-tests/regressions/mapbox-gl-js#3010": "skip - needs issue", "render-tests/regressions/mapbox-gl-js#3548": "skip - needs issue", "render-tests/regressions/mapbox-gl-js#3682": "https://github.com/mapbox/mapbox-gl-js/issues/3682", - "render-tests/regressions/mapbox-gl-js#4550": "skip - https://github.com/mapbox/mapbox-gl-native/issues/1350", - "render-tests/regressions/mapbox-gl-js#4551": "skip - https://github.com/mapbox/mapbox-gl-native/issues/1350", - "render-tests/regressions/mapbox-gl-js#4573": "skip - https://github.com/mapbox/mapbox-gl-native/issues/1350", "render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357", "render-tests/runtime-styling/image-add-sdf": "skip - https://github.com/mapbox/mapbox-gl-native/issues/9847", "render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "skip - https://github.com/mapbox/mapbox-gl-native/issues/6745", "render-tests/runtime-styling/set-style-paint-property-fill-flat-to-extrude": "skip - needs issue", "render-tests/runtime-styling/source-add-geojson-inline": "skip - needs issue", "render-tests/symbol-placement/line": "needs issue", + "render-tests/text-font/camera-function": "https://github.com/mapbox/mapbox-gl-native/pull/9439", "render-tests/text-keep-upright/line-placement-true-offset": "https://github.com/mapbox/mapbox-gl-native/issues/9271", - "render-tests/text-letter-spacing/property-function": "https://github.com/mapbox/mapbox-gl-native/issues/9668", - "render-tests/text-letter-spacing/zoom-and-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/9668", - "render-tests/text-max-width/property-function": "https://github.com/mapbox/mapbox-gl-native/issues/9654", - "render-tests/text-max-width/zoom-and-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/9654", "render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-alignment/auto-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", @@ -67,6 +57,6 @@ "render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-alignment/viewport-text-rotation-alignment-viewport": "https://github.com/mapbox/mapbox-gl-native/issues/9732", "render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732", - "render-tests/text-size/composite-function-high-base": "https://github.com/mapbox/mapbox-gl-native/issues/8654", - "render-tests/video/default": "skip - needs issue" + "render-tests/text-size/composite-expression": "https://github.com/mapbox/mapbox-gl-native/pull/9439", + "render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601" } diff --git a/platform/node/test/render.test.js b/platform/node/test/render.test.js index c8a214e919..ce549c1e27 100644 --- a/platform/node/test/render.test.js +++ b/platform/node/test/render.test.js @@ -5,9 +5,21 @@ const suiteImplementation = require('./suite_implementation'); const ignores = require('./ignores.json'); let tests; +let shuffle = false; +let recycleMap = false; + +function checkParameter(param) { + const index = tests.indexOf(param); + if (index === -1) + return false; + tests.splice(index, 1); + return true; +} if (process.argv[1] === __filename && process.argv.length > 2) { - tests = process.argv.slice(2); + tests = process.argv.slice(2).filter((value, index, self) => { return self.indexOf(value) === index; }); + shuffle = checkParameter('--shuffle'); + recycleMap = checkParameter('--recycle-map'); } -suite.run('native', {tests: tests, ignores: ignores}, suiteImplementation); +suite.run('native', {tests, ignores, shuffle, recycleMap}, suiteImplementation); diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js index 261de632e7..323f429bed 100644 --- a/platform/node/test/suite_implementation.js +++ b/platform/node/test/suite_implementation.js @@ -10,23 +10,27 @@ mbgl.on('message', function(msg) { console.log('%s (%s): %s', msg.severity, msg.class, msg.text); }); +// Map of map objects by pixel ratio +var maps = new Map(); + module.exports = function (style, options, callback) { - var map = new mbgl.Map({ - ratio: options.pixelRatio, - request: function(req, callback) { - request(req.url, {encoding: null}, function (err, response, body) { - if (err) { - callback(err); - } else if (response.statusCode == 404) { - callback(); - } else if (response.statusCode != 200) { - callback(new Error(response.statusMessage)); - } else { - callback(null, {data: body}); - } - }); + if (options.recycleMap) { + if (maps.has(options.pixelRatio)) { + var map = maps.get(options.pixelRatio); + map.request = mapRequest; + } else { + maps.set(options.pixelRatio, new mbgl.Map({ + ratio: options.pixelRatio, + request: mapRequest + })); + var map = maps.get(options.pixelRatio); } - }); + } else { + var map = new mbgl.Map({ + ratio: options.pixelRatio, + request: mapRequest + }); + } var timedOut = false; var watchdog = setTimeout(function () { @@ -46,14 +50,30 @@ module.exports = function (style, options, callback) { options.bearing = style.bearing || 0; options.pitch = style.pitch || 0; - map.load(style); + map.load(style, { defaultStyleCamera: true }); + + function mapRequest(req, callback) { + request(req.url, {encoding: null}, function (err, response, body) { + if (err) { + callback(err); + } else if (response.statusCode == 404) { + callback(); + } else if (response.statusCode != 200) { + callback(new Error(response.statusMessage)); + } else { + callback(null, {data: body}); + } + }); + }; applyOperations(options.operations, function() { map.render(options, function (err, pixels) { var results = options.queryGeometry ? map.queryRenderedFeatures(options.queryGeometry, options.queryOptions || {}) : []; - map.release(); + if (!options.recycleMap) { + map.release(); + } if (timedOut) return; clearTimeout(watchdog); callback(err, pixels, results.map(prepareFeatures)); diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index dbf8387ae0..1f2d01e9eb 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -39,19 +39,22 @@ AnnotationID AnnotationManager::addAnnotation(const Annotation& annotation, cons Annotation::visit(annotation, [&] (const auto& annotation_) { this->add(id, annotation_, maxZoom); }); + dirty = true; return id; } -Update AnnotationManager::updateAnnotation(const AnnotationID& id, const Annotation& annotation, const uint8_t maxZoom) { +bool AnnotationManager::updateAnnotation(const AnnotationID& id, const Annotation& annotation, const uint8_t maxZoom) { std::lock_guard<std::mutex> lock(mutex); - return Annotation::visit(annotation, [&] (const auto& annotation_) { - return this->update(id, annotation_, maxZoom); + Annotation::visit(annotation, [&] (const auto& annotation_) { + this->update(id, annotation_, maxZoom); }); + return dirty; } void AnnotationManager::removeAnnotation(const AnnotationID& id) { std::lock_guard<std::mutex> lock(mutex); remove(id); + dirty = true; } void AnnotationManager::add(const AnnotationID& id, const SymbolAnnotation& annotation, const uint8_t) { @@ -72,49 +75,45 @@ void AnnotationManager::add(const AnnotationID& id, const FillAnnotation& annota impl.updateStyle(*style.get().impl); } -Update AnnotationManager::update(const AnnotationID& id, const SymbolAnnotation& annotation, const uint8_t maxZoom) { - Update result = Update::Nothing; - +void AnnotationManager::update(const AnnotationID& id, const SymbolAnnotation& annotation, const uint8_t maxZoom) { auto it = symbolAnnotations.find(id); if (it == symbolAnnotations.end()) { assert(false); // Attempt to update a non-existent symbol annotation - return result; + return; } const SymbolAnnotation& existing = it->second->annotation; if (existing.geometry != annotation.geometry || existing.icon != annotation.icon) { - result |= Update::AnnotationData; + dirty = true; remove(id); add(id, annotation, maxZoom); } - - return result; } -Update AnnotationManager::update(const AnnotationID& id, const LineAnnotation& annotation, const uint8_t maxZoom) { +void AnnotationManager::update(const AnnotationID& id, const LineAnnotation& annotation, const uint8_t maxZoom) { auto it = shapeAnnotations.find(id); if (it == shapeAnnotations.end()) { assert(false); // Attempt to update a non-existent shape annotation - return Update::Nothing; + return; } shapeAnnotations.erase(it); add(id, annotation, maxZoom); - return Update::AnnotationData; + dirty = true; } -Update AnnotationManager::update(const AnnotationID& id, const FillAnnotation& annotation, const uint8_t maxZoom) { +void AnnotationManager::update(const AnnotationID& id, const FillAnnotation& annotation, const uint8_t maxZoom) { auto it = shapeAnnotations.find(id); if (it == shapeAnnotations.end()) { assert(false); // Attempt to update a non-existent shape annotation - return Update::Nothing; + return; } shapeAnnotations.erase(it); add(id, annotation, maxZoom); - return Update::AnnotationData; + dirty = true; } void AnnotationManager::remove(const AnnotationID& id) { @@ -187,8 +186,11 @@ void AnnotationManager::updateStyle() { void AnnotationManager::updateData() { std::lock_guard<std::mutex> lock(mutex); - for (auto& tile : tiles) { - tile->setData(getTileData(tile->id.canonical)); + if (dirty) { + for (auto& tile : tiles) { + tile->setData(getTileData(tile->id.canonical)); + } + dirty = false; } } diff --git a/src/mbgl/annotation/annotation_manager.hpp b/src/mbgl/annotation/annotation_manager.hpp index dee823bc0f..a028a8f1ba 100644 --- a/src/mbgl/annotation/annotation_manager.hpp +++ b/src/mbgl/annotation/annotation_manager.hpp @@ -3,7 +3,6 @@ #include <mbgl/annotation/annotation.hpp> #include <mbgl/annotation/symbol_annotation_impl.hpp> #include <mbgl/style/image.hpp> -#include <mbgl/map/update.hpp> #include <mbgl/util/noncopyable.hpp> #include <mutex> @@ -30,7 +29,7 @@ public: ~AnnotationManager(); AnnotationID addAnnotation(const Annotation&, const uint8_t maxZoom); - Update updateAnnotation(const AnnotationID&, const Annotation&, const uint8_t maxZoom); + bool updateAnnotation(const AnnotationID&, const Annotation&, const uint8_t maxZoom); void removeAnnotation(const AnnotationID&); void addImage(std::unique_ptr<style::Image>); @@ -53,9 +52,9 @@ private: void add(const AnnotationID&, const LineAnnotation&, const uint8_t); void add(const AnnotationID&, const FillAnnotation&, const uint8_t); - Update update(const AnnotationID&, const SymbolAnnotation&, const uint8_t); - Update update(const AnnotationID&, const LineAnnotation&, const uint8_t); - Update update(const AnnotationID&, const FillAnnotation&, const uint8_t); + void update(const AnnotationID&, const SymbolAnnotation&, const uint8_t); + void update(const AnnotationID&, const LineAnnotation&, const uint8_t); + void update(const AnnotationID&, const FillAnnotation&, const uint8_t); void remove(const AnnotationID&); @@ -67,6 +66,8 @@ private: std::mutex mutex; + bool dirty = false; + AnnotationID nextID = 0; using SymbolAnnotationTree = boost::geometry::index::rtree<std::shared_ptr<const SymbolAnnotationImpl>, boost::geometry::index::rstar<16, 4>>; diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index adb8ce5927..2c90b69b08 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -205,11 +205,11 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph const Shaping result = getShaping( /* string */ text, /* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ? - layout.get<TextMaxWidth>() * oneEm : 0, + layout.evaluate<TextMaxWidth>(zoom, feature) * oneEm : 0, /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm, /* anchor */ layout.evaluate<TextAnchor>(zoom, feature), /* justify */ layout.evaluate<TextJustify>(zoom, feature), - /* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.get<TextLetterSpacing>() * oneEm : 0.0f, + /* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * oneEm : 0.0f, /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm), /* verticalHeight */ oneEm, /* writingMode */ writingMode, diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 324bcc8b5d..7534fe67ad 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -50,7 +50,7 @@ public: // StyleObserver void onSourceChanged(style::Source&) override; - void onUpdate(Update) override; + void onUpdate() override; void onStyleLoading() override; void onStyleLoaded() override; void onStyleError(std::exception_ptr) override; @@ -166,11 +166,18 @@ void Map::renderStill(StillImageCallback callback) { impl->stillImageRequest = std::make_unique<StillImageRequest>(std::move(callback)); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); +} + +void Map::renderStill(const CameraOptions& camera, MapDebugOptions debugOptions, StillImageCallback callback) { + impl->cameraMutated = true; + impl->debugOptions = debugOptions; + impl->transform.jumpTo(camera); + renderStill(std::move(callback)); } void Map::triggerRepaint() { - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } #pragma mark - Map::Impl RendererObserver @@ -194,7 +201,7 @@ void Map::Impl::onDidFinishRenderingFrame(RenderMode renderMode, bool needsRepai observer.onDidFinishRenderingFrame(MapObserver::RenderMode(renderMode)); if (needsRepaint || transform.inTransition()) { - onUpdate(Update::Repaint); + onUpdate(); } } else if (stillImageRequest && rendererFullyLoaded) { auto request = std::move(stillImageRequest); @@ -233,12 +240,12 @@ void Map::setStyle(std::unique_ptr<Style> style) { void Map::cancelTransitions() { impl->transform.cancelTransitions(); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setGestureInProgress(bool inProgress) { impl->transform.setGestureInProgress(inProgress); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } bool Map::isGestureInProgress() const { @@ -266,19 +273,19 @@ CameraOptions Map::getCameraOptions(const EdgeInsets& padding) const { void Map::jumpTo(const CameraOptions& camera) { impl->cameraMutated = true; impl->transform.jumpTo(camera); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::easeTo(const CameraOptions& camera, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.easeTo(camera, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::flyTo(const CameraOptions& camera, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.flyTo(camera, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } #pragma mark - Position @@ -286,7 +293,7 @@ void Map::flyTo(const CameraOptions& camera, const AnimationOptions& animation) void Map::moveBy(const ScreenCoordinate& point, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.moveBy(point, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setLatLng(const LatLng& latLng, const AnimationOptions& animation) { @@ -297,13 +304,13 @@ void Map::setLatLng(const LatLng& latLng, const AnimationOptions& animation) { void Map::setLatLng(const LatLng& latLng, const EdgeInsets& padding, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setLatLng(latLng, padding, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setLatLng(const LatLng& latLng, optional<ScreenCoordinate> anchor, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setLatLng(latLng, anchor, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } LatLng Map::getLatLng(const EdgeInsets& padding) const { @@ -319,7 +326,7 @@ void Map::resetPosition(const EdgeInsets& padding) { camera.padding = padding; camera.zoom = 0; impl->transform.jumpTo(camera); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } @@ -333,13 +340,13 @@ void Map::setZoom(double zoom, const AnimationOptions& animation) { void Map::setZoom(double zoom, optional<ScreenCoordinate> anchor, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setZoom(zoom, anchor, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setZoom(double zoom, const EdgeInsets& padding, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setZoom(zoom, padding, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } double Map::getZoom() const { @@ -354,7 +361,7 @@ void Map::setLatLngZoom(const LatLng& latLng, double zoom, const AnimationOption void Map::setLatLngZoom(const LatLng& latLng, double zoom, const EdgeInsets& padding, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setLatLngZoom(latLng, zoom, padding, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } CameraOptions Map::cameraForLatLngBounds(const LatLngBounds& bounds, const EdgeInsets& padding) const { @@ -444,7 +451,7 @@ optional<LatLngBounds> Map::getLatLngBounds() const { void Map::setLatLngBounds(optional<LatLngBounds> bounds) { impl->cameraMutated = true; impl->transform.setLatLngBounds(bounds); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setMinZoom(const double minZoom) { @@ -495,7 +502,7 @@ double Map::getMaxPitch() const { void Map::setSize(const Size size) { impl->transform.resize(size); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } Size Map::getSize() const { @@ -507,7 +514,7 @@ Size Map::getSize() const { void Map::rotateBy(const ScreenCoordinate& first, const ScreenCoordinate& second, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.rotateBy(first, second, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setBearing(double degrees, const AnimationOptions& animation) { @@ -518,13 +525,13 @@ void Map::setBearing(double degrees, const AnimationOptions& animation) { void Map::setBearing(double degrees, optional<ScreenCoordinate> anchor, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setAngle(-degrees * util::DEG2RAD, anchor, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::setBearing(double degrees, const EdgeInsets& padding, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setAngle(-degrees * util::DEG2RAD, padding, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } double Map::getBearing() const { @@ -534,7 +541,7 @@ double Map::getBearing() const { void Map::resetNorth(const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setAngle(0, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } #pragma mark - Pitch @@ -547,7 +554,7 @@ void Map::setPitch(double pitch, const AnimationOptions& animation) { void Map::setPitch(double pitch, optional<ScreenCoordinate> anchor, const AnimationOptions& animation) { impl->cameraMutated = true; impl->transform.setPitch(pitch * util::DEG2RAD, anchor, animation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } double Map::getPitch() const { @@ -558,7 +565,7 @@ double Map::getPitch() const { void Map::setNorthOrientation(NorthOrientation orientation) { impl->transform.setNorthOrientation(orientation); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } NorthOrientation Map::getNorthOrientation() const { @@ -569,7 +576,7 @@ NorthOrientation Map::getNorthOrientation() const { void Map::setConstrainMode(mbgl::ConstrainMode mode) { impl->transform.setConstrainMode(mode); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } ConstrainMode Map::getConstrainMode() const { @@ -580,7 +587,7 @@ ConstrainMode Map::getConstrainMode() const { void Map::setViewportMode(mbgl::ViewportMode mode) { impl->transform.setViewportMode(mode); - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } ViewportMode Map::getViewportMode() const { @@ -618,24 +625,26 @@ double Map::getTopOffsetPixelsForAnnotationImage(const std::string& id) { AnnotationID Map::addAnnotation(const Annotation& annotation) { auto result = impl->annotationManager.addAnnotation(annotation, getMaxZoom()); - impl->onUpdate(Update::AnnotationData); + impl->onUpdate(); return result; } void Map::updateAnnotation(AnnotationID id, const Annotation& annotation) { - impl->onUpdate(impl->annotationManager.updateAnnotation(id, annotation, getMaxZoom())); + if (impl->annotationManager.updateAnnotation(id, annotation, getMaxZoom())) { + impl->onUpdate(); + } } void Map::removeAnnotation(AnnotationID annotation) { impl->annotationManager.removeAnnotation(annotation); - impl->onUpdate(Update::AnnotationData); + impl->onUpdate(); } #pragma mark - Toggles void Map::setDebug(MapDebugOptions debugOptions) { impl->debugOptions = debugOptions; - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } void Map::cycleDebugOptions() { @@ -659,7 +668,7 @@ void Map::cycleDebugOptions() { else impl->debugOptions = MapDebugOptions::TileBorders; - impl->onUpdate(Update::Repaint); + impl->onUpdate(); } MapDebugOptions Map::getDebug() const { @@ -683,18 +692,14 @@ void Map::Impl::onSourceChanged(style::Source& source) { } void Map::Impl::onInvalidate() { - onUpdate(Update::Repaint); + onUpdate(); } -void Map::Impl::onUpdate(Update flags) { +void Map::Impl::onUpdate() { TimePoint timePoint = mode == MapMode::Continuous ? Clock::now() : Clock::time_point::max(); transform.updateTransitions(timePoint); - if (flags & Update::AnnotationData) { - annotationManager.updateData(); - } - UpdateParameters params = { style->impl->isLoaded(), mode, diff --git a/src/mbgl/map/update.hpp b/src/mbgl/map/update.hpp deleted file mode 100644 index 057720a5c9..0000000000 --- a/src/mbgl/map/update.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include <mbgl/util/traits.hpp> -#include <mbgl/util/util.hpp> - -namespace mbgl { - -enum class Update { - Nothing = 0, - Repaint = 1 << 0, - AnnotationData = 1 << 7 -}; - -MBGL_CONSTEXPR Update operator|(Update lhs, Update rhs) { - return Update(mbgl::underlying_type(lhs) | mbgl::underlying_type(rhs)); -} - -MBGL_CONSTEXPR Update& operator|=(Update& lhs, const Update& rhs) { - return (lhs = lhs | rhs); -} - -MBGL_CONSTEXPR bool operator& (Update lhs, Update rhs) { - return mbgl::underlying_type(lhs) & mbgl::underlying_type(rhs); -} - -} // namespace mbgl diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index 3a38453d30..d023ec7d83 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -23,7 +23,7 @@ inline uint16_t packUint8Pair(T a, T b) { MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_extrude); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_pos_offset); -MBGL_DEFINE_ATTRIBUTE(int16_t, 3, a_pos_normal); +MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_pos_normal); MBGL_DEFINE_ATTRIBUTE(float, 3, a_projected_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_label_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); diff --git a/src/mbgl/programs/line_program.cpp b/src/mbgl/programs/line_program.cpp index f9e91f569f..faf57ef19b 100644 --- a/src/mbgl/programs/line_program.cpp +++ b/src/mbgl/programs/line_program.cpp @@ -10,7 +10,7 @@ namespace mbgl { using namespace style; -static_assert(sizeof(LineLayoutVertex) == 10, "expected LineLayoutVertex size"); +static_assert(sizeof(LineLayoutVertex) == 12, "expected LineLayoutVertex size"); template <class Values, class...Args> Values makeValues(const RenderLinePaintProperties::PossiblyEvaluated& properties, diff --git a/src/mbgl/programs/line_program.hpp b/src/mbgl/programs/line_program.hpp index 95b9362b85..da9964e623 100644 --- a/src/mbgl/programs/line_program.hpp +++ b/src/mbgl/programs/line_program.hpp @@ -59,7 +59,8 @@ public: {{ p.x, p.y, - static_cast<int16_t>(attributes::packUint8Pair(round ? 1 : 0, up ? 1 : 0)) + static_cast<int16_t>(round ? 1 : 0), + static_cast<int16_t>(up ? 1 : -1) }}, {{ // add 128 to store a byte in an unsigned byte diff --git a/src/mbgl/renderer/layers/render_fill_layer.cpp b/src/mbgl/renderer/layers/render_fill_layer.cpp index 394642a50d..22cb9563c1 100644 --- a/src/mbgl/renderer/layers/render_fill_layer.cpp +++ b/src/mbgl/renderer/layers/render_fill_layer.cpp @@ -99,7 +99,9 @@ void RenderFillLayer::render(PaintParameters& parameters, RenderSource*) { && evaluated.get<FillOpacity>().constantOr(0) >= 1.0f) == (parameters.pass == RenderPass::Opaque)) { draw(parameters.programs.fill, gl::Triangles(), - parameters.depthModeForSublayer(1, gl::DepthMode::ReadWrite), + parameters.depthModeForSublayer(1, parameters.pass == RenderPass::Opaque + ? gl::DepthMode::ReadWrite + : gl::DepthMode::ReadOnly), *bucket.triangleIndexBuffer, bucket.triangleSegments); } diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 21ff8ed478..9894bdc12f 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -1,3 +1,4 @@ +#include <mbgl/annotation/annotation_manager.hpp> #include <mbgl/renderer/renderer_impl.hpp> #include <mbgl/renderer/renderer_backend.hpp> #include <mbgl/renderer/renderer_observer.hpp> @@ -71,6 +72,8 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (updateParameters.mode == MapMode::Still && !updateParameters.stillImageRequest) return; assert(BackendScope::exists()); + + updateParameters.annotationManager.updateData(); const bool zoomChanged = zoomHistory.update(updateParameters.transformState.getZoom(), updateParameters.timePoint); @@ -456,10 +459,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { } #endif - int indent = 0; - // Actually render the layers - if (debug::renderTree) { Log::Info(Event::Render, "{"); indent++; } parameters.depthRangeSize = 1 - (order.size() + 2) * parameters.numSublayers * parameters.depthEpsilon; @@ -469,10 +469,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { parameters.pass = RenderPass::Opaque; MBGL_DEBUG_GROUP(parameters.context, "opaque"); - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s {", indent++ * 4, "", "opaque"); - } - uint32_t i = 0; for (auto it = order.rbegin(); it != order.rend(); ++it, ++i) { parameters.currentLayer = i; @@ -481,10 +477,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { it->layer.render(parameters, it->source); } } - - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); - } } // - TRANSLUCENT PASS -------------------------------------------------------------------------- @@ -493,10 +485,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { parameters.pass = RenderPass::Translucent; MBGL_DEBUG_GROUP(parameters.context, "translucent"); - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s {", indent++ * 4, "", "translucent"); - } - uint32_t i = static_cast<uint32_t>(order.size()) - 1; for (auto it = order.begin(); it != order.end(); ++it, --i) { parameters.currentLayer = i; @@ -505,14 +493,8 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { it->layer.render(parameters, it->source); } } - - if (debug::renderTree) { - Log::Info(Event::Render, "%*s%s", --indent * 4, "", "}"); - } } - if (debug::renderTree) { Log::Info(Event::Render, "}"); indent--; } - // - DEBUG PASS -------------------------------------------------------------------------------- // Renders debug overlays. { diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index d6a81e13ca..f7976d7210 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -34,19 +34,24 @@ void RenderGeoJSONSource::update(Immutable<style::Source::Impl> baseImpl_, GeoJSONData* data_ = impl().getData(); - if (!data_) { - return; - } - if (data_ != data) { data = data_; tilePyramid.cache.clear(); - for (auto const& item : tilePyramid.tiles) { - static_cast<GeoJSONTile*>(item.second.get())->updateData(data->getTile(item.first.canonical)); + if (data) { + for (auto const& item : tilePyramid.tiles) { + static_cast<GeoJSONTile*>(item.second.get())->updateData(data->getTile(item.first.canonical)); + } + } else { + tilePyramid.tiles.clear(); + tilePyramid.renderTiles.clear(); } } + if (!data) { + return; + } + tilePyramid.update(layers, needsRendering, needsRelayout, diff --git a/src/mbgl/shaders/line.cpp b/src/mbgl/shaders/line.cpp index f68cc91377..c700295a15 100644 --- a/src/mbgl/shaders/line.cpp +++ b/src/mbgl/shaders/line.cpp @@ -21,7 +21,7 @@ const char* line::vertexSource = R"MBGL_SHADER( // #define scale 63.0 #define scale 0.015873016 -attribute vec3 a_pos_normal; +attribute vec4 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -133,12 +133,9 @@ void main() { vec2 pos = a_pos_normal.xy; - // transform y normal so that 0 => -1 and 1 => 1 - // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap + // x is 1 if it's a round cap, 0 otherwise // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = unpack_float(a_pos_normal.z); - normal.y = sign(normal.y - 0.5); - + mediump vec2 normal = a_pos_normal.zw; v_normal = normal; // these transformations used to be applied in the JS and native code bases. diff --git a/src/mbgl/shaders/line_pattern.cpp b/src/mbgl/shaders/line_pattern.cpp index f1e64577e2..f8d785ade9 100644 --- a/src/mbgl/shaders/line_pattern.cpp +++ b/src/mbgl/shaders/line_pattern.cpp @@ -23,7 +23,7 @@ const char* line_pattern::vertexSource = R"MBGL_SHADER( // Retina devices need a smaller distance to avoid aliasing. #define ANTIALIASING 1.0 / DEVICE_PIXEL_RATIO / 2.0 -attribute vec3 a_pos_normal; +attribute vec4 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -121,12 +121,9 @@ void main() { vec2 pos = a_pos_normal.xy; - // transform y normal so that 0 => -1 and 1 => 1 - // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap + // x is 1 if it's a round cap, 0 otherwise // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = unpack_float(a_pos_normal.z); - normal.y = sign(normal.y - 0.5); - + mediump vec2 normal = a_pos_normal.zw; v_normal = normal; // these transformations used to be applied in the JS and native code bases. diff --git a/src/mbgl/shaders/line_sdf.cpp b/src/mbgl/shaders/line_sdf.cpp index dd81433543..c5d50566e8 100644 --- a/src/mbgl/shaders/line_sdf.cpp +++ b/src/mbgl/shaders/line_sdf.cpp @@ -23,7 +23,7 @@ const char* line_sdf::vertexSource = R"MBGL_SHADER( // Retina devices need a smaller distance to avoid aliasing. #define ANTIALIASING 1.0 / DEVICE_PIXEL_RATIO / 2.0 -attribute vec3 a_pos_normal; +attribute vec4 a_pos_normal; attribute vec4 a_data; uniform mat4 u_matrix; @@ -159,12 +159,9 @@ void main() { vec2 pos = a_pos_normal.xy; - // transform y normal so that 0 => -1 and 1 => 1 - // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap + // x is 1 if it's a round cap, 0 otherwise // y is 1 if the normal points up, and -1 if it points down - mediump vec2 normal = unpack_float(a_pos_normal.z); - normal.y = sign(normal.y - 0.5); - + mediump vec2 normal = a_pos_normal.zw; v_normal = normal; // these transformations used to be applied in the JS and native code bases. diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 2b86b26025..9a944657ca 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -444,15 +444,15 @@ void SymbolLayer::setTextSize(DataDrivenPropertyValue<float> value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -PropertyValue<float> SymbolLayer::getDefaultTextMaxWidth() { +DataDrivenPropertyValue<float> SymbolLayer::getDefaultTextMaxWidth() { return TextMaxWidth::defaultValue(); } -PropertyValue<float> SymbolLayer::getTextMaxWidth() const { +DataDrivenPropertyValue<float> SymbolLayer::getTextMaxWidth() const { return impl().layout.get<TextMaxWidth>(); } -void SymbolLayer::setTextMaxWidth(PropertyValue<float> value) { +void SymbolLayer::setTextMaxWidth(DataDrivenPropertyValue<float> value) { if (value == getTextMaxWidth()) return; auto impl_ = mutableImpl(); @@ -476,15 +476,15 @@ void SymbolLayer::setTextLineHeight(PropertyValue<float> value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -PropertyValue<float> SymbolLayer::getDefaultTextLetterSpacing() { +DataDrivenPropertyValue<float> SymbolLayer::getDefaultTextLetterSpacing() { return TextLetterSpacing::defaultValue(); } -PropertyValue<float> SymbolLayer::getTextLetterSpacing() const { +DataDrivenPropertyValue<float> SymbolLayer::getTextLetterSpacing() const { return impl().layout.get<TextLetterSpacing>(); } -void SymbolLayer::setTextLetterSpacing(PropertyValue<float> value) { +void SymbolLayer::setTextLetterSpacing(DataDrivenPropertyValue<float> value) { if (value == getTextLetterSpacing()) return; auto impl_ = mutableImpl(); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 0d163b9fb9..436b5cbd4f 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -122,7 +122,7 @@ struct TextSize : DataDrivenLayoutProperty<float> { static float defaultValue() { return 16; } }; -struct TextMaxWidth : LayoutProperty<float> { +struct TextMaxWidth : DataDrivenLayoutProperty<float> { static constexpr const char * key = "text-max-width"; static float defaultValue() { return 10; } }; @@ -132,7 +132,7 @@ struct TextLineHeight : LayoutProperty<float> { static float defaultValue() { return 1.2; } }; -struct TextLetterSpacing : LayoutProperty<float> { +struct TextLetterSpacing : DataDrivenLayoutProperty<float> { static constexpr const char * key = "text-letter-spacing"; static float defaultValue() { return 0; } }; diff --git a/src/mbgl/style/observer.hpp b/src/mbgl/style/observer.hpp index ea19c599e9..cc6378b366 100644 --- a/src/mbgl/style/observer.hpp +++ b/src/mbgl/style/observer.hpp @@ -1,7 +1,6 @@ #pragma once #include <mbgl/style/source_observer.hpp> -#include <mbgl/map/update.hpp> #include <exception> @@ -12,7 +11,7 @@ class Observer : public SourceObserver { public: virtual void onStyleLoading() {} virtual void onStyleLoaded() {} - virtual void onUpdate(Update) {} + virtual void onUpdate() {} virtual void onStyleError(std::exception_ptr) {} virtual void onResourceError(std::exception_ptr) {} }; diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 0fb49d1d22..37907d3f60 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -88,7 +88,7 @@ void Style::Impl::parse(const std::string& json_) { } mutated = false; - loaded = true; + loaded = false; json = json_; sources.clear(); @@ -118,6 +118,7 @@ void Style::Impl::parse(const std::string& json_) { spriteLoader->load(parser.spriteURL, scheduler, fileSource); glyphURL = parser.glyphURL; + loaded = true; observer->onStyleLoaded(); } @@ -203,7 +204,7 @@ Layer* Style::Impl::addLayer(std::unique_ptr<Layer> layer, optional<std::string> } layer->setObserver(this); - observer->onUpdate(Update::Repaint); + observer->onUpdate(); return layers.add(std::move(layer), before); } @@ -213,7 +214,7 @@ std::unique_ptr<Layer> Style::Impl::removeLayer(const std::string& id) { if (layer) { layer->setObserver(nullptr); - observer->onUpdate(Update::Repaint); + observer->onUpdate(); } return layer; @@ -288,13 +289,13 @@ void Style::Impl::setObserver(style::Observer* observer_) { void Style::Impl::onSourceLoaded(Source& source) { sources.update(source); observer->onSourceLoaded(source); - observer->onUpdate(Update::Repaint); + observer->onUpdate(); } void Style::Impl::onSourceChanged(Source& source) { sources.update(source); observer->onSourceChanged(source); - observer->onUpdate(Update::Repaint); + observer->onUpdate(); } void Style::Impl::onSourceError(Source& source, std::exception_ptr error) { @@ -318,7 +319,7 @@ void Style::Impl::onSpriteLoaded(std::vector<std::unique_ptr<Image>>&& images_) addImage(std::move(image)); } spriteLoaded = true; - observer->onUpdate(Update::Repaint); // For *-pattern properties. + observer->onUpdate(); // For *-pattern properties. } void Style::Impl::onSpriteError(std::exception_ptr error) { @@ -329,11 +330,11 @@ void Style::Impl::onSpriteError(std::exception_ptr error) { void Style::Impl::onLayerChanged(Layer& layer) { layers.update(layer); - observer->onUpdate(Update::Repaint); + observer->onUpdate(); } void Style::Impl::onLightChanged(const Light&) { - observer->onUpdate(Update::Repaint); + observer->onUpdate(); } void Style::Impl::dumpDebugLogs() const { diff --git a/src/mbgl/util/constants.cpp b/src/mbgl/util/constants.cpp index 9faef140ef..56f78c9885 100644 --- a/src/mbgl/util/constants.cpp +++ b/src/mbgl/util/constants.cpp @@ -11,7 +11,6 @@ const bool tileParseWarnings = false; const bool styleParseWarnings = false; const bool spriteWarnings = false; const bool renderWarnings = false; -const bool renderTree = false; const bool labelTextMissingWarning = true; const bool missingFontStackWarning = true; const bool missingFontFaceWarning = true; @@ -22,7 +21,6 @@ const bool tileParseWarnings = false; const bool styleParseWarnings = false; const bool spriteWarnings = false; const bool renderWarnings = false; -const bool renderTree = false; const bool labelTextMissingWarning = false; const bool missingFontStackWarning = false; const bool missingFontFaceWarning = false; diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index b53e91162c..c06634c9b2 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -169,5 +169,26 @@ std::vector<UnwrappedTileID> tileCover(const TransformState& state, int32_t z) { z); } +// Taken from https://github.com/mapbox/sphericalmercator#xyzbbox-zoom-tms_style-srs +// Computes the projected tiles for the lower left and uppoer right points of the bounds +// and uses that to compute the tile cover count +unsigned long tileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_){ + + auto sw = Projection::project(bounds.southwest().wrapped(), zoom, tileSize_); + auto ne = Projection::project(bounds.northeast().wrapped(), zoom, tileSize_); + + auto x1 = floor(sw.x/ tileSize_); + auto x2 = floor((ne.x - 1) / tileSize_); + auto y1 = floor(sw.y/ tileSize_); + auto y2 = floor((ne.y - 1) / tileSize_); + + auto minX = std::fmax(std::min(x1, x2), 0); + auto maxX = std::max(x1, x2); + auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2); + auto maxY = (std::pow(2, zoom) - 1) - std::fmax(std::min(y1, y2), 0); + + return (maxX - minX + 1) * (maxY - minY + 1); +} + } // namespace util } // namespace mbgl diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index 2d32d8bf41..405e6a48e6 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -18,5 +18,8 @@ int32_t coveringZoomLevel(double z, SourceType type, uint16_t tileSize); std::vector<UnwrappedTileID> tileCover(const TransformState&, int32_t z); std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, int32_t z); +// Compute only the count of tiles needed for tileCover +unsigned long tileCount(const LatLngBounds&, uint8_t z, uint16_t tileSize); + } // namespace util } // namespace mbgl diff --git a/test/fixtures/style_parser/geojson-missing-properties.info.json b/test/fixtures/style_parser/geojson-missing-properties.info.json new file mode 100644 index 0000000000..9c25a2f488 --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-properties.info.json @@ -0,0 +1,6 @@ +{ + "default": { + "log": [ + ] + } +} diff --git a/test/fixtures/style_parser/geojson-missing-properties.style.json b/test/fixtures/style_parser/geojson-missing-properties.style.json new file mode 100644 index 0000000000..fc4fe97c78 --- /dev/null +++ b/test/fixtures/style_parser/geojson-missing-properties.style.json @@ -0,0 +1,9 @@ +{ + "version": 8, + "sources": { + "mapbox": { + "type": "geojson", + "data": { "type": "Feature", "geometry": { "type": "Point", "coordinates": [100.0, 0.0] } } + } + } +} diff --git a/test/storage/offline.test.cpp b/test/storage/offline.test.cpp index 0faaabc298..e7ebe5199f 100644 --- a/test/storage/offline.test.cpp +++ b/test/storage/offline.test.cpp @@ -52,3 +52,11 @@ TEST(OfflineTilePyramidRegionDefinition, TileCoverWrapped) { EXPECT_EQ((std::vector<CanonicalTileID>{ { 0, 0, 0 } }), region.tileCover(SourceType::Vector, 512, { 0, 22 })); } + +TEST(OfflineTilePyramidRegionDefinition, TileCount) { + OfflineTilePyramidRegionDefinition region("", sanFranciscoWrapped, 0, 22, 1.0); + + //These numbers match the count from tileCover().size(). + EXPECT_EQ(38424u, region.tileCount(SourceType::Vector, 512, { 10, 18 })); + EXPECT_EQ(9675240u, region.tileCount(SourceType::Vector, 512, { 3, 22 })); +} diff --git a/test/util/tile_cover.test.cpp b/test/util/tile_cover.test.cpp index c746e6dab5..933c18b5ea 100644 --- a/test/util/tile_cover.test.cpp +++ b/test/util/tile_cover.test.cpp @@ -84,3 +84,12 @@ TEST(TileCover, SanFranciscoZ0Wrapped) { EXPECT_EQ((std::vector<UnwrappedTileID>{ { 0, 1, 0 } }), util::tileCover(sanFranciscoWrapped, 0)); } + +TEST(TileCount, SanFranciscoZ10) { + EXPECT_EQ(4u, util::tileCount(sanFrancisco, 10, util::tileSize)); +} + +TEST(TileCount, SanFranciscoZ22) { + EXPECT_EQ(7254450u, util::tileCount(sanFrancisco, 22, util::tileSize)); +} + |