Skip to content

Commit

Permalink
Impement FormatSectionOverride expression and use it for SymbolStyleL…
Browse files Browse the repository at this point in the history
…ayer
  • Loading branch information
alexshalamov committed Mar 26, 2019
1 parent 5a5bc24 commit 65bb6c9
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 23 deletions.
16 changes: 15 additions & 1 deletion build/generate-style-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ global.camelize = function (str) {
});
};

global.camelizeWithLeadingLowercase = function (str) {
return str.replace(/-(.)/g, function (_, x) {
return x.toUpperCase();
});
};

global.flowType = function (property) {
switch (property.type) {
case 'boolean':
Expand Down Expand Up @@ -96,10 +102,18 @@ global.defaultValue = function (property) {
}
};

global.overrides = function (property) {
return `{ runtimeType: ${runtimeType(property)}, getOverride: (o) => o.${camelizeWithLeadingLowercase(property.name)}, hasOverride: (o) => !!o.${camelizeWithLeadingLowercase(property.name)} }`;
}

global.propertyValue = function (property, type) {
switch (property['property-type']) {
case 'data-driven':
return `new DataDrivenProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`;
if (property.overridable) {
return `new DataDrivenProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"], ${overrides(property)})`;
} else {
return `new DataDrivenProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`;
}
case 'cross-faded':
return `new CrossFadedProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`;
case 'cross-faded-data-driven':
Expand Down
15 changes: 12 additions & 3 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { register } from '../../util/web_worker_transfer';
import EvaluationParameters from '../../style/evaluation_parameters';
import Formatted from '../../style-spec/expression/types/formatted';


import type {
Bucket,
BucketParameters,
Expand All @@ -45,7 +44,7 @@ import type {
} from '../bucket';
import type {CollisionBoxArray, CollisionBox, SymbolInstance} from '../array_types';
import type { StructArray, StructArrayMember } from '../../util/struct_array';
import type SymbolStyleLayer from '../../style/style_layer/symbol_style_layer';
import SymbolStyleLayer from '../../style/style_layer/symbol_style_layer';
import type Context from '../../gl/context';
import type IndexBuffer from '../../gl/index_buffer';
import type VertexBuffer from '../../gl/vertex_buffer';
Expand Down Expand Up @@ -292,6 +291,7 @@ class SymbolBucket implements Bucket {
uploaded: boolean;
sourceLayerIndex: number;
sourceID: string;
hasPaintOverrides: boolean;

constructor(options: BucketParameters<SymbolStyleLayer>) {
this.collisionBoxArray = options.collisionBoxArray;
Expand All @@ -303,6 +303,7 @@ class SymbolBucket implements Bucket {
this.pixelRatio = options.pixelRatio;
this.sourceLayerIndex = options.sourceLayerIndex;
this.hasPattern = false;
this.hasPaintOverrides = false;

const layer = this.layers[0];
const unevaluatedLayoutValues = layer._unevaluatedLayout._values;
Expand All @@ -324,6 +325,14 @@ class SymbolBucket implements Bucket {
}

createArrays() {
const layout = this.layers[0].layout;
this.hasPaintOverrides = SymbolStyleLayer.hasPaintOverrides(layout);
if (this.hasPaintOverrides) {
for (const layer of this.layers) {
layer.setPaintOverrides(layout);
}
}

this.text = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^text/.test(property)));
this.icon = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^icon/.test(property)));

Expand Down Expand Up @@ -552,7 +561,7 @@ class SymbolBucket implements Bucket {
if (feature.text && feature.text.sections) {
const sections = feature.text.sections;

if (sections[0].textColor) {
if (this.hasPaintOverrides) {
let currentSectionIndex;
const populatePaintArrayForSection = (sectionIndex?: number, lastSection: boolean) => {
if (currentSectionIndex !== undefined && (currentSectionIndex !== sectionIndex || lastSection)) {
Expand Down
54 changes: 54 additions & 0 deletions src/style-spec/expression/definitions/format_section_override.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @flow

import assert from 'assert';
import type { Expression } from '../expression';
import type EvaluationContext from '../evaluation_context';
import type { Value } from '../values';
import type { Type } from '../types';
import type { ZoomConstantExpression } from '../../expression';
import { NullType } from '../types';
import { PossiblyEvaluatedPropertyValue } from '../../../style/properties';
import { register } from '../../../util/web_worker_transfer';

export default class FormatSectionOverride<T> implements Expression {
type: Type;
defaultValue: PossiblyEvaluatedPropertyValue<T>;

constructor(defaultValue: PossiblyEvaluatedPropertyValue<T>) {
assert(defaultValue.property.overrides !== undefined);
this.type = defaultValue.property.overrides ? defaultValue.property.overrides.runtimeType : NullType;
this.defaultValue = defaultValue;
}

evaluate(ctx: EvaluationContext) {
if (ctx.formattedSection) {
const overrides = this.defaultValue.property.overrides;
if (overrides && overrides.hasOverride(ctx.formattedSection)) {
return overrides.getOverride(ctx.formattedSection);
}
}

if (ctx.feature && ctx.featureState) {
return this.defaultValue.evaluate(ctx.feature, ctx.featureState);
}
return null;
}

eachChild(fn: (Expression) => void) {
if (!this.defaultValue.isConstant()) {
const expr: ZoomConstantExpression<'source'> = ((this.defaultValue.value): any);
fn(expr._styleExpression.expression);
}
}

// Cannot be statically evaluated, as the output depends on the evaluation context.
possibleOutputs(): Array<Value | void> {
return [undefined];
}

serialize() {
return null;
}
}

register('FormatSectionOverride', FormatSectionOverride, {omit: ['defaultValue']});
14 changes: 7 additions & 7 deletions src/style-spec/expression/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,12 @@ export class ZoomDependentExpression<Kind: EvaluationKind> {
_styleExpression: StyleExpression;
_interpolationType: ?InterpolationType;

constructor(kind: Kind, expression: StyleExpression, zoomCurve: Step | Interpolate) {
constructor(kind: Kind, expression: StyleExpression, zoomStops: Array<number>, interpolationType?: InterpolationType) {
this.kind = kind;
this.zoomStops = zoomCurve.labels;
this.zoomStops = zoomStops;
this._styleExpression = expression;
this.isStateDependent = kind !== ('camera': EvaluationKind) && !isConstant.isStateConstant(expression.expression);
if (zoomCurve instanceof Interpolate) {
this._interpolationType = zoomCurve.interpolation;
}
this._interpolationType = interpolationType;
}

evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, formattedSection?: FormattedSection): any {
Expand Down Expand Up @@ -244,9 +242,11 @@ export function createPropertyExpression(expression: mixed, propertySpec: StyleP
(new ZoomConstantExpression('source', expression.value): SourceExpression));
}

const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;

return success(isFeatureConstant ?
(new ZoomDependentExpression('camera', expression.value, zoomCurve): CameraExpression) :
(new ZoomDependentExpression('composite', expression.value, zoomCurve): CompositeExpression));
(new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType): CameraExpression) :
(new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType): CompositeExpression));
}

import { isFunction, createFunction } from '../function';
Expand Down
1 change: 1 addition & 0 deletions src/style-spec/reference/v8.json
Original file line number Diff line number Diff line change
Expand Up @@ -5033,6 +5033,7 @@
"doc": "The color with which the text will be drawn.",
"default": "#000000",
"transition": true,
"overridable": true,
"requires": [
"text-field"
],
Expand Down
3 changes: 2 additions & 1 deletion src/style-spec/style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export type StylePropertySpecification = {
'property-type': ExpressionType,
expression?: ExpressionSpecification,
transition: boolean,
default?: string
default?: string,
overridable: boolean
} | {
type: 'array',
value: 'number',
Expand Down
9 changes: 8 additions & 1 deletion src/style/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,11 @@ export class DataConstantProperty<T> implements Property<T, T> {
*/
export class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPropertyValue<T>> {
specification: StylePropertySpecification;
overrides: ?Object;

constructor(specification: StylePropertySpecification) {
constructor(specification: StylePropertySpecification, overrides?: Object) {
this.specification = specification;
this.overrides = overrides;
}

possiblyEvaluate(value: PropertyValue<T, PossiblyEvaluatedPropertyValue<T>>, parameters: EvaluationParameters): PossiblyEvaluatedPropertyValue<T> {
Expand Down Expand Up @@ -716,16 +718,21 @@ export class Properties<Props: Object> {
defaultTransitionablePropertyValues: TransitionablePropertyValues<Props>;
defaultTransitioningPropertyValues: TransitioningPropertyValues<Props>;
defaultPossiblyEvaluatedValues: PossiblyEvaluatedPropertyValues<Props>;
overridableProperties: Array<string>;

constructor(properties: Props) {
this.properties = properties;
this.defaultPropertyValues = ({}: any);
this.defaultTransitionablePropertyValues = ({}: any);
this.defaultTransitioningPropertyValues = ({}: any);
this.defaultPossiblyEvaluatedValues = ({}: any);
this.overridableProperties = ([]: any);

for (const property in properties) {
const prop = properties[property];
if (prop.specification.overridable) {
this.overridableProperties.push(property);
}
const defaultPropertyValue = this.defaultPropertyValues[property] =
new PropertyValue(prop, undefined);
const defaultTransitionablePropertyValue = this.defaultTransitionablePropertyValues[property] =
Expand Down
16 changes: 12 additions & 4 deletions src/style/style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { FeatureState } from '../style-spec/expression';
import type {Bucket} from '../data/bucket';
import type Point from '@mapbox/point-geometry';
import type {FeatureFilter} from '../style-spec/feature_filter';
import type {TransitionParameters} from './properties';
import type {TransitionParameters, PropertyValue} from './properties';
import type EvaluationParameters, {CrossfadeParameters} from './evaluation_parameters';
import type Transform from '../geo/transform';
import type {
Expand Down Expand Up @@ -157,18 +157,26 @@ class StyleLayer extends Evented {
const prop = this._transitionablePaint._values[name];
const newCrossFadedValue = prop.property.specification["property-type"] === 'cross-faded-data-driven' && !prop.value.value && value;

const wasDataDriven = this._transitionablePaint._values[name].value.isDataDriven();
const oldValue = this._transitionablePaint._values[name].value;
const wasDataDriven = oldValue.isDataDriven();
this._transitionablePaint.setValue(name, value);
const isDataDriven = this._transitionablePaint._values[name].value.isDataDriven();
const newValue = this._transitionablePaint._values[name].value;
const isDataDriven = newValue.isDataDriven();
this._handleSpecialPaintPropertyUpdate(name);
return isDataDriven || wasDataDriven || newCrossFadedValue;
return isDataDriven || wasDataDriven || newCrossFadedValue || this._handleOverridablePaintPropertyUpdate(name, oldValue, newValue);
}
}

_handleSpecialPaintPropertyUpdate(_: string) {
// No-op; can be overridden by derived classes.
}

// eslint-disable-next-line no-unused-vars
_handleOverridablePaintPropertyUpdate<T, R>(name: string, oldValue: PropertyValue<T, R>, newValue: PropertyValue<T, R>): boolean {
// No-op; can be overridden by derived classes.
return false;
}

isHidden(zoom: number) {
if (this.minzoom && zoom < this.minzoom) return true;
if (this.maxzoom && zoom >= this.maxzoom) return true;
Expand Down
9 changes: 9 additions & 0 deletions src/style/style_layer/layer_properties.js.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ import {
import type Color from '../../style-spec/util/color';

import type Formatted from '../../style-spec/expression/types/formatted';
<%
const overridables = paintProperties.filter(p => p.overridable)
if (overridables.length) { -%>
import {
<%= overridables.reduce((imports, prop) => { imports.push(runtimeType(prop)); return imports; }, []).join(',\n\t'); -%>
} from '../../style-spec/expression/types';
<% } -%>

<% if (layoutProperties.length) { -%>
export type LayoutProps = {|
Expand Down
85 changes: 81 additions & 4 deletions src/style/style_layer/symbol_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,35 @@

import StyleLayer from '../style_layer';

import assert from 'assert';
import SymbolBucket from '../../data/bucket/symbol_bucket';
import resolveTokens from '../../util/token';
import { isExpression } from '../../style-spec/expression';
import assert from 'assert';
import properties from './symbol_style_layer_properties';
import { Transitionable, Transitioning, Layout, PossiblyEvaluated } from '../properties';

import {
Transitionable,
Transitioning,
Layout,
PossiblyEvaluated,
PossiblyEvaluatedPropertyValue,
PropertyValue
} from '../properties';

import {
isExpression,
StyleExpression,
ZoomConstantExpression,
ZoomDependentExpression
} from '../../style-spec/expression';

import type {BucketParameters} from '../../data/bucket';
import type {LayoutProps, PaintProps} from './symbol_style_layer_properties';
import type {Feature} from '../../style-spec/expression';
import type EvaluationParameters from '../evaluation_parameters';
import type {LayerSpecification} from '../../style-spec/types';
import type { Feature, SourceExpression, CompositeExpression } from '../../style-spec/expression';
import Formatted from '../../style-spec/expression/types/formatted';
import FormatSectionOverride from '../../style-spec/expression/definitions/format_section_override';
import FormatExpression from '../../style-spec/expression/definitions/format';

class SymbolStyleLayer extends StyleLayer {
_unevaluatedLayout: Layout<LayoutProps>;
Expand Down Expand Up @@ -77,6 +94,66 @@ class SymbolStyleLayer extends StyleLayer {
assert(false); // Should take a different path in FeatureIndex
return false;
}

setPaintOverrides(layout: PossiblyEvaluated<LayoutProps>) {
for (const overridable of properties.paint.overridableProperties) {
if (!SymbolStyleLayer.hasPaintOverride(layout, overridable)) {
continue;
}
const overriden = this.paint.get(overridable);
const override = new FormatSectionOverride(overriden);
const styleExpression = new StyleExpression(override, overriden.property.specification);
let expression = null;
if (overriden.value.kind === 'constant' || overriden.value.kind === 'source') {
expression = (new ZoomConstantExpression('source', styleExpression): SourceExpression);
} else {
expression = (new ZoomDependentExpression('composite',
styleExpression,
overriden.value.zoomStops,
overriden.value._interpolationType): CompositeExpression);
}
this.paint._values[overridable] = new PossiblyEvaluatedPropertyValue(overriden.property,
expression,
overriden.parameters);
}
}

_handleOverridablePaintPropertyUpdate<T, R>(name: string, oldValue: PropertyValue<T, R>, newValue: PropertyValue<T, R>): boolean {
if (!this.layout || oldValue.isDataDriven() || newValue.isDataDriven()) {
return false;
}
return SymbolStyleLayer.hasPaintOverride(this.layout, name);
}

static hasPaintOverride(layout: PossiblyEvaluated<LayoutProps>, propertyName: string): boolean {
const textField = layout.get('text-field');
let sections: any = [];
if (textField.value.kind === 'constant' && textField.value.value instanceof Formatted) {
sections = textField.value.value.sections;
} else if (textField.value.kind === 'source') {
const expr: ZoomConstantExpression<'source'> = ((textField.value): any);
if (expr._styleExpression && expr._styleExpression.expression instanceof FormatExpression) {
sections = expr._styleExpression.expression.sections;
}
}

const property = properties.paint.properties[propertyName];
for (const section of sections) {
if (property.overrides && property.overrides.hasOverride(section)) {
return true;
}
}
return false;
}

static hasPaintOverrides(layout: PossiblyEvaluated<LayoutProps>): boolean {
for (const overridable of properties.paint.overridableProperties) {
if (SymbolStyleLayer.hasPaintOverride(layout, overridable)) {
return true;
}
}
return false;
}
}

export default SymbolStyleLayer;
Loading

0 comments on commit 65bb6c9

Please sign in to comment.