maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
223 lines (187 loc) • 9.63 kB
text/typescript
import StyleLayer from '../style_layer';
import assert from 'assert';
import SymbolBucket, {SymbolFeature} from '../../data/bucket/symbol_bucket';
import resolveTokens from '../../util/resolve_tokens';
import properties, {SymbolLayoutPropsPossiblyEvaluated, SymbolPaintPropsPossiblyEvaluated} from './symbol_style_layer_properties.g';
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 {SymbolLayoutProps, SymbolPaintProps} from './symbol_style_layer_properties.g';
import type EvaluationParameters from '../evaluation_parameters';
import type {LayerSpecification} from '../../style-spec/types.g';
import type {Feature, SourceExpression, CompositeExpression} from '../../style-spec/expression';
import type {Expression} from '../../style-spec/expression/expression';
import type {CanonicalTileID} from '../../source/tile_id';
import {FormattedType} from '../../style-spec/expression/types';
import {typeOf} from '../../style-spec/expression/values';
import Formatted from '../../style-spec/expression/types/formatted';
import FormatSectionOverride from '../format_section_override';
import FormatExpression from '../../style-spec/expression/definitions/format';
import Literal from '../../style-spec/expression/definitions/literal';
class SymbolStyleLayer extends StyleLayer {
_unevaluatedLayout: Layout<SymbolLayoutProps>;
layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>;
_transitionablePaint: Transitionable<SymbolPaintProps>;
_transitioningPaint: Transitioning<SymbolPaintProps>;
paint: PossiblyEvaluated<SymbolPaintProps, SymbolPaintPropsPossiblyEvaluated>;
constructor(layer: LayerSpecification) {
super(layer, properties);
}
recalculate(parameters: EvaluationParameters, availableImages: Array<string>) {
super.recalculate(parameters, availableImages);
if (this.layout.get('icon-rotation-alignment') === 'auto') {
if (this.layout.get('symbol-placement') !== 'point') {
this.layout._values['icon-rotation-alignment'] = 'map';
} else {
this.layout._values['icon-rotation-alignment'] = 'viewport';
}
}
if (this.layout.get('text-rotation-alignment') === 'auto') {
if (this.layout.get('symbol-placement') !== 'point') {
this.layout._values['text-rotation-alignment'] = 'map';
} else {
this.layout._values['text-rotation-alignment'] = 'viewport';
}
}
// If unspecified, `*-pitch-alignment` inherits `*-rotation-alignment`
if (this.layout.get('text-pitch-alignment') === 'auto') {
this.layout._values['text-pitch-alignment'] = this.layout.get('text-rotation-alignment') === 'map' ? 'map' : 'viewport';
}
if (this.layout.get('icon-pitch-alignment') === 'auto') {
this.layout._values['icon-pitch-alignment'] = this.layout.get('icon-rotation-alignment');
}
if (this.layout.get('symbol-placement') === 'point') {
const writingModes = this.layout.get('text-writing-mode');
if (writingModes) {
// remove duplicates, preserving order
const deduped = [];
for (const m of writingModes) {
if (deduped.indexOf(m) < 0) deduped.push(m);
}
this.layout._values['text-writing-mode'] = deduped;
} else {
this.layout._values['text-writing-mode'] = ['horizontal'];
}
}
this._setPaintOverrides();
}
getValueAndResolveTokens(name: any, feature: Feature, canonical: CanonicalTileID, availableImages: Array<string>) {
const value = this.layout.get(name).evaluate(feature, {}, canonical, availableImages);
const unevaluated = this._unevaluatedLayout._values[name];
if (!unevaluated.isDataDriven() && !isExpression(unevaluated.value) && value) {
return resolveTokens(feature.properties, value);
}
return value;
}
createBucket(parameters: BucketParameters<any>) {
return new SymbolBucket(parameters);
}
queryRadius(): number {
return 0;
}
queryIntersectsFeature(): boolean {
assert(false); // Should take a different path in FeatureIndex
return false;
}
_setPaintOverrides() {
for (const overridable of properties.paint.overridableProperties) {
if (!SymbolStyleLayer.hasPaintOverride(this.layout, overridable)) {
continue;
}
const overriden = this.paint.get(overridable as keyof SymbolPaintPropsPossiblyEvaluated) as PossiblyEvaluatedPropertyValue<number>;
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) as SourceExpression);
} else {
expression = (new ZoomDependentExpression('composite',
styleExpression,
overriden.value.zoomStops,
(overriden.value as any)._interpolationType) as 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<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>, propertyName: string): boolean {
const textField = layout.get('text-field');
const property = properties.paint.properties[propertyName];
let hasOverrides = false;
const checkSections = (sections) => {
for (const section of sections) {
if (property.overrides && property.overrides.hasOverride(section)) {
hasOverrides = true;
return;
}
}
};
if (textField.value.kind === 'constant' && textField.value.value instanceof Formatted) {
checkSections(textField.value.value.sections);
} else if (textField.value.kind === 'source') {
const checkExpression = (expression: Expression) => {
if (hasOverrides) return;
if (expression instanceof Literal && typeOf(expression.value) === FormattedType) {
const formatted: Formatted = (expression.value as any);
checkSections(formatted.sections);
} else if (expression instanceof FormatExpression) {
checkSections(expression.sections);
} else {
expression.eachChild(checkExpression);
}
};
const expr: ZoomConstantExpression<'source'> = (textField.value as any);
if (expr._styleExpression) {
checkExpression(expr._styleExpression.expression);
}
}
return hasOverrides;
}
}
export type OverlapMode = 'never' | 'always' | 'cooperative';
export function getOverlapMode(layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>, overlapProp: 'icon-overlap', allowOverlapProp: 'icon-allow-overlap'): OverlapMode;
export function getOverlapMode(layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>, overlapProp: 'text-overlap', allowOverlapProp: 'text-allow-overlap'): OverlapMode;
export function getOverlapMode(layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>, overlapProp: 'icon-overlap' | 'text-overlap', allowOverlapProp: 'icon-allow-overlap' | 'text-allow-overlap'): OverlapMode {
let result: OverlapMode = 'never';
const overlap = layout.get(overlapProp);
if (overlap) {
// if -overlap is set, use it
result = overlap;
} else if (layout.get(allowOverlapProp)) {
// fall back to -allow-overlap, with false='never', true='always'
result = 'always';
}
return result;
}
export type SymbolPadding = [number, number, number, number];
export function getIconPadding(layout: PossiblyEvaluated<SymbolLayoutProps, SymbolLayoutPropsPossiblyEvaluated>, feature: SymbolFeature, canonical: CanonicalTileID, pixelRatio = 1): SymbolPadding {
// Support text-padding in addition to icon-padding? Unclear how to apply asymmetric text-padding to the radius for collision circles.
const result = layout.get('icon-padding').evaluate(feature, {}, canonical);
const values = result && result.values;
return [
values[0] * pixelRatio,
values[1] * pixelRatio,
values[2] * pixelRatio,
values[3] * pixelRatio,
];
}
export default SymbolStyleLayer;