UNPKG

31.7 kBPlain TextView Raw
1import assert from 'assert';
2import {clone, extend, easeCubicInOut} from '../util/util';
3import * as interpolate from '../style-spec/util/interpolate';
4import Color from '../style-spec/util/color';
5import {register} from '../util/web_worker_transfer';
6import EvaluationParameters from './evaluation_parameters';
7
8import {CanonicalTileID} from '../source/tile_id';
9import {StylePropertySpecification} from '../style-spec/style-spec';
10import {
11 TransitionSpecification,
12 PropertyValueSpecification
13} from '../style-spec/types.g';
14
15import {
16 normalizePropertyExpression,
17 Feature,
18 FeatureState,
19 StylePropertyExpression,
20 SourceExpression,
21 CompositeExpression
22} from '../style-spec/expression';
23
24type TimePoint = number;
25
26export type CrossFaded<T> = {
27 to: T;
28 from: T;
29};
30
31/**
32 * Implements a number of classes that define state and behavior for paint and layout properties, most
33 * importantly their respective evaluation chains:
34 *
35 * Transitionable paint property value
36 * → Transitioning paint property value
37 * → Possibly evaluated paint property value
38 * → Fully evaluated paint property value
39 *
40 * Layout property value
41 * → Possibly evaluated layout property value
42 * → Fully evaluated layout property value
43 *
44 * @module
45 * @private
46 */
47
48/**
49 * Implementations of the `Property` interface:
50 *
51 * * Hold metadata about a property that's independent of any specific value: stuff like the type of the value,
52 * the default value, etc. This comes from the style specification JSON.
53 * * Define behavior that needs to be polymorphic across different properties: "possibly evaluating"
54 * an input value (see below), and interpolating between two possibly-evaluted values.
55 *
56 * The type `T` is the fully-evaluated value type (e.g. `number`, `string`, `Color`).
57 * The type `R` is the intermediate "possibly evaluated" value type. See below.
58 *
59 * There are two main implementations of the interface -- one for properties that allow data-driven values,
60 * and one for properties that don't. There are a few "special case" implementations as well: one for properties
61 * which cross-fade between two values rather than interpolating, one for `heatmap-color` and `line-gradient`,
62 * and one for `light-position`.
63 *
64 * @private
65 */
66export interface Property<T, R> {
67 specification: StylePropertySpecification;
68 possiblyEvaluate(
69 value: PropertyValue<T, R>,
70 parameters: EvaluationParameters,
71 canonical?: CanonicalTileID,
72 availableImages?: Array<string>
73 ): R;
74 interpolate(a: R, b: R, t: number): R;
75}
76
77/**
78 * `PropertyValue` represents the value part of a property key-value unit. It's used to represent both
79 * paint and layout property values, and regardless of whether or not their property supports data-driven
80 * expressions.
81 *
82 * `PropertyValue` stores the raw input value as seen in a style or a runtime styling API call, i.e. one of the
83 * following:
84 *
85 * * A constant value of the type appropriate for the property
86 * * A function which produces a value of that type (but functions are quasi-deprecated in favor of expressions)
87 * * An expression which produces a value of that type
88 * * "undefined"/"not present", in which case the property is assumed to take on its default value.
89 *
90 * In addition to storing the original input value, `PropertyValue` also stores a normalized representation,
91 * effectively treating functions as if they are expressions, and constant or default values as if they are
92 * (constant) expressions.
93 *
94 * @private
95 */
96export class PropertyValue<T, R> {
97 property: Property<T, R>;
98 value: PropertyValueSpecification<T> | void;
99 expression: StylePropertyExpression;
100
101 constructor(property: Property<T, R>, value: PropertyValueSpecification<T> | void) {
102 this.property = property;
103 this.value = value;
104 this.expression = normalizePropertyExpression(value === undefined ? property.specification.default : value, property.specification);
105 }
106
107 isDataDriven(): boolean {
108 return this.expression.kind === 'source' || this.expression.kind === 'composite';
109 }
110
111 possiblyEvaluate(
112 parameters: EvaluationParameters,
113 canonical?: CanonicalTileID,
114 availableImages?: Array<string>
115 ): R {
116 return this.property.possiblyEvaluate(this, parameters, canonical, availableImages);
117 }
118}
119
120// ------- Transitionable -------
121
122export type TransitionParameters = {
123 now: TimePoint;
124 transition: TransitionSpecification;
125};
126
127/**
128 * Paint properties are _transitionable_: they can change in a fluid manner, interpolating or cross-fading between
129 * old and new value. The duration of the transition, and the delay before it begins, is configurable.
130 *
131 * `TransitionablePropertyValue` is a compositional class that stores both the property value and that transition
132 * configuration.
133 *
134 * A `TransitionablePropertyValue` can calculate the next step in the evaluation chain for paint property values:
135 * `TransitioningPropertyValue`.
136 *
137 * @private
138 */
139class TransitionablePropertyValue<T, R> {
140 property: Property<T, R>;
141 value: PropertyValue<T, R>;
142 transition: TransitionSpecification | void;
143
144 constructor(property: Property<T, R>) {
145 this.property = property;
146 this.value = new PropertyValue(property, undefined);
147 }
148
149 transitioned(parameters: TransitionParameters, prior: TransitioningPropertyValue<T, R>): TransitioningPropertyValue<T, R> {
150 return new TransitioningPropertyValue(this.property, this.value, prior, // eslint-disable-line no-use-before-define
151 extend({}, parameters.transition, this.transition), parameters.now);
152 }
153
154 untransitioned(): TransitioningPropertyValue<T, R> {
155 return new TransitioningPropertyValue(this.property, this.value, null, {}, 0); // eslint-disable-line no-use-before-define
156 }
157}
158
159/**
160 * `Transitionable` stores a map of all (property name, `TransitionablePropertyValue`) pairs for paint properties of a
161 * given layer type. It can calculate the `TransitioningPropertyValue`s for all of them at once, producing a
162 * `Transitioning` instance for the same set of properties.
163 *
164 * @private
165 */
166export class Transitionable<Props> {
167 _properties: Properties<Props>;
168 _values: {[K in keyof Props]: TransitionablePropertyValue<any, unknown>};
169
170 constructor(properties: Properties<Props>) {
171 this._properties = properties;
172 this._values = (Object.create(properties.defaultTransitionablePropertyValues) as any);
173 }
174
175 getValue<S extends keyof Props, T>(name: S): PropertyValueSpecification<T> | void {
176 return clone(this._values[name].value.value);
177 }
178
179 setValue<S extends keyof Props, T>(name: S, value: PropertyValueSpecification<T> | void) {
180 if (!Object.prototype.hasOwnProperty.call(this._values, name)) {
181 this._values[name] = new TransitionablePropertyValue(this._values[name].property);
182 }
183 // Note that we do not _remove_ an own property in the case where a value is being reset
184 // to the default: the transition might still be non-default.
185 this._values[name].value = new PropertyValue(this._values[name].property, value === null ? undefined : clone(value));
186 }
187
188 getTransition<S extends keyof Props>(name: S): TransitionSpecification | void {
189 return clone(this._values[name].transition);
190 }
191
192 setTransition<S extends keyof Props>(name: S, value: TransitionSpecification | void) {
193 if (!Object.prototype.hasOwnProperty.call(this._values, name)) {
194 this._values[name] = new TransitionablePropertyValue(this._values[name].property);
195 }
196 this._values[name].transition = clone(value) || undefined;
197 }
198
199 serialize() {
200 const result: any = {};
201 for (const property of Object.keys(this._values)) {
202 const value = this.getValue(property as keyof Props);
203 if (value !== undefined) {
204 result[property] = value;
205 }
206
207 const transition = this.getTransition(property as keyof Props);
208 if (transition !== undefined) {
209 result[`${property}-transition`] = transition;
210 }
211 }
212 return result;
213 }
214
215 transitioned(parameters: TransitionParameters, prior: Transitioning<Props>): Transitioning<Props> {
216 const result = new Transitioning(this._properties); // eslint-disable-line no-use-before-define
217 for (const property of Object.keys(this._values)) {
218 result._values[property] = this._values[property].transitioned(parameters, prior._values[property]);
219 }
220 return result;
221 }
222
223 untransitioned(): Transitioning<Props> {
224 const result = new Transitioning(this._properties); // eslint-disable-line no-use-before-define
225 for (const property of Object.keys(this._values)) {
226 result._values[property] = this._values[property].untransitioned();
227 }
228 return result;
229 }
230}
231
232// ------- Transitioning -------
233
234/**
235 * `TransitioningPropertyValue` implements the first of two intermediate steps in the evaluation chain of a paint
236 * property value. In this step, transitions between old and new values are handled: as long as the transition is in
237 * progress, `TransitioningPropertyValue` maintains a reference to the prior value, and interpolates between it and
238 * the new value based on the current time and the configured transition duration and delay. The product is the next
239 * step in the evaluation chain: the "possibly evaluated" result type `R`. See below for more on this concept.
240 *
241 * @private
242 */
243class TransitioningPropertyValue<T, R> {
244 property: Property<T, R>;
245 value: PropertyValue<T, R>;
246 prior: TransitioningPropertyValue<T, R>;
247 begin: TimePoint;
248 end: TimePoint;
249
250 constructor(property: Property<T, R>,
251 value: PropertyValue<T, R>,
252 prior: TransitioningPropertyValue<T, R>,
253 transition: TransitionSpecification,
254 now: TimePoint) {
255 this.property = property;
256 this.value = value;
257 this.begin = now + transition.delay || 0;
258 this.end = this.begin + transition.duration || 0;
259 if (property.specification.transition && (transition.delay || transition.duration)) {
260 this.prior = prior;
261 }
262 }
263
264 possiblyEvaluate(
265 parameters: EvaluationParameters,
266 canonical: CanonicalTileID,
267 availableImages: Array<string>
268 ): R {
269 const now = parameters.now || 0;
270 const finalValue = this.value.possiblyEvaluate(parameters, canonical, availableImages);
271 const prior = this.prior;
272 if (!prior) {
273 // No prior value.
274 return finalValue;
275 } else if (now > this.end) {
276 // Transition from prior value is now complete.
277 this.prior = null;
278 return finalValue;
279 } else if (this.value.isDataDriven()) {
280 // Transitions to data-driven properties are not supported.
281 // We snap immediately to the data-driven value so that, when we perform layout,
282 // we see the data-driven function and can use it to populate vertex buffers.
283 this.prior = null;
284 return finalValue;
285 } else if (now < this.begin) {
286 // Transition hasn't started yet.
287 return prior.possiblyEvaluate(parameters, canonical, availableImages);
288 } else {
289 // Interpolate between recursively-calculated prior value and final.
290 const t = (now - this.begin) / (this.end - this.begin);
291 return this.property.interpolate(prior.possiblyEvaluate(parameters, canonical, availableImages), finalValue, easeCubicInOut(t));
292 }
293 }
294}
295
296/**
297 * `Transitioning` stores a map of all (property name, `TransitioningPropertyValue`) pairs for paint properties of a
298 * given layer type. It can calculate the possibly-evaluated values for all of them at once, producing a
299 * `PossiblyEvaluated` instance for the same set of properties.
300 *
301 * @private
302 */
303export class Transitioning<Props> {
304 _properties: Properties<Props>;
305 _values: {[K in keyof Props]: PossiblyEvaluatedPropertyValue<unknown>};
306
307 constructor(properties: Properties<Props>) {
308 this._properties = properties;
309 this._values = (Object.create(properties.defaultTransitioningPropertyValues) as any);
310 }
311
312 possiblyEvaluate(
313 parameters: EvaluationParameters,
314 canonical?: CanonicalTileID,
315 availableImages?: Array<string>
316 ): PossiblyEvaluated<Props, any> {
317 const result = new PossiblyEvaluated(this._properties); // eslint-disable-line no-use-before-define
318 for (const property of Object.keys(this._values)) {
319 result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages);
320 }
321 return result;
322 }
323
324 hasTransition() {
325 for (const property of Object.keys(this._values)) {
326 if (this._values[property].prior) {
327 return true;
328 }
329 }
330 return false;
331 }
332}
333
334// ------- Layout -------
335
336/**
337 * Because layout properties are not transitionable, they have a simpler representation and evaluation chain than
338 * paint properties: `PropertyValue`s are possibly evaluated, producing possibly evaluated values, which are then
339 * fully evaluated.
340 *
341 * `Layout` stores a map of all (property name, `PropertyValue`) pairs for layout properties of a
342 * given layer type. It can calculate the possibly-evaluated values for all of them at once, producing a
343 * `PossiblyEvaluated` instance for the same set of properties.
344 *
345 * @private
346 */
347export class Layout<Props> {
348 _properties: Properties<Props>;
349 _values: {[K in keyof Props]: PropertyValue<any, PossiblyEvaluatedPropertyValue<any>>};
350
351 constructor(properties: Properties<Props>) {
352 this._properties = properties;
353 this._values = (Object.create(properties.defaultPropertyValues) as any);
354 }
355
356 getValue<S extends keyof Props>(name: S) {
357 return clone(this._values[name].value);
358 }
359
360 setValue<S extends keyof Props>(name: S, value: any) {
361 this._values[name] = new PropertyValue(this._values[name].property, value === null ? undefined : clone(value)) as any;
362 }
363
364 serialize() {
365 const result: any = {};
366 for (const property of Object.keys(this._values)) {
367 const value = this.getValue(property as keyof Props);
368 if (value !== undefined) {
369 result[property] = value;
370 }
371 }
372 return result;
373 }
374
375 possiblyEvaluate(
376 parameters: EvaluationParameters,
377 canonical?: CanonicalTileID,
378 availableImages?: Array<string>
379 ): PossiblyEvaluated<Props, any> {
380 const result = new PossiblyEvaluated(this._properties); // eslint-disable-line no-use-before-define
381 for (const property of Object.keys(this._values)) {
382 result._values[property] = this._values[property].possiblyEvaluate(parameters, canonical, availableImages);
383 }
384 return result;
385 }
386}
387
388// ------- PossiblyEvaluated -------
389
390/**
391 * "Possibly evaluated value" is an intermediate stage in the evaluation chain for both paint and layout property
392 * values. The purpose of this stage is to optimize away unnecessary recalculations for data-driven properties. Code
393 * which uses data-driven property values must assume that the value is dependent on feature data, and request that it
394 * be evaluated for each feature. But when that property value is in fact a constant or camera function, the calculation
395 * will not actually depend on the feature, and we can benefit from returning the prior result of having done the
396 * evaluation once, ahead of time, in an intermediate step whose inputs are just the value and "global" parameters
397 * such as current zoom level.
398 *
399 * `PossiblyEvaluatedValue` represents the three possible outcomes of this step: if the input value was a constant or
400 * camera expression, then the "possibly evaluated" result is a constant value. Otherwise, the input value was either
401 * a source or composite expression, and we must defer final evaluation until supplied a feature. We separate
402 * the source and composite cases because they are handled differently when generating GL attributes, buffers, and
403 * uniforms.
404 *
405 * Note that `PossiblyEvaluatedValue` (and `PossiblyEvaluatedPropertyValue`, below) are _not_ used for properties that
406 * do not allow data-driven values. For such properties, we know that the "possibly evaluated" result is always a constant
407 * scalar value. See below.
408 *
409 * @private
410 */
411type PossiblyEvaluatedValue<T> = {
412 kind: 'constant';
413 value: T;
414} | SourceExpression | CompositeExpression;
415
416/**
417 * `PossiblyEvaluatedPropertyValue` is used for data-driven paint and layout property values. It holds a
418 * `PossiblyEvaluatedValue` and the `GlobalProperties` that were used to generate it. You're not allowed to supply
419 * a different set of `GlobalProperties` when performing the final evaluation because they would be ignored in the
420 * case where the input value was a constant or camera function.
421 *
422 * @private
423 */
424export class PossiblyEvaluatedPropertyValue<T> {
425 property: DataDrivenProperty<T>;
426 value: PossiblyEvaluatedValue<T>;
427 parameters: EvaluationParameters;
428
429 constructor(property: DataDrivenProperty<T>, value: PossiblyEvaluatedValue<T>, parameters: EvaluationParameters) {
430 this.property = property;
431 this.value = value;
432 this.parameters = parameters;
433 }
434
435 isConstant(): boolean {
436 return this.value.kind === 'constant';
437 }
438
439 constantOr(value: T): T {
440 if (this.value.kind === 'constant') {
441 return this.value.value;
442 } else {
443 return value;
444 }
445 }
446
447 evaluate(
448 feature: Feature,
449 featureState: FeatureState,
450 canonical?: CanonicalTileID,
451 availableImages?: Array<string>
452 ): T {
453 return this.property.evaluate(this.value, this.parameters, feature, featureState, canonical, availableImages);
454 }
455}
456
457/**
458 * `PossiblyEvaluated` stores a map of all (property name, `R`) pairs for paint or layout properties of a
459 * given layer type.
460 * @private
461 */
462export class PossiblyEvaluated<Props, PossibleEvaluatedProps> {
463 _properties: Properties<Props>;
464 _values: PossibleEvaluatedProps;
465
466 constructor(properties: Properties<Props>) {
467 this._properties = properties;
468 this._values = Object.create(properties.defaultPossiblyEvaluatedValues);
469 }
470
471 get<S extends keyof PossibleEvaluatedProps>(name: S): PossibleEvaluatedProps[S] {
472 return this._values[name];
473 }
474}
475
476/**
477 * An implementation of `Property` for properties that do not permit data-driven (source or composite) expressions.
478 * This restriction allows us to declare statically that the result of possibly evaluating this kind of property
479 * is in fact always the scalar type `T`, and can be used without further evaluating the value on a per-feature basis.
480 *
481 * @private
482 */
483export class DataConstantProperty<T> implements Property<T, T> {
484 specification: StylePropertySpecification;
485
486 constructor(specification: StylePropertySpecification) {
487 this.specification = specification;
488 }
489
490 possiblyEvaluate(value: PropertyValue<T, T>, parameters: EvaluationParameters): T {
491 assert(!value.isDataDriven());
492 return value.expression.evaluate(parameters);
493 }
494
495 interpolate(a: T, b: T, t: number): T {
496 const interp: ((a: T, b: T, t: number) => T) = (interpolate as any)[this.specification.type];
497 if (interp) {
498 return interp(a, b, t);
499 } else {
500 return a;
501 }
502 }
503}
504
505/**
506 * An implementation of `Property` for properties that permit data-driven (source or composite) expressions.
507 * The result of possibly evaluating this kind of property is `PossiblyEvaluatedPropertyValue<T>`; obtaining
508 * a scalar value `T` requires further evaluation on a per-feature basis.
509 *
510 * @private
511 */
512export class DataDrivenProperty<T> implements Property<T, PossiblyEvaluatedPropertyValue<T>> {
513 specification: StylePropertySpecification;
514 overrides: any;
515
516 constructor(specification: StylePropertySpecification, overrides?: any) {
517 this.specification = specification;
518 this.overrides = overrides;
519 }
520
521 possiblyEvaluate(
522 value: PropertyValue<T, PossiblyEvaluatedPropertyValue<T>>,
523 parameters: EvaluationParameters,
524 canonical?: CanonicalTileID,
525 availableImages?: Array<string>
526 ): PossiblyEvaluatedPropertyValue<T> {
527 if (value.expression.kind === 'constant' || value.expression.kind === 'camera') {
528 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: value.expression.evaluate(parameters, null, {}, canonical, availableImages)}, parameters);
529 } else {
530 return new PossiblyEvaluatedPropertyValue(this, value.expression, parameters);
531 }
532 }
533
534 interpolate(
535 a: PossiblyEvaluatedPropertyValue<T>,
536 b: PossiblyEvaluatedPropertyValue<T>,
537 t: number
538 ): PossiblyEvaluatedPropertyValue<T> {
539 // If either possibly-evaluated value is non-constant, give up: we aren't able to interpolate data-driven values.
540 if (a.value.kind !== 'constant' || b.value.kind !== 'constant') {
541 return a;
542 }
543
544 // Special case hack solely for fill-outline-color. The undefined value is subsequently handled in
545 // FillStyleLayer#recalculate, which sets fill-outline-color to the fill-color value if the former
546 // is a PossiblyEvaluatedPropertyValue containing a constant undefined value. In addition to the
547 // return value here, the other source of a PossiblyEvaluatedPropertyValue containing a constant
548 // undefined value is the "default value" for fill-outline-color held in
549 // `Properties#defaultPossiblyEvaluatedValues`, which serves as the prototype of
550 // `PossiblyEvaluated#_values`.
551 if (a.value.value === undefined || b.value.value === undefined) {
552 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: undefined}, a.parameters);
553 }
554
555 const interp: ((a: T, b: T, t: number) => T) = (interpolate as any)[this.specification.type];
556 if (interp) {
557 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: interp(a.value.value, b.value.value, t)}, a.parameters);
558 } else {
559 return a;
560 }
561 }
562
563 evaluate(
564 value: PossiblyEvaluatedValue<T>,
565 parameters: EvaluationParameters,
566 feature: Feature,
567 featureState: FeatureState,
568 canonical?: CanonicalTileID,
569 availableImages?: Array<string>
570 ): T {
571 if (value.kind === 'constant') {
572 return value.value;
573 } else {
574 return value.evaluate(parameters, feature, featureState, canonical, availableImages);
575 }
576 }
577}
578
579/**
580 * An implementation of `Property` for data driven `line-pattern` which are transitioned by cross-fading
581 * rather than interpolation.
582 *
583 * @private
584 */
585
586export class CrossFadedDataDrivenProperty<T> extends DataDrivenProperty<CrossFaded<T>> {
587
588 possiblyEvaluate(
589 value: PropertyValue<CrossFaded<T>, PossiblyEvaluatedPropertyValue<CrossFaded<T>>>,
590 parameters: EvaluationParameters,
591 canonical?: CanonicalTileID,
592 availableImages?: Array<string>
593 ): PossiblyEvaluatedPropertyValue<CrossFaded<T>> {
594 if (value.value === undefined) {
595 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: undefined}, parameters);
596 } else if (value.expression.kind === 'constant') {
597 const evaluatedValue = value.expression.evaluate(parameters, null, {}, canonical, availableImages);
598 const isImageExpression = value.property.specification.type as any === 'resolvedImage';
599 const constantValue = isImageExpression && typeof evaluatedValue !== 'string' ? evaluatedValue.name : evaluatedValue;
600 const constant = this._calculate(constantValue, constantValue, constantValue, parameters);
601 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: constant}, parameters);
602 } else if (value.expression.kind === 'camera') {
603 const cameraVal = this._calculate(
604 value.expression.evaluate({zoom: parameters.zoom - 1.0}),
605 value.expression.evaluate({zoom: parameters.zoom}),
606 value.expression.evaluate({zoom: parameters.zoom + 1.0}),
607 parameters);
608 return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: cameraVal}, parameters);
609 } else {
610 // source or composite expression
611 return new PossiblyEvaluatedPropertyValue(this, value.expression, parameters);
612 }
613 }
614
615 evaluate(
616 value: PossiblyEvaluatedValue<CrossFaded<T>>,
617 globals: EvaluationParameters,
618 feature: Feature,
619 featureState: FeatureState,
620 canonical?: CanonicalTileID,
621 availableImages?: Array<string>
622 ): CrossFaded<T> {
623 if (value.kind === 'source') {
624 const constant = value.evaluate(globals, feature, featureState, canonical, availableImages);
625 return this._calculate(constant, constant, constant, globals);
626 } else if (value.kind === 'composite') {
627 return this._calculate(
628 value.evaluate({zoom: Math.floor(globals.zoom) - 1.0}, feature, featureState),
629 value.evaluate({zoom: Math.floor(globals.zoom)}, feature, featureState),
630 value.evaluate({zoom: Math.floor(globals.zoom) + 1.0}, feature, featureState),
631 globals);
632 } else {
633 return value.value;
634 }
635 }
636
637 _calculate(min: T, mid: T, max: T, parameters: EvaluationParameters): CrossFaded<T> {
638 const z = parameters.zoom;
639 return z > parameters.zoomHistory.lastIntegerZoom ? {from: min, to: mid} : {from: max, to: mid};
640 }
641
642 interpolate(a: PossiblyEvaluatedPropertyValue<CrossFaded<T>>): PossiblyEvaluatedPropertyValue<CrossFaded<T>> {
643 return a;
644 }
645}
646/**
647 * An implementation of `Property` for `*-pattern` and `line-dasharray`, which are transitioned by cross-fading
648 * rather than interpolation.
649 *
650 * @private
651 */
652export class CrossFadedProperty<T> implements Property<T, CrossFaded<T>> {
653 specification: StylePropertySpecification;
654
655 constructor(specification: StylePropertySpecification) {
656 this.specification = specification;
657 }
658
659 possiblyEvaluate(
660 value: PropertyValue<T, CrossFaded<T>>,
661 parameters: EvaluationParameters,
662 canonical?: CanonicalTileID,
663 availableImages?: Array<string>
664 ): CrossFaded<T> {
665 if (value.value === undefined) {
666 return undefined;
667 } else if (value.expression.kind === 'constant') {
668 const constant = value.expression.evaluate(parameters, null, {}, canonical, availableImages);
669 return this._calculate(constant, constant, constant, parameters);
670 } else {
671 assert(!value.isDataDriven());
672 return this._calculate(
673 value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom - 1.0), parameters)),
674 value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom), parameters)),
675 value.expression.evaluate(new EvaluationParameters(Math.floor(parameters.zoom + 1.0), parameters)),
676 parameters);
677 }
678 }
679
680 _calculate(min: T, mid: T, max: T, parameters: EvaluationParameters): CrossFaded<T> {
681 const z = parameters.zoom;
682 return z > parameters.zoomHistory.lastIntegerZoom ? {from: min, to: mid} : {from: max, to: mid};
683 }
684
685 interpolate(a?: CrossFaded<T> | null): CrossFaded<T> {
686 return a;
687 }
688}
689
690/**
691 * An implementation of `Property` for `heatmap-color` and `line-gradient`. Interpolation is a no-op, and
692 * evaluation returns a boolean value in order to indicate its presence, but the real
693 * evaluation happens in StyleLayer classes.
694 *
695 * @private
696 */
697
698export class ColorRampProperty implements Property<Color, boolean> {
699 specification: StylePropertySpecification;
700
701 constructor(specification: StylePropertySpecification) {
702 this.specification = specification;
703 }
704
705 possiblyEvaluate(
706 value: PropertyValue<Color, boolean>,
707 parameters: EvaluationParameters,
708 canonical?: CanonicalTileID,
709 availableImages?: Array<string>
710 ): boolean {
711 return !!value.expression.evaluate(parameters, null, {}, canonical, availableImages);
712 }
713
714 interpolate(): boolean { return false; }
715}
716
717/**
718 * `Properties` holds objects containing default values for the layout or paint property set of a given
719 * layer type. These objects are immutable, and they are used as the prototypes for the `_values` members of
720 * `Transitionable`, `Transitioning`, `Layout`, and `PossiblyEvaluated`. This allows these classes to avoid
721 * doing work in the common case where a property has no explicit value set and should be considered to take
722 * on the default value: using `for (const property of Object.keys(this._values))`, they can iterate over
723 * only the _own_ properties of `_values`, skipping repeated calculation of transitions and possible/final
724 * evaluations for defaults, the result of which will always be the same.
725 *
726 * @private
727 */
728export class Properties<Props> {
729 properties: Props;
730 defaultPropertyValues: {[K in keyof Props]: PropertyValue<unknown, any>};
731 defaultTransitionablePropertyValues: {[K in keyof Props]: TransitionablePropertyValue<unknown, unknown>};
732 defaultTransitioningPropertyValues: {[K in keyof Props]: TransitioningPropertyValue<unknown, unknown>};
733 defaultPossiblyEvaluatedValues: {[K in keyof Props]: PossiblyEvaluatedPropertyValue<unknown>};
734 overridableProperties: Array<string>;
735
736 constructor(properties: Props) {
737 this.properties = properties;
738 this.defaultPropertyValues = ({} as any);
739 this.defaultTransitionablePropertyValues = ({} as any);
740 this.defaultTransitioningPropertyValues = ({} as any);
741 this.defaultPossiblyEvaluatedValues = ({} as any);
742 this.overridableProperties = ([] as any);
743
744 for (const property in properties) {
745 const prop = properties[property] as any;
746 if (prop.specification.overridable) {
747 this.overridableProperties.push(property);
748 }
749 const defaultPropertyValue = this.defaultPropertyValues[property] =
750 new PropertyValue(prop, undefined);
751 const defaultTransitionablePropertyValue = this.defaultTransitionablePropertyValues[property] =
752 new TransitionablePropertyValue(prop);
753 this.defaultTransitioningPropertyValues[property] =
754 defaultTransitionablePropertyValue.untransitioned();
755 this.defaultPossiblyEvaluatedValues[property] =
756 defaultPropertyValue.possiblyEvaluate({} as any);
757 }
758 }
759}
760
761register('DataDrivenProperty', DataDrivenProperty);
762register('DataConstantProperty', DataConstantProperty);
763register('CrossFadedDataDrivenProperty', CrossFadedDataDrivenProperty);
764register('CrossFadedProperty', CrossFadedProperty);
765register('ColorRampProperty', ColorRampProperty);
766
\No newline at end of file