From f4c6fabbe6784418c64f9731fef2e3f87e7d7962 Mon Sep 17 00:00:00 2001 From: Geolives Date: Tue, 27 Feb 2024 18:42:17 +0100 Subject: [PATCH] Implementation of multi-sprites in mbgl-core (#1858) Co-authored-by: Christophe Brasseur Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers Co-authored-by: Bart Louwers --- .github/workflows/ios-ci.yml | 2 +- CMakeLists.txt | 3 + bazel/core.bzl | 4 + .../sprites/array-default-only/metrics.json | 35 ++++++++ .../sprites/array-multiple/metrics.json | 35 ++++++++ .../sprites/array-default-only/expected.png | Bin 0 -> 1545 bytes .../sprites/array-default-only/style.json | 56 +++++++++++++ .../sprites/array-multiple/expected.png | Bin 0 -> 1834 bytes .../sprites/array-multiple/style.json | 79 ++++++++++++++++++ .../sprites/array-default-only/metrics.json | 35 ++++++++ .../sprites/array-multiple/metrics.json | 35 ++++++++ platform/android/CHANGELOG.md | 1 + .../src/mbgl/storage/offline_download.cpp | 19 +++-- platform/ios/CHANGELOG.md | 4 + platform/ios/VERSION | 2 +- render-test/parser.cpp | 1 + src/mbgl/map/map_impl.cpp | 2 +- src/mbgl/sprite/sprite_loader.cpp | 64 +++++++------- src/mbgl/sprite/sprite_loader.hpp | 8 +- src/mbgl/sprite/sprite_loader_observer.hpp | 5 +- src/mbgl/sprite/sprite_parser.cpp | 22 ++++- src/mbgl/sprite/sprite_parser.hpp | 4 +- src/mbgl/style/conversion/sprite.cpp | 34 ++++++++ src/mbgl/style/conversion/sprite.hpp | 21 +++++ src/mbgl/style/parser.cpp | 42 +++++++++- src/mbgl/style/parser.hpp | 5 +- src/mbgl/style/sprite.cpp | 14 ++++ src/mbgl/style/sprite.hpp | 18 ++++ src/mbgl/style/style_impl.cpp | 48 +++++++++-- src/mbgl/style/style_impl.hpp | 7 +- .../sprites-missing-fields.info.json | 8 ++ .../sprites-missing-fields.style.json | 4 + .../sprites-not-same-ids.info.json | 7 ++ .../sprites-not-same-ids.style.json | 11 +++ test/renderer/image_manager.test.cpp | 3 +- test/renderer/pattern_atlas.test.cpp | 3 +- test/sprite/sprite_loader.test.cpp | 8 +- test/sprite/sprite_parser.test.cpp | 26 +++--- test/style/style_parser.test.cpp | 57 +++++++++++++ 39 files changed, 649 insertions(+), 83 deletions(-) create mode 100644 metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json create mode 100644 metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json create mode 100644 metrics/integration/render-tests/sprites/array-default-only/expected.png create mode 100644 metrics/integration/render-tests/sprites/array-default-only/style.json create mode 100644 metrics/integration/render-tests/sprites/array-multiple/expected.png create mode 100644 metrics/integration/render-tests/sprites/array-multiple/style.json create mode 100644 metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json create mode 100644 metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json create mode 100644 src/mbgl/style/conversion/sprite.cpp create mode 100644 src/mbgl/style/conversion/sprite.hpp create mode 100644 src/mbgl/style/sprite.cpp create mode 100644 src/mbgl/style/sprite.hpp create mode 100644 test/fixtures/style_parser/sprites-missing-fields.info.json create mode 100644 test/fixtures/style_parser/sprites-missing-fields.style.json create mode 100644 test/fixtures/style_parser/sprites-not-same-ids.info.json create mode 100644 test/fixtures/style_parser/sprites-not-same-ids.style.json diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 6373c7008ed..2504e6b3707 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -96,7 +96,7 @@ jobs: - name: Build RenderTest .ipa and .xctest for AWS Device Farm run: | set -e - bazel run --//:renderer=metal //platform/ios:xcodeproj + bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" build_dir="$(mktemp -d)" xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" diff --git a/CMakeLists.txt b/CMakeLists.txt index eadad187612..6fa5197b0da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -700,6 +700,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/collection.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/color_ramp_property_value.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/constant.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/sprite.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/coordinate.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/custom_geometry_source_options.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/filter.cpp @@ -759,6 +760,8 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/expression/within.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/filter.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/sprite.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/sprite.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/image_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layer.cpp diff --git a/bazel/core.bzl b/bazel/core.bzl index 6c22564d922..7fe2e6fcc77 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -396,6 +396,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/conversion/stringify.hpp", "src/mbgl/style/conversion/tileset.cpp", "src/mbgl/style/conversion/transition_options.cpp", + "src/mbgl/style/conversion/sprite.hpp", + "src/mbgl/style/conversion/sprite.cpp", "src/mbgl/style/custom_tile_loader.cpp", "src/mbgl/style/custom_tile_loader.hpp", "src/mbgl/style/expression/assertion.cpp", @@ -437,6 +439,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/expression/value.cpp", "src/mbgl/style/expression/within.cpp", "src/mbgl/style/filter.cpp", + "src/mbgl/style/sprite.hpp", + "src/mbgl/style/sprite.cpp", "src/mbgl/style/image.cpp", "src/mbgl/style/image_impl.cpp", "src/mbgl/style/image_impl.hpp", diff --git a/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json b/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json new file mode 100644 index 00000000000..28c44c7705c --- /dev/null +++ b/metrics/android-render-test-runner/render-tests/sprites/array-default-only/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 2, + 35923 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 1, + 4, + 9, + 1, + [ + 22080, + 22080 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json b/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json new file mode 100644 index 00000000000..45342fbc3d7 --- /dev/null +++ b/metrics/android-render-test-runner/render-tests/sprites/array-multiple/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 4, + 247582 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 2, + 6, + 13, + 1, + [ + 28480, + 28480 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/integration/render-tests/sprites/array-default-only/expected.png b/metrics/integration/render-tests/sprites/array-default-only/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9a49d0ef5d3a30d9d7dbd38b64778512d46b27 GIT binary patch literal 1545 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 H-lYEmVElB5 literal 0 HcmV?d00001 diff --git a/metrics/integration/render-tests/sprites/array-multiple/style.json b/metrics/integration/render-tests/sprites/array-multiple/style.json new file mode 100644 index 00000000000..79942a3daa1 --- /dev/null +++ b/metrics/integration/render-tests/sprites/array-multiple/style.json @@ -0,0 +1,79 @@ +{ + "version": 8, + "metadata": { + "test": { + "pixelRatio": 1, + "width": 128, + "height": 64 + } + }, + "center": [ + 0, + 0 + ], + "zoom": 0, + "sources": { + "source-one": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [25, 0] + } + } + ] + } + }, + "source-two": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [-25, 0] + } + } + ] + } + } + }, + "sprite": [ + { + "id": "default", + "url": "local://sprites/emerald" + }, + { + "id": "sprite", + "url": "local://sprites/sprite" + } + ], + "layers": [ + { + "id": "default-sprite-features", + "type": "symbol", + "source": "source-one", + "layout": { + "symbol-placement": "point", + "icon-image": "post_icon" + } + }, + { + "id": "sprite-sprite-features", + "type": "symbol", + "source": "source-two", + "layout": { + "symbol-placement": "point", + "icon-image": "sprite:night-lighthouse-12" + } + } + ] +} \ No newline at end of file diff --git a/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json b/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json new file mode 100644 index 00000000000..28c44c7705c --- /dev/null +++ b/metrics/linux-gcc8-release/render-tests/sprites/array-default-only/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 2, + 35923 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 1, + 4, + 9, + 1, + [ + 22080, + 22080 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json b/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json new file mode 100644 index 00000000000..45342fbc3d7 --- /dev/null +++ b/metrics/linux-gcc8-release/render-tests/sprites/array-multiple/metrics.json @@ -0,0 +1,35 @@ +{ + "network": [ + [ + "probeNetwork - default - end", + 4, + 247582 + ], + [ + "probeNetwork - default - start", + 0, + 0 + ] + ], + "gfx": [ + [ + "probeGFX - default - end", + 2, + 6, + 13, + 1, + [ + 28480, + 28480 + ], + [ + 46, + 46 + ], + [ + 384, + 384 + ] + ] + ] +} \ No newline at end of file diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index fa9a322392b..9f442041cd2 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -12,6 +12,7 @@ - Add support for the [`slice` expression](https://maplibre.org/maplibre-style-spec/expressions/#slice) ([#1113](https://github.com/maplibre/maplibre-native/pull/1133)) - Add support for the [`index-of` expression](https://maplibre.org/maplibre-style-spec/expressions/#index-of) ([#1113](https://github.com/maplibre/maplibre-native/pull/1113)) +- Add support for [multi sprites](https://github.com/maplibre/maplibre-native/pull/1858). More information on this feature can be found in the [Style Spec Documentation](https://maplibre.org/maplibre-style-spec/sprite/#multiple-sprite-sources). - Change to a more natural fling animation and allow setting `flingThreshold` and `flingAnimationBaseTime` in `UiSettings` ([#963](https://github.com/maplibre/maplibre-native/pull/963)) - 💥 Breaking: Change package of all classes from `com.mapbox.mapboxsdk` to `org.maplibre.android` ([#1201](https://github.com/maplibre/maplibre-native/pull/1201)). This means you will need to fix your imports. diff --git a/platform/default/src/mbgl/storage/offline_download.cpp b/platform/default/src/mbgl/storage/offline_download.cpp index d25f7019d8d..105535cef09 100644 --- a/platform/default/src/mbgl/storage/offline_download.cpp +++ b/platform/default/src/mbgl/storage/offline_download.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -223,8 +224,8 @@ OfflineRegionStatus OfflineDownload::getStatus() const { : NON_IDEOGRAPH_GLYPH_RANGES_PER_FONT_STACK); } - if (!parser.spriteURL.empty()) { - result->requiredResourceCount += 4; + if (!parser.sprites.empty()) { + result->requiredResourceCount += 4 * parser.sprites.size(); } return *result; @@ -246,7 +247,6 @@ void OfflineDownload::activateDownload() { parser.parse(*styleResponse.data); auto tileServerOptions = onlineFileSource.getResourceOptions().tileServerOptions(); - parser.spriteURL = util::mapbox::canonicalizeSpriteURL(tileServerOptions, parser.spriteURL); parser.glyphURL = util::mapbox::canonicalizeGlyphURL(tileServerOptions, parser.glyphURL); for (const auto& source : parser.sources) { @@ -343,12 +343,15 @@ void OfflineDownload::activateDownload() { } } - if (!parser.spriteURL.empty()) { + if (!parser.sprites.empty()) { // Always request 1x and @2x sprite images for portability. - queueResource(Resource::spriteImage(parser.spriteURL, 1)); - queueResource(Resource::spriteImage(parser.spriteURL, 2)); - queueResource(Resource::spriteJSON(parser.spriteURL, 1)); - queueResource(Resource::spriteJSON(parser.spriteURL, 2)); + for (const auto& sprite : parser.sprites) { + std::string spriteURL = util::mapbox::canonicalizeSpriteURL(tileServerOptions, sprite.spriteURL); + queueResource(Resource::spriteImage(spriteURL, 1)); + queueResource(Resource::spriteImage(spriteURL, 2)); + queueResource(Resource::spriteJSON(spriteURL, 1)); + queueResource(Resource::spriteJSON(spriteURL, 2)); + } } continueDownload(); diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 603710e0819..d966f0d6091 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -4,6 +4,10 @@ MapLibre welcomes participation and contributions from everyone. Please read [`C ## main +## 6.2.0 + +- Add support for [multi sprites](https://github.com/maplibre/maplibre-native/pull/1858). More information on this feature can be found in the [Style Spec Documentation](https://maplibre.org/maplibre-style-spec/sprite/#multiple-sprite-sources). + ## 6.1.1 - Tighten camera equality requirements ([#2139](https://github.com/maplibre/maplibre-native/pull/2139)). diff --git a/platform/ios/VERSION b/platform/ios/VERSION index 132c6def58c..f3b5af39e43 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.1.1 \ No newline at end of file +6.1.1 diff --git a/render-test/parser.cpp b/render-test/parser.cpp index 4bc96b9e035..bcc5527c420 100644 --- a/render-test/parser.cpp +++ b/render-test/parser.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/map/map_impl.cpp b/src/mbgl/map/map_impl.cpp index 789adc8e3b8..d9de1bb755e 100644 --- a/src/mbgl/map/map_impl.cpp +++ b/src/mbgl/map/map_impl.cpp @@ -55,7 +55,7 @@ void Map::Impl::onUpdate() { timePoint, transform.getState(), style->impl->getGlyphURL(), - style->impl->spriteLoaded, + style->impl->areSpritesLoaded(), style->impl->getTransitionOptions(), style->impl->getLight()->impl, style->impl->getImageImpls(), diff --git a/src/mbgl/sprite/sprite_loader.cpp b/src/mbgl/sprite/sprite_loader.cpp index 61f5fb47a07..ec2483f50b7 100644 --- a/src/mbgl/sprite/sprite_loader.cpp +++ b/src/mbgl/sprite/sprite_loader.cpp @@ -33,48 +33,56 @@ SpriteLoader::SpriteLoader(float pixelRatio_) SpriteLoader::~SpriteLoader() = default; -void SpriteLoader::load(const std::string& url, FileSource& fileSource) { - if (url.empty()) { +void SpriteLoader::load(const std::optional sprite, FileSource& fileSource) { + if (!sprite) { // Treat a non-existent sprite as a successfully loaded empty sprite. - observer->onSpriteLoaded({}); + observer->onSpriteLoaded(std::nullopt, {}); return; } - data = std::make_unique(); + std::string id = sprite->id; + std::string url = sprite->spriteURL; - data->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this](Response res) { + dataMap[id] = std::make_unique(); + dataMap[id]->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this, sprite](Response res) { + std::lock_guard lock(dataMapMutex); + Data* data = dataMap[sprite->id].get(); if (res.error) { - observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); + observer->onSpriteError(*sprite, std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified) { return; } else if (res.noContent) { data->json = std::make_shared(); - emitSpriteLoadedIfComplete(); + emitSpriteLoadedIfComplete(*sprite); } else { // Only trigger a sprite loaded event we got new data. assert(data->json != res.data); data->json = std::move(res.data); - emitSpriteLoadedIfComplete(); + emitSpriteLoadedIfComplete(*sprite); } }); - data->spriteRequest = fileSource.request(Resource::spriteImage(url, pixelRatio), [this](Response res) { - if (res.error) { - observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); - } else if (res.notModified) { - return; - } else if (res.noContent) { - data->image = std::make_shared(); - emitSpriteLoadedIfComplete(); - } else { - assert(data->image != res.data); - data->image = std::move(res.data); - emitSpriteLoadedIfComplete(); - } - }); + dataMap[id]->spriteRequest = fileSource.request( + Resource::spriteImage(url, pixelRatio), [this, sprite](Response res) { + std::lock_guard lock(dataMapMutex); + Data* data = dataMap[sprite->id].get(); + if (res.error) { + observer->onSpriteError(*sprite, std::make_exception_ptr(std::runtime_error(res.error->message))); + } else if (res.notModified) { + return; + } else if (res.noContent) { + data->image = std::make_shared(); + emitSpriteLoadedIfComplete(*sprite); + } else { + assert(dataMap[sprite->id]->image != res.data); + data->image = std::move(res.data); + emitSpriteLoadedIfComplete(*sprite); + } + }); } -void SpriteLoader::emitSpriteLoadedIfComplete() { +void SpriteLoader::emitSpriteLoadedIfComplete(style::Sprite sprite) { + Data* data = dataMap[sprite.id].get(); assert(data); if (!data->image || !data->json) { return; @@ -85,22 +93,22 @@ void SpriteLoader::emitSpriteLoadedIfComplete() { std::exception_ptr error; }; - auto parseClosure = [image = data->image, json = data->json]() -> ParseResult { + auto parseClosure = [sprite = sprite, image = data->image, json = data->json]() -> ParseResult { try { - return {parseSprite(*image, *json), nullptr}; + return {parseSprite(sprite.id, *image, *json), nullptr}; } catch (...) { return {{}, std::current_exception()}; } }; - auto resultClosure = [this, weak = weakFactory.makeWeakPtr()](ParseResult result) { + auto resultClosure = [this, sprite = sprite, weak = weakFactory.makeWeakPtr()](ParseResult result) { if (!weak) return; // This instance has been deleted. if (result.error) { - observer->onSpriteError(result.error); + observer->onSpriteError(std::optional(sprite), result.error); return; } - observer->onSpriteLoaded(std::move(result.images)); + observer->onSpriteLoaded(std::optional(sprite), std::move(result.images)); }; threadPool->scheduleAndReplyValue(parseClosure, resultClosure); diff --git a/src/mbgl/sprite/sprite_loader.hpp b/src/mbgl/sprite/sprite_loader.hpp index 373331e3b3e..2333d697f37 100644 --- a/src/mbgl/sprite/sprite_loader.hpp +++ b/src/mbgl/sprite/sprite_loader.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -21,12 +22,12 @@ class SpriteLoader { SpriteLoader(float pixelRatio); ~SpriteLoader(); - void load(const std::string& url, FileSource&); + void load(const std::optional sprite, FileSource&); void setObserver(SpriteLoaderObserver*); private: - void emitSpriteLoadedIfComplete(); + void emitSpriteLoadedIfComplete(style::Sprite sprite); // Invoked by SpriteAtlasWorker friend class SpriteLoaderWorker; @@ -34,7 +35,8 @@ class SpriteLoader { const float pixelRatio; struct Data; - std::unique_ptr data; + std::map> dataMap; + std::mutex dataMapMutex; SpriteLoaderObserver* observer = nullptr; std::shared_ptr threadPool; diff --git a/src/mbgl/sprite/sprite_loader_observer.hpp b/src/mbgl/sprite/sprite_loader_observer.hpp index f72f67cbbba..ad7257172d4 100644 --- a/src/mbgl/sprite/sprite_loader_observer.hpp +++ b/src/mbgl/sprite/sprite_loader_observer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -17,9 +18,9 @@ class SpriteLoaderObserver { virtual ~SpriteLoaderObserver() = default; // NOLINTNEXTLINE(performance-unnecessary-value-param) - virtual void onSpriteLoaded(std::vector>) {} + virtual void onSpriteLoaded(std::optional, std::vector>) {} - virtual void onSpriteError(std::exception_ptr) {} + virtual void onSpriteError(std::optional, std::exception_ptr) {} }; } // namespace mbgl diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index 10629b4d53d..8c7768b5c8b 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -152,7 +152,9 @@ std::optional getContent(const JSValue& value, const char* } // namespace -std::vector> parseSprite(const std::string& encodedImage, const std::string& json) { +std::vector> parseSprite(const std::string& id, + const std::string& encodedImage, + const std::string& json) { const PremultipliedImage raster = decodeImage(encodedImage); JSDocument doc; @@ -170,6 +172,11 @@ std::vector> parseSprite(const std::string& encode images.reserve(properties.MemberCount()); for (const auto& property : properties) { const std::string name = {property.name.GetString(), property.name.GetStringLength()}; + std::string completeName = name; + if (id != "default") { + completeName = id + ":"; + completeName += name; + } const JSValue& value = property.value; if (value.IsObject()) { @@ -183,8 +190,17 @@ std::vector> parseSprite(const std::string& encode style::ImageStretches stretchY = getStretches(value, "stretchY", name.c_str()); std::optional content = getContent(value, "content", name.c_str()); - auto image = createStyleImage( - name, raster, x, y, width, height, pixelRatio, sdf, std::move(stretchX), std::move(stretchY), content); + auto image = createStyleImage(completeName, + raster, + x, + y, + width, + height, + pixelRatio, + sdf, + std::move(stretchX), + std::move(stretchY), + content); if (image) { images.push_back(std::move(image->baseImpl)); } diff --git a/src/mbgl/sprite/sprite_parser.hpp b/src/mbgl/sprite/sprite_parser.hpp index 54a0746cbcb..d08d19110cb 100644 --- a/src/mbgl/sprite/sprite_parser.hpp +++ b/src/mbgl/sprite/sprite_parser.hpp @@ -20,6 +20,8 @@ std::unique_ptr createStyleImage(const std::string& id, const std::optional& content = std::nullopt); // Parses an image and an associated JSON file and returns the sprite objects. -std::vector> parseSprite(const std::string& image, const std::string& json); +std::vector> parseSprite(const std::string& id, + const std::string& image, + const std::string& json); } // namespace mbgl diff --git a/src/mbgl/style/conversion/sprite.cpp b/src/mbgl/style/conversion/sprite.cpp new file mode 100644 index 00000000000..60ae104b3a6 --- /dev/null +++ b/src/mbgl/style/conversion/sprite.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +#include + +namespace mbgl { +namespace style { +namespace conversion { + +std::optional Converter::operator()(const Convertible& value, Error& error) const { + std::optional id; + auto idValue = objectMember(value, "id"); + if (!idValue) { + error.message = "id must be defined for sprite object"; + return std::nullopt; + } + id = toString(*idValue); + + std::optional spriteURL; + auto urlValue = objectMember(value, "url"); + if (!urlValue) { + error.message = "url must be defined for sprite object"; + return std::nullopt; + } + spriteURL = toString(*urlValue); + + const Sprite sprite(*id, *spriteURL); + return sprite; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/sprite.hpp b/src/mbgl/style/conversion/sprite.hpp new file mode 100644 index 00000000000..3acea30e1dd --- /dev/null +++ b/src/mbgl/style/conversion/sprite.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { +public: + std::optional operator()(const Convertible& value, Error& error) const; +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index 461cedc7b91..c57de6ba1cc 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -17,7 +18,9 @@ #include #include +#include #include +#include namespace mbgl { namespace style { @@ -103,10 +106,7 @@ StyleParseResult Parser::parse(const std::string& json) { } if (document.HasMember("sprite")) { - const JSValue& sprite = document["sprite"]; - if (sprite.IsString()) { - spriteURL = {sprite.GetString(), sprite.GetStringLength()}; - } + parseSprites(document["sprite"]); } if (document.HasMember("glyphs")) { @@ -165,6 +165,40 @@ void Parser::parseSources(const JSValue& value) { } } +void Parser::parseSprites(const JSValue& value) { + if (value.IsString()) { + std::string url = {value.GetString(), value.GetStringLength()}; + auto sprite = Sprite("default", url); + sprites.emplace_back(sprite); + } else if (value.IsArray()) { + std::unordered_set spriteIds; + for (auto& spriteValue : value.GetArray()) { + if (!spriteValue.IsObject()) { + Log::Warning(Event::ParseStyle, "sprite child must be an object"); + continue; + } + + conversion::Error error; + std::optional sprite = conversion::convert(spriteValue, error); + if (!sprite) { + Log::Warning(Event::ParseStyle, error.message); + continue; + } + + if (spriteIds.find(sprite->id) != spriteIds.end()) { + Log::Warning(Event::ParseStyle, "sprite ids must be unique"); + continue; + } + + spriteIds.insert(sprite->id); + sprites.emplace_back(*sprite); + } + } else { + Log::Warning(Event::ParseStyle, "sprite must be an object or string"); + return; + } +} + void Parser::parseLayers(const JSValue& value) { std::vector ids; diff --git a/src/mbgl/style/parser.hpp b/src/mbgl/style/parser.hpp index ab9d2df7387..6d5e8794898 100644 --- a/src/mbgl/style/parser.hpp +++ b/src/mbgl/style/parser.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include #include - #include #include #include @@ -27,7 +27,7 @@ class Parser { StyleParseResult parse(const std::string&); - std::string spriteURL; + std::vector sprites; std::string glyphURL; std::vector> sources; @@ -49,6 +49,7 @@ class Parser { void parseTransition(const JSValue&); void parseLight(const JSValue&); void parseSources(const JSValue&); + void parseSprites(const JSValue&); void parseLayers(const JSValue&); void parseLayer(const std::string& id, const JSValue&, std::unique_ptr&); diff --git a/src/mbgl/style/sprite.cpp b/src/mbgl/style/sprite.cpp new file mode 100644 index 00000000000..bc0e42ba304 --- /dev/null +++ b/src/mbgl/style/sprite.cpp @@ -0,0 +1,14 @@ +#include + +namespace mbgl { +namespace style { + +Sprite::~Sprite() = default; + +Sprite::Sprite(std::string id_, std::string spriteURL_) { + this->id = id_; + this->spriteURL = spriteURL_; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sprite.hpp b/src/mbgl/style/sprite.hpp new file mode 100644 index 00000000000..c08f1e23ef3 --- /dev/null +++ b/src/mbgl/style/sprite.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace mbgl { +namespace style { + +class Sprite { +public: + Sprite(std::string, std::string); + ~Sprite(); + + std::string id; + std::string spriteURL; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 1b9b8e798f7..4b2119ba0c3 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -118,11 +118,22 @@ void Style::Impl::parse(const std::string& json_) { setLight(std::make_unique(parser.light)); - spriteLoaded = false; if (fileSource) { - spriteLoader->load(parser.spriteURL, *fileSource); + if (parser.sprites.empty()) { + // We identify no sprite with 'default' as string in the sprite loading status. + spritesLoadingStatus["default"] = false; + spriteLoader->load(std::nullopt, *fileSource); + } else { + for (const auto& sprite : parser.sprites) { + spritesLoadingStatus[sprite.id] = false; + spriteLoader->load(std::optional(sprite), *fileSource); + } + } } else { - onSpriteError(std::make_exception_ptr(std::runtime_error("Unable to find resource provider for sprite url."))); + // We identify no sprite with 'default' as string in the sprite loading status. + spritesLoadingStatus["default"] = false; + onSpriteError(std::nullopt, + std::make_exception_ptr(std::runtime_error("Unable to find resource provider for sprite url."))); } glyphURL = parser.glyphURL; @@ -255,12 +266,24 @@ Source* Style::Impl::getSource(const std::string& id) const { return sources.get(id); } +bool Style::Impl::areSpritesLoaded() const { + if (spritesLoadingStatus.empty()) { + return false; // If nothing is stored inside, sprites are not yet loaded. + } + for (const auto& entry : spritesLoadingStatus) { + if (!entry.second) { + return false; + } + } + return true; +} + bool Style::Impl::isLoaded() const { if (!loaded) { return false; } - if (!spriteLoaded) { + if (!areSpritesLoaded()) { return false; } @@ -338,7 +361,8 @@ void Style::Impl::onSourceDescriptionChanged(Source& source) { } } -void Style::Impl::onSpriteLoaded(std::vector> images_) { +void Style::Impl::onSpriteLoaded(std::optional sprite, + std::vector> images_) { auto newImages = makeMutable(*images); assert(std::is_sorted(newImages->begin(), newImages->end())); @@ -358,16 +382,24 @@ void Style::Impl::onSpriteLoaded(std::vector> imag newImages->end(), std::make_move_iterator(images_.begin()), std::make_move_iterator(images_.end())); std::sort(newImages->begin(), newImages->end()); images = std::move(newImages); - spriteLoaded = true; + if (sprite) { + spritesLoadingStatus[sprite->id] = true; + } else { + spritesLoadingStatus["default"] = true; + } observer->onUpdate(); // For *-pattern properties. } -void Style::Impl::onSpriteError(std::exception_ptr error) { +void Style::Impl::onSpriteError(std::optional sprite, std::exception_ptr error) { lastError = error; Log::Error(Event::Style, "Failed to load sprite: " + util::toString(error)); observer->onResourceError(error); + if (sprite) { + spritesLoadingStatus[sprite->id] = true; + } else { + spritesLoadingStatus["default"] = false; + } // Unblock rendering tiles (even though sprite request has failed). - spriteLoaded = true; observer->onUpdate(); } diff --git a/src/mbgl/style/style_impl.hpp b/src/mbgl/style/style_impl.hpp index ed943e24054..63d6b54c1cd 100644 --- a/src/mbgl/style/style_impl.hpp +++ b/src/mbgl/style/style_impl.hpp @@ -86,10 +86,10 @@ class Style::Impl : public SpriteLoaderObserver, Immutable>> getLayerImpls() const; void dumpDebugLogs() const; + bool areSpritesLoaded() const; bool mutated = false; bool loaded = false; - bool spriteLoaded = false; private: void parse(const std::string&); @@ -108,14 +108,15 @@ class Style::Impl : public SpriteLoaderObserver, Collection layers; TransitionOptions transitionOptions; std::unique_ptr light; + std::unordered_map spritesLoadingStatus; // Defaults std::string name; CameraOptions defaultCamera; // SpriteLoaderObserver implementation. - void onSpriteLoaded(std::vector>) override; - void onSpriteError(std::exception_ptr) override; + void onSpriteLoaded(std::optional sprite, std::vector>) override; + void onSpriteError(std::optional sprite, std::exception_ptr) override; // SourceObserver implementation. void onSourceLoaded(Source&) override; diff --git a/test/fixtures/style_parser/sprites-missing-fields.info.json b/test/fixtures/style_parser/sprites-missing-fields.info.json new file mode 100644 index 00000000000..96fb44b3251 --- /dev/null +++ b/test/fixtures/style_parser/sprites-missing-fields.info.json @@ -0,0 +1,8 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "id must be defined for sprite object"], + [1, "WARNING", "ParseStyle", "url must be defined for sprite object"] + ] + } +} diff --git a/test/fixtures/style_parser/sprites-missing-fields.style.json b/test/fixtures/style_parser/sprites-missing-fields.style.json new file mode 100644 index 00000000000..0c813b8cb35 --- /dev/null +++ b/test/fixtures/style_parser/sprites-missing-fields.style.json @@ -0,0 +1,4 @@ +{ + "version": 8, + "sprite": [{"id": "default"},{"url": "https://example.com"}] +} diff --git a/test/fixtures/style_parser/sprites-not-same-ids.info.json b/test/fixtures/style_parser/sprites-not-same-ids.info.json new file mode 100644 index 00000000000..7f07d5196a2 --- /dev/null +++ b/test/fixtures/style_parser/sprites-not-same-ids.info.json @@ -0,0 +1,7 @@ +{ + "default": { + "log": [ + [1, "WARNING", "ParseStyle", "sprite ids must be unique"] + ] + } +} diff --git a/test/fixtures/style_parser/sprites-not-same-ids.style.json b/test/fixtures/style_parser/sprites-not-same-ids.style.json new file mode 100644 index 00000000000..75a54eba1d1 --- /dev/null +++ b/test/fixtures/style_parser/sprites-not-same-ids.style.json @@ -0,0 +1,11 @@ +{ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com" + }, + { + "id": "default", + "url": "https://example2.com" + }] +} diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp index fb359bd7773..7eafee86a86 100644 --- a/test/renderer/image_manager.test.cpp +++ b/test/renderer/image_manager.test.cpp @@ -24,7 +24,8 @@ TEST(ImageManager, Basic) { FixtureLog log; ImageManager imageManager; - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + auto images = parseSprite("default", + util::read_file("test/fixtures/annotations/emerald.png"), util::read_file("test/fixtures/annotations/emerald.json")); for (auto& image : images) { imageManager.addImage(image); diff --git a/test/renderer/pattern_atlas.test.cpp b/test/renderer/pattern_atlas.test.cpp index 0c170175368..399b4ecea7d 100644 --- a/test/renderer/pattern_atlas.test.cpp +++ b/test/renderer/pattern_atlas.test.cpp @@ -18,7 +18,8 @@ TEST(PatternAtlas, Basic) { FixtureLog log; PatternAtlas patternAtlas; - auto images = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + auto images = parseSprite("default", + util::read_file("test/fixtures/annotations/emerald.png"), util::read_file("test/fixtures/annotations/emerald.json")); for (auto& image : images) { if (image->id == "metro") { diff --git a/test/sprite/sprite_loader.test.cpp b/test/sprite/sprite_loader.test.cpp index 8b5ad25d9cf..75eaa6c98ec 100644 --- a/test/sprite/sprite_loader.test.cpp +++ b/test/sprite/sprite_loader.test.cpp @@ -17,11 +17,11 @@ using namespace mbgl::style; class StubSpriteLoaderObserver : public SpriteLoaderObserver { public: - void onSpriteLoaded(std::vector> images) override { + void onSpriteLoaded(std::optional, std::vector> images) override { if (spriteLoaded) spriteLoaded(std::move(images)); } - void onSpriteError(std::exception_ptr error) override { + void onSpriteError(std::optional, std::exception_ptr error) override { if (spriteError) spriteError(error); } @@ -43,7 +43,9 @@ class SpriteLoaderTest { Log::setObserver(std::make_unique()); spriteLoader.setObserver(&observer); - spriteLoader.load("test/fixtures/resources/sprite", fileSource); + + Sprite sprite = Sprite("default", "test/fixtures/resources/sprite"); + spriteLoader.load(sprite, fileSource); loop.run(); } diff --git a/test/sprite/sprite_parser.test.cpp b/test/sprite/sprite_parser.test.cpp index 3599c564cb7..57c1026d4c9 100644 --- a/test/sprite/sprite_parser.test.cpp +++ b/test/sprite/sprite_parser.test.cpp @@ -256,7 +256,7 @@ TEST(Sprite, SpriteParsing) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = util::read_file("test/fixtures/annotations/emerald.json"); - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); std::set names; std::transform( @@ -352,7 +352,7 @@ TEST(Sprite, SpriteParsingInvalidJSON) { const auto json_1x = R"JSON({ "image": " })JSON"; try { - parseSprite(image_1x, json_1x); + parseSprite("default", image_1x, json_1x); FAIL() << "Expected exception"; } catch (std::runtime_error& err) { EXPECT_STREQ( @@ -367,7 +367,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -385,7 +385,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { "an array"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -403,7 +403,7 @@ TEST(Sprite, SpriteParsingInvalidStretches) { "be an array of two numbers"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -427,7 +427,7 @@ TEST(Sprite, SpriteParsingInvalidContent) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -445,7 +445,7 @@ TEST(Sprite, SpriteParsingInvalidContent) { "an array of four numbers"})); EXPECT_EQ(0u, log.uncheckedCount()); - parseSprite(image_1x, R"JSON({ + parseSprite("default", image_1x, R"JSON({ "interstate_1": { "width": 40, "height": 42, @@ -469,7 +469,7 @@ TEST(Sprite, SpriteParsingStretchAndContent) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); - const auto images = parseSprite(image_1x, R"JSON({ + const auto images = parseSprite("default", image_1x, R"JSON({ "image": { "width": 16, "height": 16, @@ -494,7 +494,7 @@ TEST(Sprite, SpriteParsingEmptyImage) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": {} })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -513,7 +513,7 @@ TEST(Sprite, SpriteParsingSimpleWidthHeight) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(1u, images.size()); } @@ -523,7 +523,7 @@ TEST(Sprite, SpriteParsingWidthTooBig) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 65536, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -550,7 +550,7 @@ TEST(Sprite, SpriteParsingNegativeWidth) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": -1, "height": 32 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, @@ -577,7 +577,7 @@ TEST(Sprite, SpriteParsingNullRatio) { const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32, "pixelRatio": 0 } })JSON"; - const auto images = parseSprite(image_1x, json_1x); + const auto images = parseSprite("default", image_1x, json_1x); EXPECT_EQ(0u, images.size()); EXPECT_EQ(1u, diff --git a/test/style/style_parser.test.cpp b/test/style/style_parser.test.cpp index 43b04148f56..8b44a70ebbb 100644 --- a/test/style/style_parser.test.cpp +++ b/test/style/style_parser.test.cpp @@ -126,6 +126,63 @@ INSTANTIATE_TEST_SUITE_P(StyleParser, StyleParserTest, ::testing::ValuesIn([] { return names; }())); +TEST(StyleParser, SpriteAsString) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": "https://example.com/default/markers" + })"); + auto result = &parser.sprites; + ASSERT_EQ(1, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); +} + +TEST(StyleParser, SpriteAsArrayEmpty) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [] + })"); + auto result = &parser.sprites; + ASSERT_EQ(0, result->size()); +} + +TEST(StyleParser, SpriteAsArraySingle) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com/default/markers" + }] + })"); + auto result = &parser.sprites; + ASSERT_EQ(1, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); +} + +TEST(StyleParser, SpriteAsArrayMultiple) { + style::Parser parser; + parser.parse(R"({ + "version": 8, + "sprite": [{ + "id": "default", + "url": "https://example.com/default/markers" + },{ + "id": "hiking", + "url": "https://example.com/hiking/markers" + }] + })"); + auto result = &parser.sprites; + ASSERT_EQ(2, result->size()); + ASSERT_EQ("https://example.com/default/markers", result->at(0).spriteURL); + ASSERT_EQ("default", result->at(0).id); + ASSERT_EQ("https://example.com/hiking/markers", result->at(1).spriteURL); + ASSERT_EQ("hiking", result->at(1).id); +} + TEST(StyleParser, FontStacks) { style::Parser parser; parser.parse(util::read_file("test/fixtures/style_parser/font_stacks.json"));