Implementation of multi-sprites in mbgl-core (#1858)

Co-authored-by: Christophe Brasseur <christophe.brasseur@geolives.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
This commit is contained in:
Geolives
2024-02-27 18:42:17 +01:00
committed by GitHub
parent 9e8837b0fb
commit f4c6fabbe6
39 changed files with 649 additions and 83 deletions

View File

@@ -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)")"

View File

@@ -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

View File

@@ -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",

View File

@@ -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
]
]
]
}

View File

@@ -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
]
]
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,56 @@
{
"version": 8,
"metadata": {
"test": {
"pixelRatio": 1,
"width": 128,
"height": 64
}
},
"center": [
0,
0
],
"zoom": 0,
"sources": {
"geojson": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [25, 0]
}
},
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [-25, 0]
}
}
]
}
}
},
"sprite": [{
"id": "default",
"url": "local://sprites/emerald"
}],
"layers": [
{
"id": "default",
"type": "symbol",
"source": "geojson",
"layout": {
"symbol-placement": "point",
"icon-image": "post_icon"
}
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -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"
}
}
]
}

View File

@@ -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
]
]
]
}

View File

@@ -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
]
]
]
}

View File

@@ -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.

View File

@@ -11,6 +11,7 @@
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/tileset.hpp>
#include <mbgl/style/sprite.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/util/mapbox.hpp>
@@ -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();

View File

@@ -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)).

View File

@@ -18,6 +18,7 @@
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/light.hpp>
#include <mbgl/style/conversion/source.hpp>
#include <mbgl/style/conversion/sprite.hpp>
#include <mbgl/style/layer.hpp>
#include <mbgl/style/light.hpp>
#include <mbgl/style/source.hpp>

View File

@@ -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(),

View File

@@ -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<style::Sprite> 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<Data>();
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<Data>();
dataMap[id]->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this, sprite](Response res) {
std::lock_guard<std::mutex> 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<std::string>();
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) {
dataMap[id]->spriteRequest = fileSource.request(
Resource::spriteImage(url, pixelRatio), [this, sprite](Response res) {
std::lock_guard<std::mutex> 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->image = std::make_shared<std::string>();
emitSpriteLoadedIfComplete();
emitSpriteLoadedIfComplete(*sprite);
} else {
assert(data->image != res.data);
assert(dataMap[sprite->id]->image != res.data);
data->image = std::move(res.data);
emitSpriteLoadedIfComplete();
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);

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/style/image.hpp>
#include <mbgl/style/sprite.hpp>
#include <mapbox/std/weak.hpp>
#include <string>
@@ -21,12 +22,12 @@ public:
SpriteLoader(float pixelRatio);
~SpriteLoader();
void load(const std::string& url, FileSource&);
void load(const std::optional<style::Sprite> sprite, FileSource&);
void setObserver(SpriteLoaderObserver*);
private:
void emitSpriteLoadedIfComplete();
void emitSpriteLoadedIfComplete(style::Sprite sprite);
// Invoked by SpriteAtlasWorker
friend class SpriteLoaderWorker;
@@ -34,7 +35,8 @@ private:
const float pixelRatio;
struct Data;
std::unique_ptr<Data> data;
std::map<std::string, std::unique_ptr<Data>> dataMap;
std::mutex dataMapMutex;
SpriteLoaderObserver* observer = nullptr;
std::shared_ptr<Scheduler> threadPool;

View File

@@ -2,6 +2,7 @@
#include <mbgl/style/image.hpp>
#include <mbgl/util/immutable.hpp>
#include <mbgl/style/sprite.hpp>
#include <exception>
#include <vector>
@@ -17,9 +18,9 @@ public:
virtual ~SpriteLoaderObserver() = default;
// NOLINTNEXTLINE(performance-unnecessary-value-param)
virtual void onSpriteLoaded(std::vector<Immutable<style::Image::Impl>>) {}
virtual void onSpriteLoaded(std::optional<style::Sprite>, std::vector<Immutable<style::Image::Impl>>) {}
virtual void onSpriteError(std::exception_ptr) {}
virtual void onSpriteError(std::optional<style::Sprite>, std::exception_ptr) {}
};
} // namespace mbgl

View File

@@ -152,7 +152,9 @@ std::optional<style::ImageContent> getContent(const JSValue& value, const char*
} // namespace
std::vector<Immutable<style::Image::Impl>> parseSprite(const std::string& encodedImage, const std::string& json) {
std::vector<Immutable<style::Image::Impl>> 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<Immutable<style::Image::Impl>> 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<Immutable<style::Image::Impl>> parseSprite(const std::string& encode
style::ImageStretches stretchY = getStretches(value, "stretchY", name.c_str());
std::optional<style::ImageContent> 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));
}

View File

@@ -20,6 +20,8 @@ std::unique_ptr<style::Image> createStyleImage(const std::string& id,
const std::optional<style::ImageContent>& content = std::nullopt);
// Parses an image and an associated JSON file and returns the sprite objects.
std::vector<Immutable<style::Image::Impl>> parseSprite(const std::string& image, const std::string& json);
std::vector<Immutable<style::Image::Impl>> parseSprite(const std::string& id,
const std::string& image,
const std::string& json);
} // namespace mbgl

View File

@@ -0,0 +1,34 @@
#include <mbgl/style/conversion/sprite.hpp>
#include <mbgl/style/conversion/constant.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <array>
namespace mbgl {
namespace style {
namespace conversion {
std::optional<Sprite> Converter<Sprite>::operator()(const Convertible& value, Error& error) const {
std::optional<std::string> 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<std::string> 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

View File

@@ -0,0 +1,21 @@
#pragma once
#include <mbgl/style/sprite.hpp>
#include <mbgl/style/conversion.hpp>
#include <optional>
#include <memory>
namespace mbgl {
namespace style {
namespace conversion {
template <>
struct Converter<Sprite> {
public:
std::optional<Sprite> operator()(const Convertible& value, Error& error) const;
};
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -5,6 +5,7 @@
#include <mbgl/style/conversion/source.hpp>
#include <mbgl/style/conversion/layer.hpp>
#include <mbgl/style/conversion/light.hpp>
#include <mbgl/style/conversion/sprite.hpp>
#include <mbgl/style/conversion/transition_options.hpp>
#include <mbgl/style/conversion_impl.hpp>
@@ -17,7 +18,9 @@
#include <rapidjson/error/en.h>
#include <algorithm>
#include <memory>
#include <set>
#include <unordered_set>
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<std::string> 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> sprite = conversion::convert<Sprite>(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<std::string> ids;

View File

@@ -1,9 +1,9 @@
#pragma once
#include <mbgl/style/layer.hpp>
#include <mbgl/style/sprite.hpp>
#include <mbgl/style/source.hpp>
#include <mbgl/style/light.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/rapidjson.hpp>
#include <mbgl/util/font_stack.hpp>
@@ -27,7 +27,7 @@ public:
StyleParseResult parse(const std::string&);
std::string spriteURL;
std::vector<Sprite> sprites;
std::string glyphURL;
std::vector<std::unique_ptr<Source>> sources;
@@ -49,6 +49,7 @@ private:
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<Layer>&);

14
src/mbgl/style/sprite.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include <mbgl/style/sprite.hpp>
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

18
src/mbgl/style/sprite.hpp Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <string>
namespace mbgl {
namespace style {
class Sprite {
public:
Sprite(std::string, std::string);
~Sprite();
std::string id;
std::string spriteURL;
};
} // namespace style
} // namespace mbgl

View File

@@ -118,11 +118,22 @@ void Style::Impl::parse(const std::string& json_) {
setLight(std::make_unique<Light>(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 {
onSpriteError(std::make_exception_ptr(std::runtime_error("Unable to find resource provider for sprite url.")));
for (const auto& sprite : parser.sprites) {
spritesLoadingStatus[sprite.id] = false;
spriteLoader->load(std::optional(sprite), *fileSource);
}
}
} else {
// 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<Immutable<style::Image::Impl>> images_) {
void Style::Impl::onSpriteLoaded(std::optional<style::Sprite> sprite,
std::vector<Immutable<style::Image::Impl>> images_) {
auto newImages = makeMutable<ImageImpls>(*images);
assert(std::is_sorted(newImages->begin(), newImages->end()));
@@ -358,16 +382,24 @@ void Style::Impl::onSpriteLoaded(std::vector<Immutable<style::Image::Impl>> 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<style::Sprite> 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();
}

View File

@@ -86,10 +86,10 @@ public:
Immutable<std::vector<Immutable<Layer::Impl>>> 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 @@ private:
Collection<Layer> layers;
TransitionOptions transitionOptions;
std::unique_ptr<Light> light;
std::unordered_map<std::string, bool> spritesLoadingStatus;
// Defaults
std::string name;
CameraOptions defaultCamera;
// SpriteLoaderObserver implementation.
void onSpriteLoaded(std::vector<Immutable<style::Image::Impl>>) override;
void onSpriteError(std::exception_ptr) override;
void onSpriteLoaded(std::optional<style::Sprite> sprite, std::vector<Immutable<style::Image::Impl>>) override;
void onSpriteError(std::optional<style::Sprite> sprite, std::exception_ptr) override;
// SourceObserver implementation.
void onSourceLoaded(Source&) override;

View File

@@ -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"]
]
}
}

View File

@@ -0,0 +1,4 @@
{
"version": 8,
"sprite": [{"id": "default"},{"url": "https://example.com"}]
}

View File

@@ -0,0 +1,7 @@
{
"default": {
"log": [
[1, "WARNING", "ParseStyle", "sprite ids must be unique"]
]
}
}

View File

@@ -0,0 +1,11 @@
{
"version": 8,
"sprite": [{
"id": "default",
"url": "https://example.com"
},
{
"id": "default",
"url": "https://example2.com"
}]
}

View File

@@ -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);

View File

@@ -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") {

View File

@@ -17,11 +17,11 @@ using namespace mbgl::style;
class StubSpriteLoaderObserver : public SpriteLoaderObserver {
public:
void onSpriteLoaded(std::vector<Immutable<style::Image::Impl>> images) override {
void onSpriteLoaded(std::optional<Sprite>, std::vector<Immutable<style::Image::Impl>> images) override {
if (spriteLoaded) spriteLoaded(std::move(images));
}
void onSpriteError(std::exception_ptr error) override {
void onSpriteError(std::optional<Sprite>, std::exception_ptr error) override {
if (spriteError) spriteError(error);
}
@@ -43,7 +43,9 @@ public:
Log::setObserver(std::make_unique<Log::NullObserver>());
spriteLoader.setObserver(&observer);
spriteLoader.load("test/fixtures/resources/sprite", fileSource);
Sprite sprite = Sprite("default", "test/fixtures/resources/sprite");
spriteLoader.load(sprite, fileSource);
loop.run();
}

View File

@@ -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<std::string> 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,

View File

@@ -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"));