Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[core] Port symbol-sort-key symbol layout property #14386

Merged
merged 8 commits into from
Apr 17, 2019
4 changes: 4 additions & 0 deletions include/mbgl/style/layers/symbol_layer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class SymbolLayer : public Layer {
const PropertyValue<bool>& getSymbolAvoidEdges() const;
void setSymbolAvoidEdges(const PropertyValue<bool>&);

static PropertyValue<float> getDefaultSymbolSortKey();
const PropertyValue<float>& getSymbolSortKey() const;
void setSymbolSortKey(const PropertyValue<float>&);

static PropertyValue<SymbolZOrderType> getDefaultSymbolZOrder();
const PropertyValue<SymbolZOrderType>& getSymbolZOrder() const;
void setSymbolZOrder(const PropertyValue<SymbolZOrderType>&);
Expand Down
1 change: 1 addition & 0 deletions include/mbgl/style/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum class SymbolPlacementType : uint8_t {
};

enum class SymbolZOrderType : uint8_t {
Auto,
ViewportY,
Source
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public final class Property {

// SYMBOL_Z_ORDER: Controls the order in which overlapping symbols in the same layer are rendered

/**
* If {@link SYMBOL_SORT_KEY} is set, sort based on that. Otherwise sort symbols by their position relative to the viewport.
alexshalamov marked this conversation as resolved.
Show resolved Hide resolved
*/
public static final String SYMBOL_Z_ORDER_AUTO = "auto";
/**
* Symbols will be sorted by their y-position relative to the viewport.
*/
Expand All @@ -123,6 +127,7 @@ public final class Property {
* Controls the order in which overlapping symbols in the same layer are rendered
*/
@StringDef({
SYMBOL_Z_ORDER_AUTO,
SYMBOL_Z_ORDER_VIEWPORT_Y,
SYMBOL_Z_ORDER_SOURCE,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,26 @@ public static PropertyValue<Expression> symbolAvoidEdges(Expression value) {
return new LayoutPropertyValue<>("symbol-avoid-edges", value);
}

/**
* Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key wehn they overlap. Features with a lower sort key will have priority over other features when doing placement.
alexshalamov marked this conversation as resolved.
Show resolved Hide resolved
*
* @param value a Float value
* @return property wrapper around Float
*/
public static PropertyValue<Float> symbolSortKey(Float value) {
return new LayoutPropertyValue<>("symbol-sort-key", value);
}

/**
* Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key wehn they overlap. Features with a lower sort key will have priority over other features when doing placement.
*
* @param value a Float value
* @return property wrapper around Float
*/
public static PropertyValue<Expression> symbolSortKey(Expression value) {
return new LayoutPropertyValue<>("symbol-sort-key", value);
}

/**
* Controls the order in which overlapping symbols in the same layer are rendered
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,18 @@ public PropertyValue<Boolean> getSymbolAvoidEdges() {
return (PropertyValue<Boolean>) new PropertyValue("symbol-avoid-edges", nativeGetSymbolAvoidEdges());
}

/**
* Get the SymbolSortKey property
*
* @return property wrapper value around Float
*/
@NonNull
@SuppressWarnings("unchecked")
public PropertyValue<Float> getSymbolSortKey() {
checkThread();
return (PropertyValue<Float>) new PropertyValue("symbol-sort-key", nativeGetSymbolSortKey());
}

/**
* Get the SymbolZOrder property
*
Expand Down Expand Up @@ -1113,6 +1125,10 @@ public PropertyValue<String> getTextTranslateAnchor() {
@Keep
private native Object nativeGetSymbolAvoidEdges();

@NonNull
@Keep
private native Object nativeGetSymbolSortKey();

@NonNull
@Keep
private native Object nativeGetSymbolZOrder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,32 @@ public void testSymbolAvoidEdgesAsConstant() {
assertEquals(layer.getSymbolAvoidEdges().getValue(), propertyValue);
}

@Test
@UiThreadTest
public void testSymbolSortKeyAsConstant() {
Timber.i("symbol-sort-key");
assertNotNull(layer);
assertNull(layer.getSymbolSortKey().getValue());

// Set and Get
Float propertyValue = 0.3f;
layer.setProperties(symbolSortKey(propertyValue));
assertEquals(layer.getSymbolSortKey().getValue(), propertyValue);
}

@Test
@UiThreadTest
public void testSymbolSortKeyAsExpression() {
Timber.i("symbol-sort-key-expression");
assertNotNull(layer);
assertNull(layer.getSymbolSortKey().getExpression());

// Set and Get
Expression expression = number(Expression.get("undefined"));
layer.setProperties(symbolSortKey(expression));
assertEquals(layer.getSymbolSortKey().getExpression(), expression);
}

@Test
@UiThreadTest
public void testSymbolZOrderAsConstant() {
Expand All @@ -148,7 +174,7 @@ public void testSymbolZOrderAsConstant() {
assertNull(layer.getSymbolZOrder().getValue());

// Set and Get
String propertyValue = SYMBOL_Z_ORDER_VIEWPORT_Y;
String propertyValue = SYMBOL_Z_ORDER_AUTO;
layer.setProperties(symbolZOrder(propertyValue));
assertEquals(layer.getSymbolZOrder().getValue(), propertyValue);
}
Expand Down
6 changes: 6 additions & 0 deletions platform/android/src/style/layers/symbol_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ namespace android {
return std::move(*convert<jni::Local<jni::Object<>>>(env, toSymbolLayer(layer).getSymbolAvoidEdges()));
}

jni::Local<jni::Object<>> SymbolLayer::getSymbolSortKey(jni::JNIEnv& env) {
using namespace mbgl::android::conversion;
return std::move(*convert<jni::Local<jni::Object<>>>(env, toSymbolLayer(layer).getSymbolSortKey()));
}

jni::Local<jni::Object<>> SymbolLayer::getSymbolZOrder(jni::JNIEnv& env) {
using namespace mbgl::android::conversion;
return std::move(*convert<jni::Local<jni::Object<>>>(env, toSymbolLayer(layer).getSymbolZOrder()));
Expand Down Expand Up @@ -500,6 +505,7 @@ namespace android {
METHOD(&SymbolLayer::getSymbolPlacement, "nativeGetSymbolPlacement"),
METHOD(&SymbolLayer::getSymbolSpacing, "nativeGetSymbolSpacing"),
METHOD(&SymbolLayer::getSymbolAvoidEdges, "nativeGetSymbolAvoidEdges"),
METHOD(&SymbolLayer::getSymbolSortKey, "nativeGetSymbolSortKey"),
METHOD(&SymbolLayer::getSymbolZOrder, "nativeGetSymbolZOrder"),
METHOD(&SymbolLayer::getIconAllowOverlap, "nativeGetIconAllowOverlap"),
METHOD(&SymbolLayer::getIconIgnorePlacement, "nativeGetIconIgnorePlacement"),
Expand Down
2 changes: 2 additions & 0 deletions platform/android/src/style/layers/symbol_layer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class SymbolLayer : public Layer {

jni::Local<jni::Object<jni::ObjectTag>> getSymbolAvoidEdges(jni::JNIEnv&);

jni::Local<jni::Object<jni::ObjectTag>> getSymbolSortKey(jni::JNIEnv&);

jni::Local<jni::Object<jni::ObjectTag>> getSymbolZOrder(jni::JNIEnv&);

jni::Local<jni::Object<jni::ObjectTag>> getIconAllowOverlap(jni::JNIEnv&);
Expand Down
28 changes: 26 additions & 2 deletions platform/darwin/src/MGLSymbolStyleLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ typedef NS_ENUM(NSUInteger, MGLSymbolPlacement) {
property.
*/
typedef NS_ENUM(NSUInteger, MGLSymbolZOrder) {
/**
If `MGLSymbolStyleLayer.symbolSortKey` is set, sort based on that.
Otherwise sort symbols by their position relative to the viewport.
*/
MGLSymbolZOrderAuto,
/**
Specify this z order if symbols’ appearance relies on lower features
overlapping higher features. For example, symbols with a pin-like
Expand Down Expand Up @@ -1018,6 +1023,23 @@ MGL_EXPORT
*/
@property (nonatomic, null_resettable) NSExpression *symbolPlacement;

/**
Sorts features in ascending order based on this value. Features with a higher
sort key will appear above features with a lower sort key wehn they overlap.
alexshalamov marked this conversation as resolved.
Show resolved Hide resolved
Features with a lower sort key will have priority over other features when
doing placement.

You can set this property to an expression containing any of the following:

* Constant numeric values
* Predefined functions, including mathematical and string operators
* Conditional expressions
* Variable assignments and references to assigned variables
* Interpolation and step functions applied to the `$zoomLevel` variable and/or
feature attributes
*/
@property (nonatomic, null_resettable) NSExpression *symbolSortKey;

/**
Distance between two symbol anchors.

Expand Down Expand Up @@ -1045,13 +1067,15 @@ MGL_EXPORT
/**
Controls the order in which overlapping symbols in the same layer are rendered

The default value of this property is an expression that evaluates to
`viewport-y`. Set this property to `nil` to reset it to the default value.
The default value of this property is an expression that evaluates to `auto`.
Set this property to `nil` to reset it to the default value.

You can set this property to an expression containing any of the following:

* Constant `MGLSymbolZOrder` values
* Any of the following constant string values:
* `auto`: If `symbol-sort-key` is set, sort based on that. Otherwise sort
symbols by their position relative to the viewport.
* `viewport-y`: Specify this z order if symbols’ appearance relies on lower
features overlapping higher features. For example, symbols with a pin-like
appearance would require this z order.
Expand Down
19 changes: 19 additions & 0 deletions platform/darwin/src/MGLSymbolStyleLayer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
});

MBGL_DEFINE_ENUM(MGLSymbolZOrder, {
{ MGLSymbolZOrderAuto, "auto" },
{ MGLSymbolZOrderViewportY, "viewport-y" },
{ MGLSymbolZOrderSource, "source" },
});
Expand Down Expand Up @@ -587,6 +588,24 @@ - (NSExpression *)symbolPlacement {
return MGLStyleValueTransformer<mbgl::style::SymbolPlacementType, NSValue *, mbgl::style::SymbolPlacementType, MGLSymbolPlacement>().toExpression(propertyValue);
}

- (void)setSymbolSortKey:(NSExpression *)symbolSortKey {
MGLAssertStyleLayerIsValid();
MGLLogDebug(@"Setting symbolSortKey: %@", symbolSortKey);

auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(symbolSortKey, true);
self.rawLayer->setSymbolSortKey(mbglValue);
}

- (NSExpression *)symbolSortKey {
MGLAssertStyleLayerIsValid();

auto propertyValue = self.rawLayer->getSymbolSortKey();
if (propertyValue.isUndefined()) {
propertyValue = self.rawLayer->getDefaultSymbolSortKey();
}
return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue);
}

- (void)setSymbolSpacing:(NSExpression *)symbolSpacing {
MGLAssertStyleLayerIsValid();
MGLLogDebug(@"Setting symbolSpacing: %@", symbolSpacing);
Expand Down
71 changes: 71 additions & 0 deletions platform/darwin/test/MGLSymbolStyleLayerTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,75 @@ - (void)testProperties {
XCTAssertThrowsSpecificNamed(layer.symbolPlacement = functionExpression, NSException, NSInvalidArgumentException, @"MGLSymbolLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
}

// symbol-sort-key
{
XCTAssertTrue(rawLayer->getSymbolSortKey().isUndefined(),
@"symbol-sort-key should be unset initially.");
NSExpression *defaultExpression = layer.symbolSortKey;

NSExpression *constantExpression = [NSExpression expressionWithFormat:@"1"];
layer.symbolSortKey = constantExpression;
mbgl::style::PropertyValue<float> propertyValue = { 1.0 };
XCTAssertEqual(rawLayer->getSymbolSortKey(), propertyValue,
@"Setting symbolSortKey to a constant value expression should update symbol-sort-key.");
XCTAssertEqualObjects(layer.symbolSortKey, constantExpression,
@"symbolSortKey should round-trip constant value expressions.");

constantExpression = [NSExpression expressionWithFormat:@"1"];
NSExpression *functionExpression = [NSExpression expressionWithFormat:@"mgl_step:from:stops:($zoomLevel, %@, %@)", constantExpression, @{@18: constantExpression}];
layer.symbolSortKey = functionExpression;

{
using namespace mbgl::style::expression::dsl;
propertyValue = mbgl::style::PropertyExpression<float>(
step(zoom(), literal(1.0), 18.0, literal(1.0))
);
}

XCTAssertEqual(rawLayer->getSymbolSortKey(), propertyValue,
@"Setting symbolSortKey to a camera expression should update symbol-sort-key.");
XCTAssertEqualObjects(layer.symbolSortKey, functionExpression,
@"symbolSortKey should round-trip camera expressions.");

functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(keyName, 'linear', nil, %@)", @{@18: constantExpression}];
layer.symbolSortKey = functionExpression;

{
using namespace mbgl::style::expression::dsl;
propertyValue = mbgl::style::PropertyExpression<float>(
interpolate(linear(), number(get("keyName")), 18.0, literal(1.0))
);
}

XCTAssertEqual(rawLayer->getSymbolSortKey(), propertyValue,
@"Setting symbolSortKey to a data expression should update symbol-sort-key.");
NSExpression *pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:(CAST(keyName, 'NSNumber'), 'linear', nil, %@)", @{@18: constantExpression}];
XCTAssertEqualObjects(layer.symbolSortKey, pedanticFunctionExpression,
@"symbolSortKey should round-trip data expressions.");

functionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: functionExpression}];
layer.symbolSortKey = functionExpression;

{
using namespace mbgl::style::expression::dsl;
propertyValue = mbgl::style::PropertyExpression<float>(
interpolate(linear(), zoom(), 10.0, interpolate(linear(), number(get("keyName")), 18.0, literal(1.0)))
);
}

XCTAssertEqual(rawLayer->getSymbolSortKey(), propertyValue,
@"Setting symbolSortKey to a camera-data expression should update symbol-sort-key.");
pedanticFunctionExpression = [NSExpression expressionWithFormat:@"mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'linear', nil, %@)", @{@10: pedanticFunctionExpression}];
XCTAssertEqualObjects(layer.symbolSortKey, pedanticFunctionExpression,
@"symbolSortKey should round-trip camera-data expressions.");

layer.symbolSortKey = nil;
XCTAssertTrue(rawLayer->getSymbolSortKey().isUndefined(),
@"Unsetting symbolSortKey should return symbol-sort-key to the default value.");
XCTAssertEqualObjects(layer.symbolSortKey, defaultExpression,
@"symbolSortKey should return the default value after being unset.");
}

// symbol-spacing
{
XCTAssertTrue(rawLayer->getSymbolSpacing().isUndefined(),
Expand Down Expand Up @@ -2994,6 +3063,7 @@ - (void)testPropertyNames {
[self testPropertyName:@"maximum-text-width" isBoolean:NO];
[self testPropertyName:@"symbol-avoids-edges" isBoolean:YES];
[self testPropertyName:@"symbol-placement" isBoolean:NO];
[self testPropertyName:@"symbol-sort-key" isBoolean:NO];
[self testPropertyName:@"symbol-spacing" isBoolean:NO];
[self testPropertyName:@"symbol-z-order" isBoolean:NO];
[self testPropertyName:@"text" isBoolean:NO];
Expand Down Expand Up @@ -3053,6 +3123,7 @@ - (void)testValueAdditions {
XCTAssertEqual([NSValue valueWithMGLSymbolPlacement:MGLSymbolPlacementPoint].MGLSymbolPlacementValue, MGLSymbolPlacementPoint);
XCTAssertEqual([NSValue valueWithMGLSymbolPlacement:MGLSymbolPlacementLine].MGLSymbolPlacementValue, MGLSymbolPlacementLine);
XCTAssertEqual([NSValue valueWithMGLSymbolPlacement:MGLSymbolPlacementLineCenter].MGLSymbolPlacementValue, MGLSymbolPlacementLineCenter);
XCTAssertEqual([NSValue valueWithMGLSymbolZOrder:MGLSymbolZOrderAuto].MGLSymbolZOrderValue, MGLSymbolZOrderAuto);
XCTAssertEqual([NSValue valueWithMGLSymbolZOrder:MGLSymbolZOrderViewportY].MGLSymbolZOrderValue, MGLSymbolZOrderViewportY);
XCTAssertEqual([NSValue valueWithMGLSymbolZOrder:MGLSymbolZOrderSource].MGLSymbolZOrderValue, MGLSymbolZOrderSource);
XCTAssertEqual([NSValue valueWithMGLTextAnchor:MGLTextAnchorCenter].MGLTextAnchorValue, MGLTextAnchorCenter);
Expand Down
3 changes: 0 additions & 3 deletions platform/node/test/ignores.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,6 @@
"render-tests/feature-state/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12613",
"render-tests/feature-state/set-paint-property": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue",
"render-tests/feature-state/vector-source": "https://github.com/mapbox/mapbox-gl-native/issues/12613",
"render-tests/symbol-sort-key/icon-expression": "https://github.com/mapbox/mapbox-gl-native/issues/14028",
"render-tests/symbol-sort-key/text-expression": "https://github.com/mapbox/mapbox-gl-native/issues/14028",
"render-tests/symbol-sort-key/text-placement": "https://github.com/mapbox/mapbox-gl-native/issues/14028",
"render-tests/fill-opacity/opaque-fill-over-symbol-layer": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/7612",
"render-tests/text-variable-anchor/pitched-rotated-debug": "https://github.com/mapbox/mapbox-gl-native/issues/14211",
"render-tests/text-variable-anchor/rotated-offset": "https://github.com/mapbox/mapbox-gl-native/issues/14211",
Expand Down
5 changes: 0 additions & 5 deletions scripts/style-spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8');

// Make temporary modifications here when Native doesn't have all features that JS has.
delete spec.layout_symbol['symbol-sort-key'];
delete spec.layout_symbol['symbol-z-order'].values['auto'];
spec.layout_symbol['symbol-z-order'].default = 'viewport-y';
5 changes: 5 additions & 0 deletions src/mbgl/layout/symbol_feature.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ class SymbolFeature : public GeometryTileFeature {
FeatureIdentifier getID() const override { return feature->getID(); };
GeometryCollection getGeometries() const override { return geometry; };

friend bool operator < (const SymbolFeature& lhs, const SymbolFeature& rhs) {
return lhs.sortKey < rhs.sortKey;
}

std::unique_ptr<GeometryTileFeature> feature;
GeometryCollection geometry;
optional<TaggedString> formattedText;
optional<std::string> icon;
float sortKey = 0.0f;
std::size_t index;
};

Expand Down
Loading