package.src.style.style_layer.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mapbox-gl Show documentation
Show all versions of mapbox-gl Show documentation
A WebGL interactive maps library
The newest version!
// @flow
import {endsWith, filterObject} from '../util/util';
import styleSpec from '../style-spec/reference/latest';
import {
validateStyle,
validateLayoutProperty,
validatePaintProperty,
emitValidationErrors
} from './validate_style';
import {Evented} from '../util/evented';
import {Layout, Transitionable, Transitioning, Properties, PossiblyEvaluated, PossiblyEvaluatedPropertyValue} from './properties';
import {supportsPropertyExpression} from '../style-spec/util/properties';
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, PropertyValue} from './properties';
import type EvaluationParameters, {CrossfadeParameters} from './evaluation_parameters';
import type Transform from '../geo/transform';
import type {
LayerSpecification,
FilterSpecification
} from '../style-spec/types';
import type {CustomLayerInterface} from './style_layer/custom_style_layer';
import type Map from '../ui/map';
import type {StyleSetterOptions} from './style';
const TRANSITION_SUFFIX = '-transition';
class StyleLayer extends Evented {
id: string;
metadata: mixed;
type: string;
source: string;
sourceLayer: ?string;
minzoom: ?number;
maxzoom: ?number;
filter: FilterSpecification | void;
visibility: 'visible' | 'none' | void;
_crossfadeParameters: CrossfadeParameters;
_unevaluatedLayout: Layout;
+layout: mixed;
_transitionablePaint: Transitionable;
_transitioningPaint: Transitioning;
+paint: mixed;
_featureFilter: FeatureFilter;
+queryRadius: (bucket: Bucket) => number;
+queryIntersectsFeature: (queryGeometry: Array,
feature: VectorTileFeature,
featureState: FeatureState,
geometry: Array>,
zoom: number,
transform: Transform,
pixelsToTileUnits: number,
pixelPosMatrix: Float32Array) => boolean | number;
+onAdd: ?(map: Map) => void;
+onRemove: ?(map: Map) => void;
constructor(layer: LayerSpecification | CustomLayerInterface, properties: $ReadOnly<{layout?: Properties<*>, paint?: Properties<*>}>) {
super();
this.id = layer.id;
this.type = layer.type;
this._featureFilter = {filter: () => true, needGeometry: false};
if (layer.type === 'custom') return;
layer = ((layer: any): LayerSpecification);
this.metadata = layer.metadata;
this.minzoom = layer.minzoom;
this.maxzoom = layer.maxzoom;
if (layer.type !== 'background') {
this.source = layer.source;
this.sourceLayer = layer['source-layer'];
this.filter = layer.filter;
}
if (properties.layout) {
this._unevaluatedLayout = new Layout(properties.layout);
}
if (properties.paint) {
this._transitionablePaint = new Transitionable(properties.paint);
for (const property in layer.paint) {
this.setPaintProperty(property, layer.paint[property], {validate: false});
}
for (const property in layer.layout) {
this.setLayoutProperty(property, layer.layout[property], {validate: false});
}
this._transitioningPaint = this._transitionablePaint.untransitioned();
//$FlowFixMe
this.paint = new PossiblyEvaluated(properties.paint);
}
}
getCrossfadeParameters() {
return this._crossfadeParameters;
}
getLayoutProperty(name: string) {
if (name === 'visibility') {
return this.visibility;
}
return this._unevaluatedLayout.getValue(name);
}
setLayoutProperty(name: string, value: any, options: StyleSetterOptions = {}) {
if (value !== null && value !== undefined) {
const key = `layers.${this.id}.layout.${name}`;
if (this._validate(validateLayoutProperty, key, name, value, options)) {
return;
}
}
if (name === 'visibility') {
this.visibility = value;
return;
}
this._unevaluatedLayout.setValue(name, value);
}
getPaintProperty(name: string) {
if (endsWith(name, TRANSITION_SUFFIX)) {
return this._transitionablePaint.getTransition(name.slice(0, -TRANSITION_SUFFIX.length));
} else {
return this._transitionablePaint.getValue(name);
}
}
setPaintProperty(name: string, value: mixed, options: StyleSetterOptions = {}) {
if (value !== null && value !== undefined) {
const key = `layers.${this.id}.paint.${name}`;
if (this._validate(validatePaintProperty, key, name, value, options)) {
return false;
}
}
if (endsWith(name, TRANSITION_SUFFIX)) {
this._transitionablePaint.setTransition(name.slice(0, -TRANSITION_SUFFIX.length), (value: any) || undefined);
return false;
} else {
const transitionable = this._transitionablePaint._values[name];
const isCrossFadedProperty = transitionable.property.specification["property-type"] === 'cross-faded-data-driven';
const wasDataDriven = transitionable.value.isDataDriven();
const oldValue = transitionable.value;
this._transitionablePaint.setValue(name, value);
this._handleSpecialPaintPropertyUpdate(name);
const newValue = this._transitionablePaint._values[name].value;
const isDataDriven = newValue.isDataDriven();
// if a cross-faded value is changed, we need to make sure the new icons get added to each tile's iconAtlas
// so a call to _updateLayer is necessary, and we return true from this function so it gets called in
// Style#setPaintProperty
return isDataDriven || wasDataDriven || isCrossFadedProperty || this._handleOverridablePaintPropertyUpdate(name, oldValue, newValue);
}
}
_handleSpecialPaintPropertyUpdate(_: string) {
// No-op; can be overridden by derived classes.
}
// eslint-disable-next-line no-unused-vars
_handleOverridablePaintPropertyUpdate(name: string, oldValue: PropertyValue, newValue: PropertyValue): 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;
return this.visibility === 'none';
}
updateTransitions(parameters: TransitionParameters) {
this._transitioningPaint = this._transitionablePaint.transitioned(parameters, this._transitioningPaint);
}
hasTransition() {
return this._transitioningPaint.hasTransition();
}
recalculate(parameters: EvaluationParameters, availableImages: Array) {
if (parameters.getCrossfadeParameters) {
this._crossfadeParameters = parameters.getCrossfadeParameters();
}
if (this._unevaluatedLayout) {
(this: any).layout = this._unevaluatedLayout.possiblyEvaluate(parameters, undefined, availableImages);
}
(this: any).paint = this._transitioningPaint.possiblyEvaluate(parameters, undefined, availableImages);
}
serialize() {
const output: any = {
'id': this.id,
'type': this.type,
'source': this.source,
'source-layer': this.sourceLayer,
'metadata': this.metadata,
'minzoom': this.minzoom,
'maxzoom': this.maxzoom,
'filter': this.filter,
'layout': this._unevaluatedLayout && this._unevaluatedLayout.serialize(),
'paint': this._transitionablePaint && this._transitionablePaint.serialize()
};
if (this.visibility) {
output.layout = output.layout || {};
output.layout.visibility = this.visibility;
}
return filterObject(output, (value, key) => {
return value !== undefined &&
!(key === 'layout' && !Object.keys(value).length) &&
!(key === 'paint' && !Object.keys(value).length);
});
}
_validate(validate: Function, key: string, name: string, value: mixed, options: StyleSetterOptions = {}) {
if (options && options.validate === false) {
return false;
}
return emitValidationErrors(this, validate.call(validateStyle, {
key,
layerType: this.type,
objectKey: name,
value,
styleSpec,
// Workaround for https://github.com/mapbox/mapbox-gl-js/issues/2407
style: {glyphs: true, sprite: true}
}));
}
is3D() {
return false;
}
isTileClipped() {
return false;
}
hasOffscreenPass() {
return false;
}
resize() {
// noop
}
isStateDependent() {
for (const property in (this: any).paint._values) {
const value = (this: any).paint.get(property);
if (!(value instanceof PossiblyEvaluatedPropertyValue) || !supportsPropertyExpression(value.property.specification)) {
continue;
}
if ((value.value.kind === 'source' || value.value.kind === 'composite') &&
value.value.isStateDependent) {
return true;
}
}
return false;
}
}
export default StyleLayer;