UNPKG

mapbox-gl

Version:
172 lines (142 loc) 7.25 kB
// @flow import Point from '@mapbox/point-geometry'; import StyleLayer from '../style_layer.js'; import LineBucket from '../../data/bucket/line_bucket.js'; import {polygonIntersectsBufferedMultiLine} from '../../util/intersection_tests.js'; import {getMaximumPaintValue, translateDistance, translate} from '../query_utils.js'; import properties from './line_style_layer_properties.js'; import {extend} from '../../util/util.js'; import EvaluationParameters from '../evaluation_parameters.js'; import {Transitionable, Transitioning, Layout, PossiblyEvaluated, DataDrivenProperty} from '../properties.js'; import ProgramConfiguration from '../../data/program_configuration.js'; import Step from '../../style-spec/expression/definitions/step.js'; import type {PossiblyEvaluatedValue, PropertyValue, PossiblyEvaluatedPropertyValue} from '../properties.js'; import type {Feature, FeatureState, ZoomConstantExpression, StylePropertyExpression} from '../../style-spec/expression/index.js'; import type {Bucket, BucketParameters} from '../../data/bucket.js'; import type {LayoutProps, PaintProps} from './line_style_layer_properties.js'; import type Transform from '../../geo/transform.js'; import type {LayerSpecification} from '../../style-spec/types.js'; import type {TilespaceQueryGeometry} from '../query_geometry.js'; import type {IVectorTileFeature} from '@mapbox/vector-tile'; class LineFloorwidthProperty extends DataDrivenProperty<number> { useIntegerZoom: ?boolean; possiblyEvaluate(value: PropertyValue<number, PossiblyEvaluatedPropertyValue<number>>, parameters: EvaluationParameters): PossiblyEvaluatedPropertyValue<number> { parameters = new EvaluationParameters(Math.floor(parameters.zoom), { now: parameters.now, fadeDuration: parameters.fadeDuration, transition: parameters.transition }); return super.possiblyEvaluate(value, parameters); } evaluate(value: PossiblyEvaluatedValue<number>, globals: EvaluationParameters, feature: Feature, featureState: FeatureState): number { globals = extend({}, globals, {zoom: Math.floor(globals.zoom)}); return super.evaluate(value, globals, feature, featureState); } } const lineFloorwidthProperty = new LineFloorwidthProperty(properties.paint.properties['line-width'].specification); lineFloorwidthProperty.useIntegerZoom = true; class LineStyleLayer extends StyleLayer { _unevaluatedLayout: Layout<LayoutProps>; layout: PossiblyEvaluated<LayoutProps>; gradientVersion: number; stepInterpolant: boolean; _transitionablePaint: Transitionable<PaintProps>; _transitioningPaint: Transitioning<PaintProps>; paint: PossiblyEvaluated<PaintProps>; constructor(layer: LayerSpecification) { super(layer, properties); this.gradientVersion = 0; } _handleSpecialPaintPropertyUpdate(name: string) { if (name === 'line-gradient') { const expression: ZoomConstantExpression<'source'> = ((this._transitionablePaint._values['line-gradient'].value.expression): any); this.stepInterpolant = expression._styleExpression && expression._styleExpression.expression instanceof Step; this.gradientVersion = (this.gradientVersion + 1) % Number.MAX_SAFE_INTEGER; } } gradientExpression(): StylePropertyExpression { return this._transitionablePaint._values['line-gradient'].value.expression; } widthExpression(): StylePropertyExpression { return this._transitionablePaint._values['line-width'].value.expression; } recalculate(parameters: EvaluationParameters, availableImages: Array<string>) { super.recalculate(parameters, availableImages); (this.paint._values: any)['line-floorwidth'] = lineFloorwidthProperty.possiblyEvaluate(this._transitioningPaint._values['line-width'].value, parameters); } createBucket(parameters: BucketParameters<LineStyleLayer>): LineBucket { return new LineBucket(parameters); } getProgramIds(): string[] { const patternProperty = this.paint.get('line-pattern'); const image = patternProperty.constantOr((1: any)); const programId = image ? 'linePattern' : 'line'; return [programId]; } getProgramConfiguration(zoom: number): ProgramConfiguration { return new ProgramConfiguration(this, zoom); } // $FlowFixMe[method-unbinding] queryRadius(bucket: Bucket): number { const lineBucket: LineBucket = (bucket: any); const width = getLineWidth( getMaximumPaintValue('line-width', this, lineBucket), getMaximumPaintValue('line-gap-width', this, lineBucket)); const offset = getMaximumPaintValue('line-offset', this, lineBucket); return width / 2 + Math.abs(offset) + translateDistance(this.paint.get('line-translate')); } // $FlowFixMe[method-unbinding] queryIntersectsFeature(queryGeometry: TilespaceQueryGeometry, feature: IVectorTileFeature, featureState: FeatureState, geometry: Array<Array<Point>>, zoom: number, transform: Transform): boolean { if (queryGeometry.queryGeometry.isAboveHorizon) return false; const translatedPolygon = translate(queryGeometry.tilespaceGeometry, this.paint.get('line-translate'), this.paint.get('line-translate-anchor'), transform.angle, queryGeometry.pixelToTileUnitsFactor); const halfWidth = queryGeometry.pixelToTileUnitsFactor / 2 * getLineWidth( this.paint.get('line-width').evaluate(feature, featureState), this.paint.get('line-gap-width').evaluate(feature, featureState)); const lineOffset = this.paint.get('line-offset').evaluate(feature, featureState); if (lineOffset) { geometry = offsetLine(geometry, lineOffset * queryGeometry.pixelToTileUnitsFactor); } return polygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth); } isTileClipped(): boolean { return true; } } export default LineStyleLayer; function getLineWidth(lineWidth: number, lineGapWidth: number) { if (lineGapWidth > 0) { return lineGapWidth + 2 * lineWidth; } else { return lineWidth; } } function offsetLine(rings: Array<Array<Point>>, offset: number) { const newRings = []; const zero = new Point(0, 0); for (let k = 0; k < rings.length; k++) { const ring = rings[k]; const newRing = []; for (let i = 0; i < ring.length; i++) { const a = ring[i - 1]; const b = ring[i]; const c = ring[i + 1]; const aToB = i === 0 ? zero : b.sub(a)._unit()._perp(); const bToC = i === ring.length - 1 ? zero : c.sub(b)._unit()._perp(); const extrude = aToB._add(bToC)._unit(); const cosHalfAngle = extrude.x * bToC.x + extrude.y * bToC.y; extrude._mult(1 / cosHalfAngle); newRing.push(extrude._mult(offset)._add(b)); } newRings.push(newRing); } return newRings; }