summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFabian Guerra <fabian.guerra@mapbox.com>2018-04-23 10:44:02 -0400
committerFabian Guerra <fabian.guerra@mapbox.com>2018-04-23 10:44:02 -0400
commite08b6fe87f5824ab05a4cc67d9a76af5bb5ddd3b (patch)
tree886e10260bfa044f62943186ec837b9ccd02934c /src
parent2bb785dad2489d04db179fa9cf65514640db0a96 (diff)
parenta45670cfb5752866b9c8130024a313944684c2db (diff)
downloadqtlocation-mapboxgl-e08b6fe87f5824ab05a4cc67d9a76af5bb5ddd3b.tar.gz
Merge branch 'release-boba' into masterupstream/fabian-merge-v4.0.0
# Conflicts: # circle.yml # include/mbgl/style/expression/let.hpp # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapGestureDetector.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapKeyListener.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java # platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Transform.java # platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java # platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml # platform/android/gradle/dependencies.gradle # platform/android/src/example_custom_layer.cpp # platform/android/src/geojson/point.cpp # platform/darwin/src/NSPredicate+MGLAdditions.mm # platform/darwin/test/MGLExpressionTests.mm # platform/ios/Mapbox-iOS-SDK-nightly-dynamic.podspec # platform/ios/Mapbox-iOS-SDK-symbols.podspec # platform/ios/Mapbox-iOS-SDK.podspec # platform/ios/app/MBXViewController.m # src/mbgl/renderer/layers/render_custom_layer.cpp # src/mbgl/style/conversion/filter.cpp # src/mbgl/style/expression/interpolate.cpp # src/mbgl/style/expression/value.cpp # test/style/filter.test.cpp
Diffstat (limited to 'src')
-rw-r--r--src/mbgl/geometry/feature_index.cpp17
-rw-r--r--src/mbgl/geometry/feature_index.hpp7
-rw-r--r--src/mbgl/layout/symbol_instance.cpp4
-rw-r--r--src/mbgl/layout/symbol_layout.cpp4
-rw-r--r--src/mbgl/layout/symbol_projection.cpp6
-rw-r--r--src/mbgl/map/map.cpp1
-rw-r--r--src/mbgl/programs/symbol_program.hpp4
-rw-r--r--src/mbgl/renderer/layers/render_custom_layer.cpp33
-rw-r--r--src/mbgl/renderer/layers/render_custom_layer.hpp3
-rw-r--r--src/mbgl/shaders/collision_box.cpp5
-rw-r--r--src/mbgl/shaders/line_pattern.cpp10
-rw-r--r--src/mbgl/shaders/symbol_icon.cpp7
-rw-r--r--src/mbgl/shaders/symbol_sdf.cpp7
-rw-r--r--src/mbgl/style/conversion/filter.cpp7
-rw-r--r--src/mbgl/style/conversion/stringify.hpp131
-rw-r--r--src/mbgl/style/expression/at.cpp14
-rw-r--r--src/mbgl/style/expression/compound_expression.cpp20
-rw-r--r--src/mbgl/style/expression/interpolate.cpp6
-rw-r--r--src/mbgl/style/expression/length.cpp66
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp72
-rw-r--r--src/mbgl/style/expression/value.cpp9
-rw-r--r--src/mbgl/style/layers/background_layer.cpp2
-rw-r--r--src/mbgl/style/layers/circle_layer.cpp2
-rw-r--r--src/mbgl/style/layers/custom_layer.cpp16
-rw-r--r--src/mbgl/style/layers/custom_layer_impl.cpp12
-rw-r--r--src/mbgl/style/layers/custom_layer_impl.hpp14
-rw-r--r--src/mbgl/style/layers/fill_extrusion_layer.cpp2
-rw-r--r--src/mbgl/style/layers/fill_layer.cpp2
-rw-r--r--src/mbgl/style/layers/heatmap_layer.cpp2
-rw-r--r--src/mbgl/style/layers/hillshade_layer.cpp2
-rw-r--r--src/mbgl/style/layers/layer.cpp.ejs2
-rw-r--r--src/mbgl/style/layers/line_layer.cpp2
-rw-r--r--src/mbgl/style/layers/raster_layer.cpp2
-rw-r--r--src/mbgl/style/layers/symbol_layer.cpp2
-rw-r--r--src/mbgl/tile/geometry_tile.cpp43
-rw-r--r--src/mbgl/tile/geometry_tile.hpp39
-rw-r--r--src/mbgl/tile/geometry_tile_worker.cpp175
-rw-r--r--src/mbgl/tile/geometry_tile_worker.hpp14
-rw-r--r--src/mbgl/util/color.cpp22
39 files changed, 411 insertions, 377 deletions
diff --git a/src/mbgl/geometry/feature_index.cpp b/src/mbgl/geometry/feature_index.cpp
index 6fb0d5e446..c67786274a 100644
--- a/src/mbgl/geometry/feature_index.cpp
+++ b/src/mbgl/geometry/feature_index.cpp
@@ -17,8 +17,9 @@
namespace mbgl {
-FeatureIndex::FeatureIndex()
- : grid(util::EXTENT, util::EXTENT, util::EXTENT / 16) { // 16x16 grid -> 32px cell
+FeatureIndex::FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_)
+ : grid(util::EXTENT, util::EXTENT, util::EXTENT / 16) // 16x16 grid -> 32px cell
+ , tileData(std::move(tileData_)) {
}
void FeatureIndex::insert(const GeometryCollection& geometries,
@@ -47,12 +48,15 @@ void FeatureIndex::query(
const double tileSize,
const double scale,
const RenderedQueryOptions& queryOptions,
- const GeometryTileData& geometryTileData,
const UnwrappedTileID& tileID,
const std::string& sourceID,
const std::vector<const RenderLayer*>& layers,
const CollisionIndex& collisionIndex,
const float additionalQueryRadius) const {
+
+ if (!tileData) {
+ return;
+ }
// Determine query radius
const float pixelsToTileUnits = util::EXTENT / tileSize / scale;
@@ -72,13 +76,13 @@ void FeatureIndex::query(
if (indexedFeature.sortIndex == previousSortIndex) continue;
previousSortIndex = indexedFeature.sortIndex;
- addFeature(result, indexedFeature, queryGeometry, queryOptions, geometryTileData, tileID.canonical, layers, bearing, pixelsToTileUnits);
+ addFeature(result, indexedFeature, queryGeometry, queryOptions, tileID.canonical, layers, bearing, pixelsToTileUnits);
}
std::vector<IndexedSubfeature> symbolFeatures = collisionIndex.queryRenderedSymbols(queryGeometry, tileID, sourceID);
std::sort(symbolFeatures.begin(), symbolFeatures.end(), topDownSymbols);
for (const auto& symbolFeature : symbolFeatures) {
- addFeature(result, symbolFeature, queryGeometry, queryOptions, geometryTileData, tileID.canonical, layers, bearing, pixelsToTileUnits);
+ addFeature(result, symbolFeature, queryGeometry, queryOptions, tileID.canonical, layers, bearing, pixelsToTileUnits);
}
}
@@ -87,7 +91,6 @@ void FeatureIndex::addFeature(
const IndexedSubfeature& indexedFeature,
const GeometryCoordinates& queryGeometry,
const RenderedQueryOptions& options,
- const GeometryTileData& geometryTileData,
const CanonicalTileID& tileID,
const std::vector<const RenderLayer*>& layers,
const float bearing,
@@ -113,7 +116,7 @@ void FeatureIndex::addFeature(
}
if (!geometryTileFeature) {
- sourceLayer = geometryTileData.getLayer(indexedFeature.sourceLayerName);
+ sourceLayer = tileData->getLayer(indexedFeature.sourceLayerName);
assert(sourceLayer);
geometryTileFeature = sourceLayer->getFeature(indexedFeature.index);
diff --git a/src/mbgl/geometry/feature_index.hpp b/src/mbgl/geometry/feature_index.hpp
index e95bb94da6..9e0c172342 100644
--- a/src/mbgl/geometry/feature_index.hpp
+++ b/src/mbgl/geometry/feature_index.hpp
@@ -50,8 +50,10 @@ public:
class FeatureIndex {
public:
- FeatureIndex();
+ FeatureIndex(std::unique_ptr<const GeometryTileData> tileData_);
+ const GeometryTileData* getData() { return tileData.get(); }
+
void insert(const GeometryCollection&, std::size_t index, const std::string& sourceLayerName, const std::string& bucketName);
void query(
@@ -61,7 +63,6 @@ public:
const double tileSize,
const double scale,
const RenderedQueryOptions& options,
- const GeometryTileData&,
const UnwrappedTileID&,
const std::string&,
const std::vector<const RenderLayer*>&,
@@ -83,7 +84,6 @@ private:
const IndexedSubfeature&,
const GeometryCoordinates& queryGeometry,
const RenderedQueryOptions& options,
- const GeometryTileData&,
const CanonicalTileID&,
const std::vector<const RenderLayer*>&,
const float bearing,
@@ -93,5 +93,6 @@ private:
unsigned int sortIndex = 0;
std::unordered_map<std::string, std::vector<std::string>> bucketLayerIDs;
+ std::unique_ptr<const GeometryTileData> tileData;
};
} // namespace mbgl
diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp
index 6e152349ca..7dfa8edf43 100644
--- a/src/mbgl/layout/symbol_instance.cpp
+++ b/src/mbgl/layout/symbol_instance.cpp
@@ -27,7 +27,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
anchor(anchor_),
line(line_),
index(index_),
- hasText(shapedTextOrientations.first || shapedTextOrientations.second),
+ hasText(false),
hasIcon(shapedIcon),
// Create the collision features that will be used to check whether this symbol instance can be placed
@@ -48,6 +48,8 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
if (shapedTextOrientations.second) {
verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions);
}
+ // 'hasText' depends on finding at least one glyph in the shaping that's also in the GlyphPositionMap
+ hasText = horizontalGlyphQuads.size() > 0 || verticalGlyphQuads.size() > 0;
if (shapedTextOrientations.first && shapedTextOrientations.second) {
writingModes = WritingModeType::Horizontal | WritingModeType::Vertical;
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index 3bf85407c6..82a9255824 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -126,7 +126,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
if (hasText) {
std::string u8string = layout.evaluate<TextField>(zoom, ft);
- if (layout.get<TextField>().isConstant()) {
+ if (layout.get<TextField>().isConstant() && !leader.layout.get<TextField>().isExpression()) {
u8string = util::replaceTokens(u8string, getValue);
}
@@ -159,7 +159,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
if (hasIcon) {
std::string icon = layout.evaluate<IconImage>(zoom, ft);
- if (layout.get<IconImage>().isConstant()) {
+ if (layout.get<IconImage>().isConstant() && !leader.layout.get<IconImage>().isExpression()) {
icon = util::replaceTokens(icon, getValue);
}
ft.icon = icon;
diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp
index 9e077e2532..ef669c6e19 100644
--- a/src/mbgl/layout/symbol_projection.cpp
+++ b/src/mbgl/layout/symbol_projection.cpp
@@ -240,7 +240,11 @@ namespace mbgl {
const PlacedSymbol& symbol,
const mat4& labelPlaneMatrix,
const bool returnTileDistance) {
-
+ if (symbol.glyphOffsets.empty()) {
+ assert(false);
+ return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
+ }
+
const float firstGlyphOffset = symbol.glyphOffsets.front();
const float lastGlyphOffset = symbol.glyphOffsets.back();;
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 525291a628..d81544eed5 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -446,7 +446,6 @@ CameraOptions Map::cameraForGeometry(const Geometry<double>& geometry, const Edg
latLngs.push_back({ pt.y, pt.x });
});
return cameraForLatLngs(latLngs, padding, bearing);
-
}
LatLngBounds Map::latLngBoundsForCamera(const CameraOptions& camera) const {
diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp
index a14afac702..9b5037ed9f 100644
--- a/src/mbgl/programs/symbol_program.hpp
+++ b/src/mbgl/programs/symbol_program.hpp
@@ -61,8 +61,8 @@ struct SymbolLayoutAttributes : gl::Attributes<
{{
static_cast<int16_t>(labelAnchor.x),
static_cast<int16_t>(labelAnchor.y),
- static_cast<int16_t>(::round(o.x * 64)), // use 1/64 pixels for placement
- static_cast<int16_t>(::round((o.y + glyphOffsetY) * 64))
+ static_cast<int16_t>(::round(o.x * 32)), // use 1/32 pixels for placement
+ static_cast<int16_t>(::round((o.y + glyphOffsetY) * 32))
}},
{{
tx,
diff --git a/src/mbgl/renderer/layers/render_custom_layer.cpp b/src/mbgl/renderer/layers/render_custom_layer.cpp
index a429b8d82e..be9f64d9eb 100644
--- a/src/mbgl/renderer/layers/render_custom_layer.cpp
+++ b/src/mbgl/renderer/layers/render_custom_layer.cpp
@@ -6,23 +6,24 @@
#include <mbgl/style/layers/custom_layer_impl.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/gl/gl.hpp>
+#include <mbgl/util/mat4.hpp>
namespace mbgl {
using namespace style;
RenderCustomLayer::RenderCustomLayer(Immutable<style::CustomLayer::Impl> _impl)
- : RenderLayer(LayerType::Custom, _impl) {
+ : RenderLayer(LayerType::Custom, _impl), host(_impl->host) {
+ assert(BackendScope::exists());
+ host->initialize();
}
RenderCustomLayer::~RenderCustomLayer() {
assert(BackendScope::exists());
- if (initialized) {
- if (contextDestroyed && impl().contextLostFn ) {
- impl().contextLostFn(impl().context);
- } else if (!contextDestroyed && impl().deinitializeFn) {
- impl().deinitializeFn(impl().context);
- }
+ if (contextDestroyed) {
+ host->contextLost();
+ } else {
+ host->deinitialize();
}
}
@@ -44,15 +45,13 @@ std::unique_ptr<Bucket> RenderCustomLayer::createBucket(const BucketParameters&,
}
void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*) {
- if (context != impl().context || !initialized) {
+ if (host != impl().host) {
//If the context changed, deinitialize the previous one before initializing the new one.
- if (context && !contextDestroyed && impl().deinitializeFn) {
- MBGL_CHECK_ERROR(impl().deinitializeFn(context));
+ if (host && !contextDestroyed) {
+ MBGL_CHECK_ERROR(host->deinitialize());
}
- context = impl().context;
- assert(impl().initializeFn);
- MBGL_CHECK_ERROR(impl().initializeFn(impl().context));
- initialized = true;
+ host = impl().host;
+ MBGL_CHECK_ERROR(host->initialize());
}
gl::Context& glContext = paintParameters.context;
@@ -74,9 +73,11 @@ void RenderCustomLayer::render(PaintParameters& paintParameters, RenderSource*)
parameters.bearing = -state.getAngle() * util::RAD2DEG;
parameters.pitch = state.getPitch();
parameters.fieldOfView = state.getFieldOfView();
+ mat4 projMatrix;
+ state.getProjMatrix(projMatrix);
+ parameters.projectionMatrix = projMatrix;
- assert(impl().renderFn);
- MBGL_CHECK_ERROR(impl().renderFn(context, parameters));
+ MBGL_CHECK_ERROR(host->render(parameters));
// Reset the view back to our original one, just in case the CustomLayer changed
// the viewport or Framebuffer.
diff --git a/src/mbgl/renderer/layers/render_custom_layer.hpp b/src/mbgl/renderer/layers/render_custom_layer.hpp
index 6d1fea99d3..971d8d8f42 100644
--- a/src/mbgl/renderer/layers/render_custom_layer.hpp
+++ b/src/mbgl/renderer/layers/render_custom_layer.hpp
@@ -24,9 +24,8 @@ public:
};
private:
- bool initialized = false;
bool contextDestroyed = false;
- void * context = nullptr;
+ std::shared_ptr<style::CustomLayerHost> host;
};
template <>
diff --git a/src/mbgl/shaders/collision_box.cpp b/src/mbgl/shaders/collision_box.cpp
index 9d11640bf4..bc5d9bc6f9 100644
--- a/src/mbgl/shaders/collision_box.cpp
+++ b/src/mbgl/shaders/collision_box.cpp
@@ -22,7 +22,10 @@ varying float v_notUsed;
void main() {
vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1);
highp float camera_to_anchor_distance = projectedPoint.w;
- highp float collision_perspective_ratio = 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance);
+ highp float collision_perspective_ratio = clamp(
+ 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance),
+ 0.0, // Prevents oversized near-field boxes in pitched/overzoomed tiles
+ 4.0);
gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0);
gl_Position.xy += a_extrude * u_extrude_scale * gl_Position.w * collision_perspective_ratio;
diff --git a/src/mbgl/shaders/line_pattern.cpp b/src/mbgl/shaders/line_pattern.cpp
index f8d785ade9..be88255e3c 100644
--- a/src/mbgl/shaders/line_pattern.cpp
+++ b/src/mbgl/shaders/line_pattern.cpp
@@ -215,8 +215,14 @@ void main() {
float x_a = mod(v_linesofar / u_pattern_size_a.x, 1.0);
float x_b = mod(v_linesofar / u_pattern_size_b.x, 1.0);
- float y_a = 0.5 + (v_normal.y * v_width2.s / u_pattern_size_a.y);
- float y_b = 0.5 + (v_normal.y * v_width2.s / u_pattern_size_b.y);
+
+ // v_normal.y is 0 at the midpoint of the line, -1 at the lower edge, 1 at the upper edge
+ // we clamp the line width outset to be between 0 and half the pattern height plus padding (2.0)
+ // to ensure we don't sample outside the designated symbol on the sprite sheet.
+ // 0.5 is added to shift the component to be bounded between 0 and 1 for interpolation of
+ // the texture coordinate
+ float y_a = 0.5 + (v_normal.y * clamp(v_width2.s, 0.0, (u_pattern_size_a.y + 2.0) / 2.0) / u_pattern_size_a.y);
+ float y_b = 0.5 + (v_normal.y * clamp(v_width2.s, 0.0, (u_pattern_size_b.y + 2.0) / 2.0) / u_pattern_size_b.y);
vec2 pos_a = mix(u_pattern_tl_a / u_texsize, u_pattern_br_a / u_texsize, vec2(x_a, y_a));
vec2 pos_b = mix(u_pattern_tl_b / u_texsize, u_pattern_br_b / u_texsize, vec2(x_b, y_b));
diff --git a/src/mbgl/shaders/symbol_icon.cpp b/src/mbgl/shaders/symbol_icon.cpp
index f5c2bbe22d..c037c81005 100644
--- a/src/mbgl/shaders/symbol_icon.cpp
+++ b/src/mbgl/shaders/symbol_icon.cpp
@@ -80,7 +80,10 @@ void main() {
highp float distance_ratio = u_pitch_with_map ?
camera_to_anchor_distance / u_camera_to_center_distance :
u_camera_to_center_distance / camera_to_anchor_distance;
- highp float perspective_ratio = 0.5 + 0.5 * distance_ratio;
+ highp float perspective_ratio = clamp(
+ 0.5 + 0.5 * distance_ratio,
+ 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles
+ 4.0);
size *= perspective_ratio;
@@ -102,7 +105,7 @@ void main() {
mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos);
vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0);
- gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0);
+ gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0);
v_tex = a_tex / u_texsize;
vec2 fade_opacity = unpack_opacity(a_fade_opacity);
diff --git a/src/mbgl/shaders/symbol_sdf.cpp b/src/mbgl/shaders/symbol_sdf.cpp
index 441eaf7aac..b584c00315 100644
--- a/src/mbgl/shaders/symbol_sdf.cpp
+++ b/src/mbgl/shaders/symbol_sdf.cpp
@@ -156,7 +156,10 @@ void main() {
highp float distance_ratio = u_pitch_with_map ?
camera_to_anchor_distance / u_camera_to_center_distance :
u_camera_to_center_distance / camera_to_anchor_distance;
- highp float perspective_ratio = 0.5 + 0.5 * distance_ratio;
+ highp float perspective_ratio = clamp(
+ 0.5 + 0.5 * distance_ratio,
+ 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles
+ 4.0);
size *= perspective_ratio;
@@ -180,7 +183,7 @@ void main() {
mat2 rotation_matrix = mat2(angle_cos, -1.0 * angle_sin, angle_sin, angle_cos);
vec4 projected_pos = u_label_plane_matrix * vec4(a_projected_pos.xy, 0.0, 1.0);
- gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0);
+ gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 32.0 * fontScale), 0.0, 1.0);
float gamma_scale = gl_Position.w;
vec2 tex = a_tex / u_texsize;
diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp
index fba149da12..3c941945fd 100644
--- a/src/mbgl/style/conversion/filter.cpp
+++ b/src/mbgl/style/conversion/filter.cpp
@@ -2,7 +2,7 @@
#include <mbgl/util/geometry.hpp>
#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/type.hpp>
-#include <mbgl/style/conversion/expression.hpp>
+#include <mbgl/style/expression/parsing_context.hpp>
namespace mbgl {
namespace style {
@@ -236,10 +236,13 @@ optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) {
}
optional<Filter> convertExpressionFilter(const Convertible& value, Error& error) {
- optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, expression::type::Boolean);
+ expression::ParsingContext ctx(expression::type::Boolean);
+ expression::ParseResult expression = ctx.parseExpression(value);
if (!expression) {
+ error = { ctx.getCombinedErrors() };
return {};
}
+
return { ExpressionFilter { std::move(*expression) } };
}
diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp
index 7924a442c4..7b7727d7c4 100644
--- a/src/mbgl/style/conversion/stringify.hpp
+++ b/src/mbgl/style/conversion/stringify.hpp
@@ -290,138 +290,19 @@ void stringify(Writer& writer, const Undefined&) {
writer.Null();
}
-template <class Writer>
-void stringify(Writer& writer, const CategoricalValue& v) {
- CategoricalValue::visit(v, [&] (const auto& v_) { stringify(writer, v_); });
-}
-
-template <class Writer>
-class StringifyStops {
-public:
- Writer& writer;
-
- template <class T>
- void operator()(const ExponentialStops<T>& f) {
- writer.Key("type");
- writer.String("exponential");
- writer.Key("base");
- writer.Double(f.base);
- writer.Key("stops");
- stringifyStops(f.stops);
- }
-
- template <class T>
- void operator()(const IntervalStops<T>& f) {
- writer.Key("type");
- writer.String("interval");
- writer.Key("stops");
- stringifyStops(f.stops);
- }
-
- template <class T>
- void operator()(const CategoricalStops<T>& f) {
- writer.Key("type");
- writer.String("categorical");
- writer.Key("stops");
- stringifyStops(f.stops);
- }
-
- template <class T>
- void operator()(const IdentityStops<T>&) {
- writer.Key("type");
- writer.String("identity");
- }
-
- template <class T>
- void operator()(const CompositeExponentialStops<T>& f) {
- writer.Key("type");
- writer.String("exponential");
- writer.Key("base");
- writer.Double(f.base);
- writer.Key("stops");
- stringifyCompositeStops(f.stops);
- }
-
- template <class T>
- void operator()(const CompositeIntervalStops<T>& f) {
- writer.Key("type");
- writer.String("interval");
- writer.Key("stops");
- stringifyCompositeStops(f.stops);
- }
-
- template <class T>
- void operator()(const CompositeCategoricalStops<T>& f) {
- writer.Key("type");
- writer.String("categorical");
- writer.Key("stops");
- stringifyCompositeStops(f.stops);
- }
-
-private:
- template <class K, class V>
- void stringifyStops(const std::map<K, V>& stops) {
- writer.StartArray();
- for (const auto& stop : stops) {
- writer.StartArray();
- stringify(writer, stop.first);
- stringify(writer, stop.second);
- writer.EndArray();
- }
- writer.EndArray();
- }
-
- template <class InnerStops>
- void stringifyCompositeStops(const std::map<float, InnerStops>& stops) {
- writer.StartArray();
- for (const auto& outer : stops) {
- for (const auto& inner : outer.second) {
- writer.StartArray();
- writer.StartObject();
- writer.Key("zoom");
- writer.Double(outer.first);
- writer.Key("value");
- stringify(writer, inner.first);
- writer.EndObject();
- stringify(writer, inner.second);
- writer.EndArray();
- }
- }
- writer.EndArray();
- }
-};
-
template <class Writer, class T>
-void stringify(Writer& writer, const CameraFunction<T>& f) {
- writer.StartObject();
- CameraFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer });
- writer.EndObject();
+void stringify(Writer& writer, const CameraFunction<T>& fn) {
+ stringify(writer, fn.getExpression().serialize());
}
template <class Writer, class T>
-void stringify(Writer& writer, const SourceFunction<T>& f) {
- writer.StartObject();
- writer.Key("property");
- writer.String(f.property);
- SourceFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer });
- if (f.defaultValue) {
- writer.Key("default");
- stringify(writer, *f.defaultValue);
- }
- writer.EndObject();
+void stringify(Writer& writer, const SourceFunction<T>& fn) {
+ stringify(writer, fn.getExpression().serialize());
}
template <class Writer, class T>
-void stringify(Writer& writer, const CompositeFunction<T>& f) {
- writer.StartObject();
- writer.Key("property");
- writer.String(f.property);
- CompositeFunction<T>::Stops::visit(f.stops, StringifyStops<Writer> { writer });
- if (f.defaultValue) {
- writer.Key("default");
- stringify(writer, *f.defaultValue);
- }
- writer.EndObject();
+void stringify(Writer& writer, const CompositeFunction<T>& fn) {
+ stringify(writer, fn.getExpression().serialize());
}
template <class Writer, class T>
diff --git a/src/mbgl/style/expression/at.cpp b/src/mbgl/style/expression/at.cpp
index e447d33bc7..725e5ddb51 100644
--- a/src/mbgl/style/expression/at.cpp
+++ b/src/mbgl/style/expression/at.cpp
@@ -19,15 +19,21 @@ EvaluationResult At::evaluate(const EvaluationContext& params) const {
const auto i = evaluatedIndex->get<double>();
const auto inputArray = evaluatedInput->get<std::vector<Value>>();
- if (i < 0 || i >= inputArray.size()) {
+ if (i < 0) {
return EvaluationError {
- "Array index out of bounds: " + stringify(i) +
- " > " + util::toString(inputArray.size()) + "."
+ "Array index out of bounds: " + util::toString(i) + " < 0."
+ };
+ }
+
+ if (i >= inputArray.size()) {
+ return EvaluationError {
+ "Array index out of bounds: " + util::toString(i) +
+ " > " + util::toString(inputArray.size() - 1) + "."
};
}
if (i != std::floor(i)) {
return EvaluationError {
- "Array index must be an integer, but found " + stringify(i) + " instead."
+ "Array index must be an integer, but found " + util::toString(i) + " instead."
};
}
return inputArray[static_cast<std::size_t>(i)];
diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp
index 86d968c521..c36ffa33e3 100644
--- a/src/mbgl/style/expression/compound_expression.cpp
+++ b/src/mbgl/style/expression/compound_expression.cpp
@@ -6,6 +6,7 @@
#include <mbgl/util/ignore.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/platform.hpp>
+#include <cmath>
namespace mbgl {
namespace style {
@@ -209,12 +210,7 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
);
});
define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> {
- return std::array<double, 4> {{
- 255 * color.r / color.a,
- 255 * color.g / color.a,
- 255 * color.b / color.a,
- color.a
- }};
+ return color.toArray();
});
define("rgba", rgba);
@@ -271,13 +267,6 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
return object.at(key);
});
- define("length", [](const std::vector<Value>& arr) -> Result<double> {
- return arr.size();
- });
- define("length", [] (const std::string s) -> Result<double> {
- return s.size();
- });
-
define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> {
if (!params.feature) {
return EvaluationError {
@@ -374,6 +363,11 @@ std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initiali
return result;
});
+ define("round", [](double x) -> Result<double> { return std::round(x); });
+ define("floor", [](double x) -> Result<double> { return std::floor(x); });
+ define("ceil", [](double x) -> Result<double> { return std::ceil(x); });
+ define("abs", [](double x) -> Result<double> { return std::abs(x); });
+
define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; });
define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; });
define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; });
diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp
index 30b2cba81b..daad8523f2 100644
--- a/src/mbgl/style/expression/interpolate.cpp
+++ b/src/mbgl/style/expression/interpolate.cpp
@@ -223,7 +223,11 @@ mbgl::Value Interpolate<T>::serialize() const {
interpolator.match(
[&](const ExponentialInterpolator& exponential) {
- serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("exponential"), exponential.base }});
+ if (exponential.base == 1) {
+ serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("linear") }});
+ } else {
+ serialized.emplace_back(std::vector<mbgl::Value>{{ std::string("exponential"), exponential.base }});
+ }
},
[&](const CubicBezierInterpolator& cubicBezier) {
static const std::string cubicBezierTag("cubic-bezier");
diff --git a/src/mbgl/style/expression/length.cpp b/src/mbgl/style/expression/length.cpp
new file mode 100644
index 0000000000..258353ae4e
--- /dev/null
+++ b/src/mbgl/style/expression/length.cpp
@@ -0,0 +1,66 @@
+#include <mbgl/style/expression/length.hpp>
+#include <mbgl/util/string.hpp>
+
+namespace mbgl {
+namespace style {
+namespace expression {
+
+Length::Length(std::unique_ptr<Expression> input_)
+ : Expression(type::Number),
+ input(std::move(input_)) {
+}
+
+EvaluationResult Length::evaluate(const EvaluationContext& params) const {
+ const EvaluationResult value = input->evaluate(params);
+ if (!value) return value;
+ return value->match(
+ [] (const std::string& s) {
+ return EvaluationResult { double(s.size()) };
+ },
+ [] (const std::vector<Value>& v) {
+ return EvaluationResult { double(v.size()) };
+ },
+ [&] (const auto&) -> EvaluationResult {
+ return EvaluationError { "Expected value to be of type string or array, but found " + toString(typeOf(*value)) + " instead." };
+ });
+}
+
+void Length::eachChild(const std::function<void(const Expression&)>& visit) const {
+ visit(*input);
+}
+
+bool Length::operator==(const Expression& e) const {
+ if (auto eq = dynamic_cast<const Length*>(&e)) {
+ return *eq->input == *input;
+ }
+ return false;
+}
+
+std::vector<optional<Value>> Length::possibleOutputs() const {
+ return { nullopt };
+}
+
+using namespace mbgl::style::conversion;
+ParseResult Length::parse(const Convertible& value, ParsingContext& ctx) {
+ std::size_t length = arrayLength(value);
+
+ if (length != 2) {
+ ctx.error("Expected one argument, but found " + util::toString(length) + " instead.");
+ return ParseResult();
+ }
+
+ ParseResult input = ctx.parse(arrayMember(value, 1), 1);
+ if (!input) return ParseResult();
+
+ type::Type type = (*input)->getType();
+ if (!type.is<type::Array>() && !type.is<type::StringType>() && !type.is<type::ValueType>()) {
+ ctx.error("Expected argument of type string or array, but found " + toString(type) + " instead.");
+ return ParseResult();
+ }
+
+ return ParseResult(std::make_unique<Length>(std::move(*input)));
+}
+
+} // namespace expression
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
index 0215982209..b522aeff9a 100644
--- a/src/mbgl/style/expression/parsing_context.cpp
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -15,6 +15,7 @@
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/equals.hpp>
#include <mbgl/style/expression/interpolate.hpp>
+#include <mbgl/style/expression/length.hpp>
#include <mbgl/style/expression/let.hpp>
#include <mbgl/style/expression/literal.hpp>
#include <mbgl/style/expression/match.hpp>
@@ -31,23 +32,36 @@ namespace style {
namespace expression {
bool isConstant(const Expression& expression) {
- if (dynamic_cast<const Var*>(&expression)) {
- return false;
+ if (auto varExpression = dynamic_cast<const Var*>(&expression)) {
+ return isConstant(*varExpression->getBoundExpression());
}
-
+
if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) {
if (compound->getName() == "error") {
return false;
}
}
+
+ bool isTypeAnnotation = dynamic_cast<const Coercion*>(&expression) ||
+ dynamic_cast<const Assertion*>(&expression) ||
+ dynamic_cast<const ArrayAssertion*>(&expression);
- bool literalArgs = true;
+ bool childrenConstant = true;
expression.eachChild([&](const Expression& child) {
- if (!dynamic_cast<const Literal*>(&child)) {
- literalArgs = false;
+ // We can _almost_ assume that if `expressions` children are constant,
+ // they would already have been evaluated to Literal values when they
+ // were parsed. Type annotations are the exception, because they might
+ // have been inferred and added after a child was parsed.
+
+ // So we recurse into isConstant() for the children of type annotations,
+ // but otherwise simply check whether they are Literals.
+ if (isTypeAnnotation) {
+ childrenConstant = childrenConstant && isConstant(child);
+ } else {
+ childrenConstant = childrenConstant && dynamic_cast<const Literal*>(&child);
}
});
- if (!literalArgs) {
+ if (!childrenConstant) {
return false;
}
@@ -89,6 +103,7 @@ const ExpressionRegistry& getExpressionRegistry() {
{"case", Case::parse},
{"coalesce", Coalesce::parse},
{"interpolate", parseInterpolate},
+ {"length", Length::parse},
{"let", Let::parse},
{"literal", Literal::parse},
{"match", parseMatch},
@@ -139,15 +154,15 @@ ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption
return parsed;
}
- if (expected) {
- auto array = [&](std::unique_ptr<Expression> expression) {
- std::vector<std::unique_ptr<Expression>> args;
- args.push_back(std::move(expression));
- return args;
- };
+ auto array = [&](std::unique_ptr<Expression> expression) {
+ std::vector<std::unique_ptr<Expression>> args;
+ args.push_back(std::move(expression));
+ return args;
+ };
+ if (expected) {
const type::Type actual = (*parsed)->getType();
- if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean) && actual == type::Value) {
+ if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean || *expected == type::Object) && actual == type::Value) {
if (typeAnnotationOption == includeTypeAnnotations) {
parsed = { std::make_unique<Assertion>(*expected, array(std::move(*parsed))) };
}
@@ -167,7 +182,7 @@ ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption
}
}
- // If an expression's arguments are all literals, we can evaluate
+ // If an expression's arguments are all constant, we can evaluate
// it immediately and replace it with a literal value in the
// parsed result.
if (!dynamic_cast<Literal *>(parsed->get()) && isConstant(**parsed)) {
@@ -191,8 +206,16 @@ ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption
}
}
- // if this is the root expression, enforce constraints on the use ["zoom"].
- if (key.size() == 0 && !isZoomConstant(**parsed)) {
+ return parsed;
+}
+
+ParseResult ParsingContext::parseExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) {
+ return parse(value, typeAnnotationOption);
+}
+
+ParseResult ParsingContext::parseLayerPropertyExpression(const Convertible& value, TypeAnnotationOption typeAnnotationOption) {
+ ParseResult parsed = parse(value, typeAnnotationOption);
+ if (parsed && !isZoomConstant(**parsed)) {
optional<variant<const InterpolateBase*, const Step*, ParsingError>> zoomCurve = findZoomCurve(parsed->get());
if (!zoomCurve) {
error(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)");
@@ -202,10 +225,23 @@ ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption
return ParseResult();
}
}
-
return parsed;
}
+const std::string ParsingContext::getCombinedErrors() const {
+ std::string combinedError;
+ for (const ParsingError& parsingError : *errors) {
+ if (combinedError.size() > 0) {
+ combinedError += "\n";
+ }
+ if (parsingError.key.size() > 0) {
+ combinedError += parsingError.key + ": ";
+ }
+ combinedError += parsingError.message;
+ }
+ return combinedError;
+}
+
optional<std::string> ParsingContext::checkType(const type::Type& t) {
assert(expected);
optional<std::string> err = type::checkSubtype(*expected, t);
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
index 72779d4956..1b3257c755 100644
--- a/src/mbgl/style/expression/value.cpp
+++ b/src/mbgl/style/expression/value.cpp
@@ -106,12 +106,13 @@ Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) {
mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value) {
return value.match(
[&](const Color& color)->mbgl::Value {
+ std::array<double, 4> array = color.toArray();
return std::vector<mbgl::Value>{
std::string("rgba"),
- double(255 * color.r / color.a),
- double(255 * color.g / color.a),
- double(255 * color.b / color.a),
- double(color.a)
+ array[0],
+ array[1],
+ array[2],
+ array[3],
};
},
[&](const std::vector<Value>& values)->mbgl::Value {
diff --git a/src/mbgl/style/layers/background_layer.cpp b/src/mbgl/style/layers/background_layer.cpp
index d4ead18816..66ab46c078 100644
--- a/src/mbgl/style/layers/background_layer.cpp
+++ b/src/mbgl/style/layers/background_layer.cpp
@@ -53,12 +53,14 @@ void BackgroundLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void BackgroundLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/circle_layer.cpp b/src/mbgl/style/layers/circle_layer.cpp
index 9854932699..6dd744df1f 100644
--- a/src/mbgl/style/layers/circle_layer.cpp
+++ b/src/mbgl/style/layers/circle_layer.cpp
@@ -81,12 +81,14 @@ void CircleLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void CircleLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/custom_layer.cpp b/src/mbgl/style/layers/custom_layer.cpp
index 854c771847..0e51a70e50 100644
--- a/src/mbgl/style/layers/custom_layer.cpp
+++ b/src/mbgl/style/layers/custom_layer.cpp
@@ -6,20 +6,8 @@ namespace mbgl {
namespace style {
CustomLayer::CustomLayer(const std::string& layerID,
- CustomLayerInitializeFunction init,
- CustomLayerRenderFunction render,
- CustomLayerContextLostFunction contextLost,
- CustomLayerDeinitializeFunction deinit,
- void* context)
- : Layer(makeMutable<Impl>(layerID, init, render, contextLost, deinit, context)) {
-}
-
-CustomLayer::CustomLayer(const std::string& layerID,
- CustomLayerInitializeFunction init,
- CustomLayerRenderFunction render,
- CustomLayerDeinitializeFunction deinit,
- void* context)
- : Layer(makeMutable<Impl>(layerID, init, render, nullptr, deinit, context)) {
+ std::unique_ptr<CustomLayerHost> host)
+ : Layer(makeMutable<Impl>(layerID, std::move(host))) {
}
CustomLayer::~CustomLayer() = default;
diff --git a/src/mbgl/style/layers/custom_layer_impl.cpp b/src/mbgl/style/layers/custom_layer_impl.cpp
index 1de268d2e2..05c41623c4 100644
--- a/src/mbgl/style/layers/custom_layer_impl.cpp
+++ b/src/mbgl/style/layers/custom_layer_impl.cpp
@@ -4,17 +4,9 @@ namespace mbgl {
namespace style {
CustomLayer::Impl::Impl(const std::string& id_,
- CustomLayerInitializeFunction initializeFn_,
- CustomLayerRenderFunction renderFn_,
- CustomLayerContextLostFunction contextLostFn_,
- CustomLayerDeinitializeFunction deinitializeFn_,
- void* context_)
+ std::unique_ptr<CustomLayerHost> host_)
: Layer::Impl(LayerType::Custom, id_, std::string()) {
- initializeFn = initializeFn_;
- renderFn = renderFn_;
- deinitializeFn = deinitializeFn_;
- contextLostFn = contextLostFn_;
- context = context_;
+ host = std::move(host_);
}
bool CustomLayer::Impl::hasLayoutDifference(const Layer::Impl&) const {
diff --git a/src/mbgl/style/layers/custom_layer_impl.hpp b/src/mbgl/style/layers/custom_layer_impl.hpp
index 62efbbe15b..a41962c276 100644
--- a/src/mbgl/style/layers/custom_layer_impl.hpp
+++ b/src/mbgl/style/layers/custom_layer_impl.hpp
@@ -3,6 +3,8 @@
#include <mbgl/style/layer_impl.hpp>
#include <mbgl/style/layers/custom_layer.hpp>
+#include <memory>
+
namespace mbgl {
class TransformState;
@@ -12,20 +14,12 @@ namespace style {
class CustomLayer::Impl : public Layer::Impl {
public:
Impl(const std::string& id,
- CustomLayerInitializeFunction,
- CustomLayerRenderFunction,
- CustomLayerContextLostFunction,
- CustomLayerDeinitializeFunction,
- void* context);
+ std::unique_ptr<CustomLayerHost> host);
bool hasLayoutDifference(const Layer::Impl&) const override;
void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
- CustomLayerInitializeFunction initializeFn = nullptr;
- CustomLayerRenderFunction renderFn = nullptr;
- CustomLayerContextLostFunction contextLostFn = nullptr;
- CustomLayerDeinitializeFunction deinitializeFn = nullptr;
- void* context = nullptr;
+ std::shared_ptr<CustomLayerHost> host;
};
} // namespace style
diff --git a/src/mbgl/style/layers/fill_extrusion_layer.cpp b/src/mbgl/style/layers/fill_extrusion_layer.cpp
index 62f92cef75..c5b4ef0ef3 100644
--- a/src/mbgl/style/layers/fill_extrusion_layer.cpp
+++ b/src/mbgl/style/layers/fill_extrusion_layer.cpp
@@ -81,12 +81,14 @@ void FillExtrusionLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void FillExtrusionLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/fill_layer.cpp b/src/mbgl/style/layers/fill_layer.cpp
index 65975752db..99a2a51ed0 100644
--- a/src/mbgl/style/layers/fill_layer.cpp
+++ b/src/mbgl/style/layers/fill_layer.cpp
@@ -81,12 +81,14 @@ void FillLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void FillLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp
index 4989ff15f1..3f7881ddd3 100644
--- a/src/mbgl/style/layers/heatmap_layer.cpp
+++ b/src/mbgl/style/layers/heatmap_layer.cpp
@@ -85,12 +85,14 @@ void HeatmapLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void HeatmapLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp
index ea736af1ad..e352ae090c 100644
--- a/src/mbgl/style/layers/hillshade_layer.cpp
+++ b/src/mbgl/style/layers/hillshade_layer.cpp
@@ -59,12 +59,14 @@ void HillshadeLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void HillshadeLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs
index 657a7f5a8a..6d748311bf 100644
--- a/src/mbgl/style/layers/layer.cpp.ejs
+++ b/src/mbgl/style/layers/layer.cpp.ejs
@@ -108,12 +108,14 @@ void <%- camelize(type) %>Layer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void <%- camelize(type) %>Layer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/line_layer.cpp b/src/mbgl/style/layers/line_layer.cpp
index 1c7f0d28ee..56eac34c00 100644
--- a/src/mbgl/style/layers/line_layer.cpp
+++ b/src/mbgl/style/layers/line_layer.cpp
@@ -82,12 +82,14 @@ void LineLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void LineLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/raster_layer.cpp b/src/mbgl/style/layers/raster_layer.cpp
index a9a8d273fa..36b2e3e027 100644
--- a/src/mbgl/style/layers/raster_layer.cpp
+++ b/src/mbgl/style/layers/raster_layer.cpp
@@ -59,12 +59,14 @@ void RasterLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void RasterLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp
index d1a1ba246e..c940f3b00a 100644
--- a/src/mbgl/style/layers/symbol_layer.cpp
+++ b/src/mbgl/style/layers/symbol_layer.cpp
@@ -82,12 +82,14 @@ void SymbolLayer::setMinZoom(float minZoom) {
auto impl_ = mutableImpl();
impl_->minZoom = minZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
void SymbolLayer::setMaxZoom(float maxZoom) {
auto impl_ = mutableImpl();
impl_->maxZoom = maxZoom;
baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
}
// Layout properties
diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp
index 82d0c91806..a99cb91d26 100644
--- a/src/mbgl/tile/geometry_tile.cpp
+++ b/src/mbgl/tile/geometry_tile.cpp
@@ -125,23 +125,16 @@ void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) {
}
void GeometryTile::onLayout(LayoutResult result, const uint64_t resultCorrelationID) {
- // Don't mark ourselves loaded or renderable until the first successful placement
- // TODO: Ideally we'd render this tile without symbols as long as this tile wasn't
- // replacing a tile at a different zoom that _did_ have symbols.
- (void)resultCorrelationID;
- nonSymbolBuckets = std::move(result.nonSymbolBuckets);
- pendingFeatureIndex = std::move(result.featureIndex);
- pendingData = std::move(result.tileData);
- observer->onTileChanged(*this);
-}
-
-void GeometryTile::onPlacement(PlacementResult result, const uint64_t resultCorrelationID) {
loaded = true;
renderable = true;
if (resultCorrelationID == correlationID) {
pending = false;
}
- symbolBuckets = std::move(result.symbolBuckets);
+
+ buckets = std::move(result.buckets);
+
+ featureIndexPendingCommit = { std::move(result.featureIndex) };
+
if (result.glyphAtlasImage) {
glyphAtlasImage = std::move(*result.glyphAtlasImage);
}
@@ -183,11 +176,7 @@ void GeometryTile::upload(gl::Context& context) {
}
};
- for (auto& entry : nonSymbolBuckets) {
- uploadFn(*entry.second);
- }
-
- for (auto& entry : symbolBuckets) {
+ for (auto& entry : buckets) {
uploadFn(*entry.second);
}
@@ -203,7 +192,6 @@ void GeometryTile::upload(gl::Context& context) {
}
Bucket* GeometryTile::getBucket(const Layer::Impl& layer) const {
- const auto& buckets = layer.type == LayerType::Symbol ? symbolBuckets : nonSymbolBuckets;
const auto it = buckets.find(layer.id);
if (it == buckets.end()) {
return nullptr;
@@ -214,11 +202,11 @@ Bucket* GeometryTile::getBucket(const Layer::Impl& layer) const {
}
void GeometryTile::commitFeatureIndex() {
- if (pendingFeatureIndex) {
- featureIndex = std::move(pendingFeatureIndex);
- }
- if (pendingData) {
- data = std::move(pendingData);
+ // We commit our pending FeatureIndex when a global placement has run,
+ // synchronizing the global CollisionIndex with the latest buckets/FeatureIndex
+ if (featureIndexPendingCommit) {
+ featureIndex = std::move(*featureIndexPendingCommit);
+ featureIndexPendingCommit = nullopt;
}
}
@@ -230,7 +218,7 @@ void GeometryTile::queryRenderedFeatures(
const RenderedQueryOptions& options,
const CollisionIndex& collisionIndex) {
- if (!featureIndex || !data) return;
+ if (!getData()) return;
// Determine the additional radius needed factoring in property functions
float additionalRadius = 0;
@@ -247,7 +235,6 @@ void GeometryTile::queryRenderedFeatures(
util::tileSize * id.overscaleFactor(),
std::pow(2, transformState.getZoom() - id.overscaledZ),
options,
- *data,
id.toUnwrapped(),
sourceID,
layers,
@@ -259,8 +246,8 @@ void GeometryTile::querySourceFeatures(
std::vector<Feature>& result,
const SourceQueryOptions& options) {
- // Data not yet available
- if (!data) {
+ // Data not yet available, or tile is empty
+ if (!getData()) {
return;
}
@@ -273,7 +260,7 @@ void GeometryTile::querySourceFeatures(
for (auto sourceLayer : *options.sourceLayers) {
// Go throught all sourceLayers, if any
// to gather all the features
- auto layer = data->getLayer(sourceLayer);
+ auto layer = getData()->getLayer(sourceLayer);
if (layer) {
auto featureCount = layer->featureCount();
diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp
index 00a4aafadf..418db4a0b2 100644
--- a/src/mbgl/tile/geometry_tile.hpp
+++ b/src/mbgl/tile/geometry_tile.hpp
@@ -65,33 +65,21 @@ public:
class LayoutResult {
public:
- std::unordered_map<std::string, std::shared_ptr<Bucket>> nonSymbolBuckets;
+ std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
std::unique_ptr<FeatureIndex> featureIndex;
- std::unique_ptr<GeometryTileData> tileData;
-
- LayoutResult(std::unordered_map<std::string, std::shared_ptr<Bucket>> nonSymbolBuckets_,
- std::unique_ptr<FeatureIndex> featureIndex_,
- std::unique_ptr<GeometryTileData> tileData_)
- : nonSymbolBuckets(std::move(nonSymbolBuckets_)),
- featureIndex(std::move(featureIndex_)),
- tileData(std::move(tileData_)) {}
- };
- void onLayout(LayoutResult, uint64_t correlationID);
-
- class PlacementResult {
- public:
- std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets;
optional<AlphaImage> glyphAtlasImage;
optional<PremultipliedImage> iconAtlasImage;
- PlacementResult(std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets_,
- optional<AlphaImage> glyphAtlasImage_,
- optional<PremultipliedImage> iconAtlasImage_)
- : symbolBuckets(std::move(symbolBuckets_)),
+ LayoutResult(std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets_,
+ std::unique_ptr<FeatureIndex> featureIndex_,
+ optional<AlphaImage> glyphAtlasImage_,
+ optional<PremultipliedImage> iconAtlasImage_)
+ : buckets(std::move(buckets_)),
+ featureIndex(std::move(featureIndex_)),
glyphAtlasImage(std::move(glyphAtlasImage_)),
iconAtlasImage(std::move(iconAtlasImage_)) {}
};
- void onPlacement(PlacementResult, uint64_t correlationID);
+ void onLayout(LayoutResult, uint64_t correlationID);
void onError(std::exception_ptr, uint64_t correlationID);
@@ -104,7 +92,7 @@ public:
protected:
const GeometryTileData* getData() {
- return data.get();
+ return featureIndex ? featureIndex->getData() : nullptr;
}
private:
@@ -123,17 +111,14 @@ private:
uint64_t correlationID = 0;
- std::unordered_map<std::string, std::shared_ptr<Bucket>> nonSymbolBuckets;
+ std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
+
+ optional<std::unique_ptr<FeatureIndex>> featureIndexPendingCommit;
std::unique_ptr<FeatureIndex> featureIndex;
- std::unique_ptr<FeatureIndex> pendingFeatureIndex;
- std::unique_ptr<const GeometryTileData> data;
- std::unique_ptr<const GeometryTileData> pendingData;
optional<AlphaImage> glyphAtlasImage;
optional<PremultipliedImage> iconAtlasImage;
- std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets;
-
const MapMode mode;
bool showCollisionBoxes;
diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp
index 8b6f1f63bf..1378ad5d3a 100644
--- a/src/mbgl/tile/geometry_tile_worker.cpp
+++ b/src/mbgl/tile/geometry_tile_worker.cpp
@@ -41,48 +41,73 @@ GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
GeometryTileWorker::~GeometryTileWorker() = default;
/*
- NOTE: The comments below are technically correct, but currently
- conceptually misleading. The change to foreground label placement
- means that:
- (1) "placement" here is a misnomer: the remaining role of
- "attemptPlacement" is symbol buffer generation
- (2) Once a tile has completed layout, we will only run
- "attemptPlacement" once
- (3) Tiles won't be rendered until "attemptPlacement" has run once
-
- TODO: Simplify GeometryTileWorker to fit its new role
- https://github.com/mapbox/mapbox-gl-native/issues/10457
-
GeometryTileWorker is a state machine. This is its transition diagram.
States are indicated by [state], lines are transitions triggered by
messages, (parentheses) are actions taken on transition.
- [idle] <----------------------------.
- | |
- set{Data,Layers,Placement}, symbolDependenciesChanged |
- | |
- (do layout/placement; self-send "coalesced") |
- v |
- [coalescing] --- coalesced ------------.
- | |
- .-----------------. .---------------.
+ [Idle] <-------------------------.
+ | |
+ set{Data,Layers}, symbolDependenciesChanged, |
+ setShowCollisionBoxes |
+ | |
+ (do parse and/or symbol layout; self-send "coalesced") |
+ v |
+ [Coalescing] --- coalesced ---------.
+ | |
+ .-----------. .---------------------.
| |
- .--- set{Data,Layers} setPlacement -----.
- | | | |
- | v v |
- .-- [need layout] <-- set{Data,Layers} -- [need placement] --.
+ .--- set{Data,Layers} setShowCollisionBoxes,
+ | | symbolDependenciesChanged --.
+ | | | |
+ | v v |
+ .-- [NeedsParse] <-- set{Data,Layers} -- [NeedsSymbolLayout] ---.
| |
coalesced coalesced
| |
v v
- (do layout or placement; self-send "coalesced"; goto [coalescing])
-
- The idea is that in the [idle] state, layout or placement happens immediately
- in response to a "set" message. During this processing, multiple "set" messages
- might get queued in the mailbox. At the end of processing, we self-send "coalesced",
- read all the queued messages until we get to "coalesced", and then redo either
- layout or placement if there were one or more "set"s (with layout taking priority,
- since it will trigger placement when complete), or return to the [idle] state if not.
+ (do parse or symbol layout; self-send "coalesced"; goto [coalescing])
+
+ The idea is that in the [idle] state, parsing happens immediately in response to
+ a "set" message, and symbol layout happens once all symbol dependencies are met.
+ During this processing, multiple "set" messages might get queued in the mailbox.
+ At the end of processing, we self-send "coalesced", read all the queued messages
+ until we get to "coalesced", and then re-parse if there were one or more "set"s or
+ return to the [idle] state if not.
+
+ One important goal of the design is to prevent starvation. Under heavy load new
+ requests for tiles should not prevent in progress request from completing.
+ It is nevertheless possible to restart an in-progress request:
+
+ - [Idle] setData -> parse()
+ sends getGlyphs, hasPendingSymbolDependencies() is true
+ enters [Coalescing], sends coalesced
+ - [Coalescing] coalesced -> [Idle]
+ - [Idle] setData -> new parse(), interrupts old parse()
+ sends getGlyphs, hasPendingSymbolDependencies() is true
+ enters [Coalescing], sends coalesced
+ - [Coalescing] onGlyphsAvailable -> [NeedsSymbolLayout]
+ hasPendingSymbolDependencies() may or may not be true
+ - [NeedsSymbolLayout] coalesced -> performSymbolLayout()
+ Generates result depending on whether dependencies are met
+ -> [Idle]
+
+ In this situation, we are counting on the idea that even with rapid changes to
+ the tile's data, the set of glyphs/images it requires will not keep growing without
+ limit.
+
+ Although parsing (which populates all non-symbol buckets and requests dependencies
+ for symbol buckets) is internally separate from symbol layout, we only return
+ results to the foreground when we have completed both steps. Because we _move_
+ the result buckets to the foreground, it is necessary to re-generate all buckets from
+ scratch for `setShowCollisionBoxes`, even though it only affects symbol layers.
+
+ The GL JS equivalent (in worker_tile.js and vector_tile_worker_source.js)
+ is somewhat simpler because it relies on getGlyphs/getImages calls that transfer
+ an entire set of glyphs/images on every tile load, while the native logic
+ maintains a local state that can be incrementally updated. Because each tile load
+ call becomes self-contained, the equivalent of the coalescing logic is handled by
+ 'reloadTile' queueing a single extra 'reloadTile' callback to run after the next
+ completed parse.
*/
void GeometryTileWorker::setData(std::unique_ptr<const GeometryTileData> data_, uint64_t correlationID_) {
@@ -92,14 +117,14 @@ void GeometryTileWorker::setData(std::unique_ptr<const GeometryTileData> data_,
switch (state) {
case Idle:
- redoLayout();
+ parse();
coalesce();
break;
case Coalescing:
- case NeedLayout:
- case NeedPlacement:
- state = NeedLayout;
+ case NeedsParse:
+ case NeedsSymbolLayout:
+ state = NeedsParse;
break;
}
} catch (...) {
@@ -114,16 +139,16 @@ void GeometryTileWorker::setLayers(std::vector<Immutable<Layer::Impl>> layers_,
switch (state) {
case Idle:
- redoLayout();
+ parse();
coalesce();
break;
case Coalescing:
- case NeedPlacement:
- state = NeedLayout;
+ case NeedsSymbolLayout:
+ state = NeedsParse;
break;
- case NeedLayout:
+ case NeedsParse:
break;
}
} catch (...) {
@@ -138,16 +163,20 @@ void GeometryTileWorker::setShowCollisionBoxes(bool showCollisionBoxes_, uint64_
switch (state) {
case Idle:
- attemptPlacement();
- coalesce();
+ if (!hasPendingParseResult()) {
+ // Trigger parse if nothing is in flight, otherwise symbol layout will automatically
+ // pick up the change
+ parse();
+ coalesce();
+ }
break;
case Coalescing:
- state = NeedPlacement;
+ state = NeedsSymbolLayout;
break;
- case NeedPlacement:
- case NeedLayout:
+ case NeedsSymbolLayout:
+ case NeedsParse:
break;
}
} catch (...) {
@@ -160,19 +189,23 @@ void GeometryTileWorker::symbolDependenciesChanged() {
switch (state) {
case Idle:
if (symbolLayoutsNeedPreparation) {
- attemptPlacement();
+ // symbolLayoutsNeedPreparation can only be set true by parsing
+ // and the parse result can only be cleared by performSymbolLayout
+ // which also clears symbolLayoutsNeedPreparation
+ assert(hasPendingParseResult());
+ performSymbolLayout();
coalesce();
}
break;
case Coalescing:
if (symbolLayoutsNeedPreparation) {
- state = NeedPlacement;
+ state = NeedsSymbolLayout;
}
break;
- case NeedPlacement:
- case NeedLayout:
+ case NeedsSymbolLayout:
+ case NeedsParse:
break;
}
} catch (...) {
@@ -191,13 +224,16 @@ void GeometryTileWorker::coalesced() {
state = Idle;
break;
- case NeedLayout:
- redoLayout();
+ case NeedsParse:
+ parse();
coalesce();
break;
- case NeedPlacement:
- attemptPlacement();
+ case NeedsSymbolLayout:
+ // We may have entered NeedsSymbolLayout while coalescing
+ // after a performSymbolLayout. In that case, we need to
+ // start over with parsing in order to do another layout.
+ hasPendingParseResult() ? performSymbolLayout() : parse();
coalesce();
break;
}
@@ -279,7 +315,7 @@ static std::vector<std::unique_ptr<RenderLayer>> toRenderLayers(const std::vecto
return renderLayers;
}
-void GeometryTileWorker::redoLayout() {
+void GeometryTileWorker::parse() {
if (!data || !layers) {
return;
}
@@ -292,8 +328,8 @@ void GeometryTileWorker::redoLayout() {
}
std::unordered_map<std::string, std::unique_ptr<SymbolLayout>> symbolLayoutMap;
- std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
- auto featureIndex = std::make_unique<FeatureIndex>();
+ buckets.clear();
+ featureIndex = std::make_unique<FeatureIndex>(*data ? (*data)->clone() : nullptr);
BucketParameters parameters { id, mode, pixelRatio };
GlyphDependencies glyphDependencies;
@@ -368,13 +404,7 @@ void GeometryTileWorker::redoLayout() {
requestNewGlyphs(glyphDependencies);
requestNewImages(imageDependencies);
- parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult {
- std::move(buckets),
- std::move(featureIndex),
- *data ? (*data)->clone() : nullptr,
- }, correlationID);
-
- attemptPlacement();
+ performSymbolLayout();
}
bool GeometryTileWorker::hasPendingSymbolDependencies() const {
@@ -385,9 +415,13 @@ bool GeometryTileWorker::hasPendingSymbolDependencies() const {
}
return !pendingImageDependencies.empty();
}
+
+bool GeometryTileWorker::hasPendingParseResult() const {
+ return bool(featureIndex);
+}
-void GeometryTileWorker::attemptPlacement() {
- if (!data || !layers || hasPendingSymbolDependencies()) {
+void GeometryTileWorker::performSymbolLayout() {
+ if (!data || !layers || !hasPendingParseResult() || hasPendingSymbolDependencies()) {
return;
}
@@ -414,8 +448,6 @@ void GeometryTileWorker::attemptPlacement() {
symbolLayoutsNeedPreparation = false;
}
- std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
-
for (auto& symbolLayout : symbolLayouts) {
if (obsolete) {
return;
@@ -435,11 +467,12 @@ void GeometryTileWorker::attemptPlacement() {
}
firstLoad = false;
-
- parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult {
+
+ parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult {
std::move(buckets),
+ std::move(featureIndex),
std::move(glyphAtlasImage),
- std::move(iconAtlasImage),
+ std::move(iconAtlasImage)
}, correlationID);
}
diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp
index 0276392679..b5417c8114 100644
--- a/src/mbgl/tile/geometry_tile_worker.hpp
+++ b/src/mbgl/tile/geometry_tile_worker.hpp
@@ -8,6 +8,8 @@
#include <mbgl/util/optional.hpp>
#include <mbgl/util/immutable.hpp>
#include <mbgl/style/layer_impl.hpp>
+#include <mbgl/geometry/feature_index.hpp>
+#include <mbgl/renderer/bucket.hpp>
#include <atomic>
#include <memory>
@@ -43,8 +45,8 @@ public:
private:
void coalesced();
- void redoLayout();
- void attemptPlacement();
+ void parse();
+ void performSymbolLayout();
void coalesce();
@@ -53,6 +55,7 @@ private:
void symbolDependenciesChanged();
bool hasPendingSymbolDependencies() const;
+ bool hasPendingParseResult() const;
ActorRef<GeometryTileWorker> self;
ActorRef<GeometryTile> parent;
@@ -62,12 +65,15 @@ private:
const std::atomic<bool>& obsolete;
const MapMode mode;
const float pixelRatio;
+
+ std::unique_ptr<FeatureIndex> featureIndex;
+ std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
enum State {
Idle,
Coalescing,
- NeedLayout,
- NeedPlacement
+ NeedsParse,
+ NeedsSymbolLayout
};
State state = Idle;
diff --git a/src/mbgl/util/color.cpp b/src/mbgl/util/color.cpp
index c8145d36e7..55f1b65436 100644
--- a/src/mbgl/util/color.cpp
+++ b/src/mbgl/util/color.cpp
@@ -23,11 +23,25 @@ optional<Color> Color::parse(const std::string& s) {
}
std::string Color::stringify() const {
+ std::array<double, 4> array = toArray();
return "rgba(" +
- util::toString(r * 255 / a) + "," +
- util::toString(g * 255 / a) + "," +
- util::toString(b * 255 / a) + "," +
- util::toString(a) + ")";
+ util::toString(array[0]) + "," +
+ util::toString(array[1]) + "," +
+ util::toString(array[2]) + "," +
+ util::toString(array[3]) + ")";
+}
+
+std::array<double, 4> Color::toArray() const {
+ if (a == 0) {
+ return {{ 0, 0, 0, 0 }};
+ } else {
+ return {{
+ r * 255 / a,
+ g * 255 / a,
+ b * 255 / a,
+ a,
+ }};
+ }
}
} // namespace mbgl