From a21c6f0a01431065a73b3cc059a06862f23f794c Mon Sep 17 00:00:00 2001 From: zmiao Date: Mon, 24 Feb 2020 11:29:43 +0200 Subject: [PATCH 01/14] Add within expression --- bench/benchmarks/symbol_layout.js | 3 +- src/data/bucket.js | 6 +- src/data/bucket/circle_bucket.js | 15 +-- src/data/bucket/fill_bucket.js | 17 +-- src/data/bucket/fill_extrusion_bucket.js | 14 +-- src/data/bucket/line_bucket.js | 21 ++-- src/data/bucket/symbol_bucket.js | 20 +-- src/data/program_configuration.js | 30 ++--- src/source/worker_tile.js | 9 +- .../expression/definitions/index.js | 4 +- .../expression/definitions/within.js | 117 ++++++++++++++++++ .../expression/evaluation_context.js | 3 + src/style-spec/expression/index.js | 37 +++--- src/style-spec/expression/is_constant.js | 6 +- src/style-spec/expression/parsing_context.js | 4 + src/style-spec/feature_filter/index.js | 3 +- src/style-spec/reference/v8.json | 9 ++ src/style-spec/types.js | 1 + src/style/properties.js | 44 +++---- src/style/style_layer/circle_style_layer.js | 5 +- .../style_layer/fill_extrusion_style_layer.js | 5 +- src/style/style_layer/line_style_layer.js | 4 +- src/symbol/symbol_layout.js | 37 +++--- src/ui/map.js | 1 - src/util/web_worker_transfer.js | 1 - test/ignores.json | 13 +- 26 files changed, 286 insertions(+), 143 deletions(-) create mode 100644 src/style-spec/expression/definitions/within.js diff --git a/bench/benchmarks/symbol_layout.js b/bench/benchmarks/symbol_layout.js index 98f26854313..69397653456 100644 --- a/bench/benchmarks/symbol_layout.js +++ b/bench/benchmarks/symbol_layout.js @@ -37,7 +37,8 @@ export default class SymbolLayout extends Layout { tileResult.glyphPositions, tileResult.iconMap, tileResult.imageAtlas.iconPositions, - false); + false, + tileResult.tileID.canonical); } } }); diff --git a/src/data/bucket.js b/src/data/bucket.js index f93ba793a84..710e9e08dd6 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -7,7 +7,7 @@ import type FeatureIndex from './feature_index'; import type Context from '../gl/context'; import type {FeatureStates} from '../source/source_state'; import type {ImagePosition} from '../render/image_atlas'; - +import type {CanonicalTileID} from '../source/tile_id'; export type BucketParameters = { index: number, layers: Array, @@ -74,8 +74,8 @@ export interface Bucket { +layers: Array; +stateDependentLayers: Array; +stateDependentLayerIds: Array; - populate(features: Array, options: PopulateParameters): void; - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}): void; + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID): void; + update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[string]: ImagePosition}): void; isEmpty(): boolean; upload(context: Context): void; diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index 5e2a37ec1d1..f1e81701d0b 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -10,7 +10,7 @@ import loadGeometry from '../load_geometry'; import EXTENT from '../extent'; import {register} from '../../util/web_worker_transfer'; import EvaluationParameters from '../../style/evaluation_parameters'; - +import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, BucketParameters, @@ -75,7 +75,7 @@ class CircleBucket implements Bucke this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); } - populate(features: Array, options: PopulateParameters) { + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID) { const styleLayer = this.layers[0]; const bucketFeatures = []; let circleSortKey = null; @@ -88,8 +88,9 @@ class CircleBucket implements Bucke for (const {feature, id, index, sourceLayerIndex} of features) { if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) { const geometry = loadGeometry(feature); + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = circleSortKey ? - circleSortKey.evaluate(feature, {}) : + circleSortKey.evaluate(newFeature, {}, canonical) : undefined; const bucketFeature: BucketFeature = { @@ -118,7 +119,7 @@ class CircleBucket implements Bucke const {geometry, index, sourceLayerIndex} = bucketFeature; const feature = features[index].feature; - this.addFeature(bucketFeature, geometry, index); + this.addFeature(bucketFeature, geometry, index, canonical); options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } @@ -153,7 +154,7 @@ class CircleBucket implements Bucke this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID) { for (const ring of geometry) { for (const point of ring) { const x = point.x; @@ -186,8 +187,8 @@ class CircleBucket implements Bucke segment.primitiveLength += 2; } } - - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}); + // debugger; + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, {}); } } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 48eac842225..687ba3ef93b 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -14,7 +14,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; - +import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, BucketParameters, @@ -73,7 +73,7 @@ class FillBucket implements Bucket { this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); } - populate(features: Array, options: PopulateParameters) { + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID) { this.hasPattern = hasPattern('fill', this.layers, options); const fillSortKey = this.layers[0].layout.get('fill-sort-key'); const bucketFeatures = []; @@ -82,8 +82,9 @@ class FillBucket implements Bucket { if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; const geometry = loadGeometry(feature); + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = fillSortKey ? - fillSortKey.evaluate(feature, {}, options.availableImages) : + fillSortKey.evaluate(newFeature, {}, canonical, options.availableImages) : undefined; const bucketFeature: BucketFeature = { @@ -116,7 +117,7 @@ class FillBucket implements Bucket { // so are stored during populate until later updated with positions by tile worker in addFeatures this.patternFeatures.push(patternFeature); } else { - this.addFeature(bucketFeature, geometry, index, {}); + this.addFeature(bucketFeature, geometry, index, canonical, {}); } const feature = features[index].feature; @@ -129,9 +130,9 @@ class FillBucket implements Bucket { this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } - addFeatures(options: PopulateParameters, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { for (const feature of this.patternFeatures) { - this.addFeature(feature, feature.geometry, feature.index, imagePositions); + this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } } @@ -162,7 +163,7 @@ class FillBucket implements Bucket { this.segments2.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { @@ -216,7 +217,7 @@ class FillBucket implements Bucket { triangleSegment.vertexLength += numVertices; triangleSegment.primitiveLength += indices.length / 3; } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); } } diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 1f98d3d5dfc..e75db5d7a40 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -17,7 +17,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; - +import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, BucketParameters, @@ -87,7 +87,7 @@ class FillExtrusionBucket implements Bucket { } - populate(features: Array, options: PopulateParameters) { + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID) { this.features = []; this.hasPattern = hasPattern('fill-extrusion', this.layers, options); @@ -113,17 +113,17 @@ class FillExtrusionBucket implements Bucket { if (this.hasPattern) { this.features.push(addPatternDependencies('fill-extrusion', this.layers, patternFeature, this.zoom, options)); } else { - this.addFeature(patternFeature, geometry, index, {}); + this.addFeature(patternFeature, geometry, index, canonical, {}); } options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index, true); } } - addFeatures(options: PopulateParameters, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { for (const feature of this.features) { const {geometry} = feature; - this.addFeature(feature, geometry, feature.index, imagePositions); + this.addFeature(feature, geometry, feature.index, canonical, imagePositions); } } @@ -157,7 +157,7 @@ class FillExtrusionBucket implements Bucket { this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { @@ -263,7 +263,7 @@ class FillExtrusionBucket implements Bucket { segment.vertexLength += numVertices; } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); } } diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index b68c54dbe9d..3251ef122ba 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -13,7 +13,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; - +import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, BucketParameters, @@ -116,7 +116,7 @@ class LineBucket implements Bucket { this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); } - populate(features: Array, options: PopulateParameters) { + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID) { this.hasPattern = hasPattern('line', this.layers, options); const lineSortKey = this.layers[0].layout.get('line-sort-key'); const bucketFeatures = []; @@ -125,8 +125,9 @@ class LineBucket implements Bucket { if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; const geometry = loadGeometry(feature); + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = lineSortKey ? - lineSortKey.evaluate(feature, {}) : + lineSortKey.evaluate(newFeature, {}, canonical) : undefined; const bucketFeature: BucketFeature = { @@ -159,7 +160,7 @@ class LineBucket implements Bucket { // so are stored during populate until later updated with positions by tile worker in addFeatures this.patternFeatures.push(patternBucketFeature); } else { - this.addFeature(bucketFeature, geometry, index, {}); + this.addFeature(bucketFeature, geometry, index, canonical, {}); } const feature = features[index].feature; @@ -172,9 +173,9 @@ class LineBucket implements Bucket { this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } - addFeatures(options: PopulateParameters, imagePositions: {[_: string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { for (const feature of this.patternFeatures) { - this.addFeature(feature, feature.geometry, feature.index, imagePositions); + this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } } @@ -203,7 +204,7 @@ class LineBucket implements Bucket { this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, imagePositions: {[_: string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { const layout = this.layers[0].layout; const join = layout.get('line-join').evaluate(feature, {}); const cap = layout.get('line-cap'); @@ -211,11 +212,11 @@ class LineBucket implements Bucket { const roundLimit = layout.get('line-round-limit'); for (const line of geometry) { - this.addLine(line, feature, join, cap, miterLimit, roundLimit, index, imagePositions); + this.addLine(line, feature, join, cap, miterLimit, roundLimit, index, canonical, imagePositions); } } - addLine(vertices: Array, feature: BucketFeature, join: string, cap: string, miterLimit: number, roundLimit: number, index: number, imagePositions: {[_: string]: ImagePosition}) { + addLine(vertices: Array, feature: BucketFeature, join: string, cap: string, miterLimit: number, roundLimit: number, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { this.distance = 0; this.scaledDistance = 0; this.totalDistance = 0; @@ -461,7 +462,7 @@ class LineBucket implements Bucket { } } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); } /** diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index ceb2b6e92a1..1d353122e97 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -39,7 +39,7 @@ import EvaluationParameters from '../../style/evaluation_parameters'; import Formatted from '../../style-spec/expression/types/formatted'; import ResolvedImage from '../../style-spec/expression/types/resolved_image'; import {plugin as globalRTLTextPlugin, getRTLTextPluginStatus} from '../../source/rtl_text_plugin'; - +import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, BucketParameters, @@ -433,13 +433,13 @@ class SymbolBucket implements Bucket { if (!layer._featureFilter(globalProperties, feature)) { continue; } - + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; let text: Formatted | void; if (hasText) { // Expression evaluation will automatically coerce to Formatted // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('text-field', feature, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('text-field', newFeature, availableImages); const formattedText = Formatted.factory(resolvedTokens); if (containsRTLText(formattedText)) { this.hasRTLText = true; @@ -449,7 +449,7 @@ class SymbolBucket implements Bucket { getRTLTextPluginStatus() === 'unavailable' || // We don't intend to lazy-load the rtl text plugin, so proceed with incorrect shaping this.hasRTLText && globalRTLTextPlugin.isParsed() // Use the rtlText plugin to shape text ) { - text = transformText(formattedText, layer, feature); + text = transformText(formattedText, layer, newFeature); } } @@ -458,7 +458,7 @@ class SymbolBucket implements Bucket { // Expression evaluation will automatically coerce to Image // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('icon-image', feature, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('icon-image', newFeature, availableImages); if (resolvedTokens instanceof ResolvedImage) { icon = resolvedTokens; } else { @@ -469,9 +469,8 @@ class SymbolBucket implements Bucket { if (!text && !icon) { continue; } - const sortKey = this.sortFeaturesByKey ? - symbolSortKey.evaluate(feature, {}) : + symbolSortKey.evaluate(newFeature, {}) : undefined; const symbolFeature: SymbolFeature = { @@ -492,7 +491,7 @@ class SymbolBucket implements Bucket { } if (text) { - const fontStack = textFont.evaluate(feature, {}).join(','); + const fontStack = textFont.evaluate(newFeature, {}).join(','); const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; this.allowVerticalPlacement = this.writingModes && this.writingModes.indexOf(WritingMode.vertical) >= 0; for (const section of text.sections) { @@ -606,7 +605,8 @@ class SymbolBucket implements Bucket { labelAnchor: Anchor, lineStartIndex: number, lineLength: number, - associatedIconIndex: number) { + associatedIconIndex: number, + canonical: CanonicalTileID) { const indexArray = arrays.indexArray; const layoutVertexArray = arrays.layoutVertexArray; @@ -639,7 +639,7 @@ class SymbolBucket implements Bucket { this.glyphOffsetArray.emplaceBack(glyphOffset[0]); if (i === quads.length - 1 || sectionIndex !== quads[i + 1].sectionIndex) { - arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, {}, sections && sections[sectionIndex]); + arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, canonical, {}, sections && sections[sectionIndex]); } } diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 22e9cba0d3f..123a20706c4 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -7,7 +7,8 @@ import {register} from '../util/web_worker_transfer'; import {PossiblyEvaluatedPropertyValue} from '../style/properties'; import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray} from './array_types'; import {clamp} from '../util/util'; - +import loadGeometry from './load_geometry'; +import type {CanonicalTileID} from '../source/tile_id'; import EvaluationParameters from '../style/evaluation_parameters'; import FeaturePositionMap from './feature_position_map'; import { @@ -77,8 +78,8 @@ function packColor(color: Color): [number, number] { */ interface AttributeBinder { - populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, formattedSection?: FormattedSection): void; - updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}): void; + populatePaintArray(length: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection): void; + updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[string]: ImagePosition}): void; upload(Context): void; destroy(): void; } @@ -161,9 +162,9 @@ class SourceExpressionBinder implements AttributeBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArray(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { const start = this.paintVertexArray.length; - const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, [], formattedSection); + const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, [], formattedSection); this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, value); } @@ -232,9 +233,9 @@ class CompositeExpressionBinder implements AttributeBinder, UniformBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, formattedSection?: FormattedSection) { - const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, [], formattedSection); - const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, [], formattedSection); + populatePaintArray(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { + const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, [], formattedSection); + const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, [], formattedSection); const start = this.paintVertexArray.length; this.paintVertexArray.resize(newLength); this._setPaintValue(start, newLength, min, max); @@ -319,7 +320,7 @@ class CrossFadedCompositeBinder implements AttributeBinder { this.zoomOutPaintVertexArray = new PaintVertexArray(); } - populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}) { + populatePaintArray(length: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { const start = this.zoomInPaintVertexArray.length; this.zoomInPaintVertexArray.resize(length); this.zoomOutPaintVertexArray.resize(length); @@ -442,11 +443,11 @@ export default class ProgramConfiguration { return binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder ? binder.maxValue : 0; } - populatePaintArrays(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArrays(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) - (binder: AttributeBinder).populatePaintArray(newLength, feature, imagePositions, formattedSection); + (binder: AttributeBinder).populatePaintArray(newLength, feature, canonical, imagePositions, formattedSection); } } setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { @@ -472,7 +473,8 @@ export default class ProgramConfiguration { //AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255 const value = layer.paint.get(property); (binder: any).expression = value.value; - (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions); + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, newFeature, featureStates[id], imagePositions); dirty = true; } } @@ -569,9 +571,9 @@ export class ProgramConfigurationSet { this._bufferOffset = 0; } - populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[_: string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArrays(length: number, feature: Feature, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { for (const key in this.programConfigurations) { - this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, formattedSection); + this.programConfigurations[key].populatePaintArrays(length, feature, canonical, imagePositions, formattedSection); } if (feature.id !== undefined) { diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 4a49625997b..909541754bb 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -27,7 +27,6 @@ import type { WorkerTileCallback, } from '../source/worker_source'; import type {PromoteIdSpecification} from '../style-spec/types'; - class WorkerTile { tileID: OverscaledTileID; uid: string; @@ -66,7 +65,6 @@ class WorkerTile { parse(data: VectorTile, layerIndex: StyleLayerIndex, availableImages: Array, actor: Actor, callback: WorkerTileCallback) { this.status = 'parsing'; this.data = data; - this.collisionBoxArray = new CollisionBoxArray(); const sourceLayerCoder = new DictionaryCoder(Object.keys(data.layers).sort()); @@ -123,8 +121,7 @@ class WorkerTile { sourceLayerIndex, sourceID: this.source }); - - bucket.populate(features, options); + bucket.populate(features, options, this.tileID.canonical); featureIndex.bucketLayerIDs.push(family.map((l) => l.id)); } } @@ -186,13 +183,13 @@ class WorkerTile { const bucket = buckets[key]; if (bucket instanceof SymbolBucket) { recalculateLayers(bucket.layers, this.zoom, availableImages); - performSymbolLayout(bucket, glyphMap, glyphAtlas.positions, iconMap, imageAtlas.iconPositions, this.showCollisionBoxes); + performSymbolLayout(bucket, glyphMap, glyphAtlas.positions, iconMap, imageAtlas.iconPositions, this.showCollisionBoxes, this.tileID.canonical); } else if (bucket.hasPattern && (bucket instanceof LineBucket || bucket instanceof FillBucket || bucket instanceof FillExtrusionBucket)) { recalculateLayers(bucket.layers, this.zoom, availableImages); - bucket.addFeatures(options, imageAtlas.patternPositions); + bucket.addFeatures(options, this.tileID.canonical, imageAtlas.patternPositions); } } diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index 7ec0107a549..e8fb1fdbc37 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -42,6 +42,7 @@ import NumberFormat from './number_format'; import FormatExpression from './format'; import ImageExpression from './image'; import Length from './length'; +import Within from './within'; import type {Varargs} from '../compound_expression'; import type {ExpressionRegistry} from '../expression'; @@ -79,7 +80,8 @@ const expressions: ExpressionRegistry = { 'to-color': Coercion, 'to-number': Coercion, 'to-string': Coercion, - 'var': Var + 'var': Var, + 'within': Within }; function rgba(ctx, [r, g, b, a]) { diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js new file mode 100644 index 00000000000..9600df63447 --- /dev/null +++ b/src/style-spec/expression/definitions/within.js @@ -0,0 +1,117 @@ +// @flow + +import {isValue} from '../values'; +import type {Type} from '../types'; +import {BooleanType} from '../types'; +import type {Expression} from '../expression'; +import type ParsingContext from '../parsing_context'; +import type EvaluationContext from '../evaluation_context'; +import type {CanonicalTileID} from '../../../source/tile_id'; +import type {GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types'; +import type {Feature} from '../index'; +import MercatorCoordinate from '../../../geo/mercator_coordinate'; +import EXTENT from '../../../data/extent'; +import Point from '@mapbox/point-geometry'; + +type GeoJSONPolygons =| GeoJSONPolygon | GeoJSONMultiPolygon; + +function rayIntersect(p, p1, p2) { + return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]); +} + +// ray casting algorithm for detecting if point is in polygon +function pointWithinPolygon(rings, p) { + let inside = false; + for (let i = 0, len = rings.length; i < len; i++) { + const ring = rings[i]; + for (let j = 0, len2 = ring.length, k = len2 - 1; j < len2; k = j++) { + if (rayIntersect(p, ring[j], ring[k])) inside = !inside; + } + } + return inside; +} + +function pointWithinPolygons(polygons, lngLat) { + const p = [lngLat.lng, lngLat.lat]; + if (polygons.type === 'Polygon') { + return pointWithinPolygon(polygons.coordinates, p); + } + for (let i = 0; i < polygons.coordinates.length; i++) { + if (!pointWithinPolygon(polygons.coordinates[i], p)) return false; + } + return true; +} + +function getMercatorPoint(coord: Point, canonical: CanonicalTileID) { + const tilesAtZoom = Math.pow(2, canonical.z); + const x = (coord.x / EXTENT + canonical.x) / tilesAtZoom; + const y = (coord.y / EXTENT + canonical.y) / tilesAtZoom; + return new MercatorCoordinate(x, y); +} + +function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons) { + + for (const points of feature.geometry) { + for (const point of points) { + if (!pointWithinPolygons(polygonGeometry, getMercatorPoint(point, canonical).toLngLat())) return false; + } + } + + return true; +} + +class Within implements Expression { + type: Type; + geojson: GeoJSONPolygons; + + constructor(geojson: GeoJSONPolygons) { + this.type = BooleanType; + this.geojson = geojson; + } + + static parse(args: $ReadOnlyArray, context: ParsingContext) { + if (args.length !== 2) + return context.error(`'within' expression requires exactly one argument, but found ${args.length - 1} instead.`); + if (isValue(args[1])) { + const geojson = (args[1]: Object); + if (geojson.type === 'FeatureCollection') { + for (let i = 0; i < geojson.features.length; ++i) { + const type = geojson.features[i].geometry.type; + if (type === 'Polygon' || type === 'MultiPolygon') { + return new Within(geojson.features[i].geometry); + } + } + } else if (geojson.type === 'Feature') { + const type = geojson.feature.geometry.type; + if (type === 'Polygon' || type === 'MultiPolygon') { + return new Within(geojson.feature.geometry); + } + } else if (geojson.geometry.type === 'Polygon' || geojson.geometry.type === 'MultiPolygon') { + return new Within(geojson.geometry); + } + } + return context.error(`'within' expression requires valid geojson source that contains polygon geometry type.`); + } + + evaluate(ctx: EvaluationContext) { + if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'Point') { + return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geojson); + } else if (ctx.geometryType() === 'LineString') { + return true; + } + return false; + } + + eachChild() {} + + possibleOutputs() { + return [true, false]; + } + + serialize(): Array { + return ["within", this.geojson]; + } + +} + +export default Within; diff --git a/src/style-spec/expression/evaluation_context.js b/src/style-spec/expression/evaluation_context.js index 82c801b3725..3ea192a2566 100644 --- a/src/style-spec/expression/evaluation_context.js +++ b/src/style-spec/expression/evaluation_context.js @@ -3,6 +3,7 @@ import {Color} from './values'; import type {FormattedSection} from './types/formatted'; import type {GlobalProperties, Feature, FeatureState} from './index'; +import type {CanonicalTileID} from '../../source/tile_id'; const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon']; @@ -12,6 +13,7 @@ class EvaluationContext { featureState: ?FeatureState; formattedSection: ?FormattedSection; availableImages: ?Array; + canonical: ?CanonicalTileID; _parseColorCache: {[_: string]: ?Color}; @@ -22,6 +24,7 @@ class EvaluationContext { this.formattedSection = null; this._parseColorCache = {}; this.availableImages = null; + this.canonical = null; } id() { diff --git a/src/style-spec/expression/index.js b/src/style-spec/expression/index.js index 077baa855d4..fd9e2da3662 100644 --- a/src/style-spec/expression/index.js +++ b/src/style-spec/expression/index.js @@ -25,12 +25,15 @@ import type {Result} from '../util/result'; import type {InterpolationType} from './definitions/interpolate'; import type {PropertyValueSpecification} from '../types'; import type {FormattedSection} from './types/formatted'; +import type Point from '@mapbox/point-geometry'; +import type {CanonicalTileID} from '../../source/tile_id'; export type Feature = { +type: 1 | 2 | 3 | 'Unknown' | 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon', +id?: any, - +properties: {[_: string]: any}, - +patterns?: {[_: string]: {"min": string, "mid": string, "max": string}} + +properties: {[string]: any}, + +patterns?: {[string]: {"min": string, "mid": string, "max": string}}, + +geometry: Array> }; export type FeatureState = {[_: string]: any}; @@ -59,20 +62,22 @@ export class StyleExpression { this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null; } - evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { + evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { this._evaluator.globals = globals; this._evaluator.feature = feature; this._evaluator.featureState = featureState; + this._evaluator.canonical = canonical; this._evaluator.availableImages = availableImages || null; this._evaluator.formattedSection = formattedSection; return this.expression.evaluate(this._evaluator); } - evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { + evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { this._evaluator.globals = globals; this._evaluator.feature = feature || null; this._evaluator.featureState = featureState || null; + this._evaluator.canonical = canonical; this._evaluator.availableImages = availableImages || null; this._evaluator.formattedSection = formattedSection || null; @@ -138,12 +143,12 @@ export class ZoomConstantExpression { this.isStateDependent = kind !== ('constant': EvaluationKind) && !isConstant.isStateConstant(expression.expression); } - evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { - return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, availableImages, formattedSection); + evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { + return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); } - evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { - return this._styleExpression.evaluate(globals, feature, featureState, availableImages, formattedSection); + evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { + return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); } } @@ -163,12 +168,12 @@ export class ZoomDependentExpression { this.interpolationType = interpolationType; } - evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { - return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, availableImages, formattedSection); + evaluateWithoutErrorHandling(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { + return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection); } - evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection): any { - return this._styleExpression.evaluate(globals, feature, featureState, availableImages, formattedSection); + evaluate(globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection): any { + return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection); } interpolationFactor(input: number, lower: number, upper: number): number { @@ -182,18 +187,18 @@ export class ZoomDependentExpression { export type ConstantExpression = { kind: 'constant', - +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array) => any, + +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array) => any, } export type SourceExpression = { kind: 'source', isStateDependent: boolean, - +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection) => any, + +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection) => any, }; export type CameraExpression = { kind: 'camera', - +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array) => any, + +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array) => any, +interpolationFactor: (input: number, lower: number, upper: number) => number, zoomStops: Array, interpolationType: ?InterpolationType @@ -202,7 +207,7 @@ export type CameraExpression = { export type CompositeExpression = { kind: 'composite', isStateDependent: boolean, - +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, availableImages?: Array, formattedSection?: FormattedSection) => any, + +evaluate: (globals: GlobalProperties, feature?: Feature, featureState?: FeatureState, canonical?: CanonicalTileID, availableImages?: Array, formattedSection?: FormattedSection) => any, +interpolationFactor: (input: number, lower: number, upper: number) => number, zoomStops: Array, interpolationType: ?InterpolationType diff --git a/src/style-spec/expression/is_constant.js b/src/style-spec/expression/is_constant.js index 92e7ab24be2..bb997e899a5 100644 --- a/src/style-spec/expression/is_constant.js +++ b/src/style-spec/expression/is_constant.js @@ -1,7 +1,7 @@ // @flow import CompoundExpression from './compound_expression'; - +import Within from './definitions/within'; import type {Expression} from './expression.js'; function isFeatureConstant(e: Expression) { @@ -23,6 +23,10 @@ function isFeatureConstant(e: Expression) { } } + if (e instanceof Within) { + return false; + } + let result = true; e.eachChild(arg => { if (result && !isFeatureConstant(arg)) { result = false; } diff --git a/src/style-spec/expression/parsing_context.js b/src/style-spec/expression/parsing_context.js index e08fa11ff66..7ee28791564 100644 --- a/src/style-spec/expression/parsing_context.js +++ b/src/style-spec/expression/parsing_context.js @@ -9,6 +9,7 @@ import Coercion from './definitions/coercion'; import EvaluationContext from './evaluation_context'; import CompoundExpression from './compound_expression'; import CollatorExpression from './definitions/collator'; +import Within from './definitions/within'; import {isGlobalPropertyConstant, isFeatureConstant} from './is_constant'; import Var from './definitions/var'; @@ -94,6 +95,7 @@ class ParsingContext { } const Expr = this.registry[op]; + if (Expr) { let parsed = Expr.parse(expr, this); if (!parsed) return null; @@ -201,6 +203,8 @@ function isConstant(expression: Expression) { // generally shouldn't change between executions, we can't serialize them // as constant expressions because results change based on environment. return false; + } else if (expression instanceof Within) { + return false; } const isTypeAnnotation = expression instanceof Coercion || diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index b9c23e48ad6..109f5ab33e1 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -83,7 +83,8 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - return (globalProperties: GlobalProperties, feature: VectorTileFeature) => compiled.value.evaluate(globalProperties, feature); + + return (globalProperties: GlobalProperties, feature: Feature) => compiled.value.evaluate(globalProperties, feature); } } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 587838ef31e..d3a8f964160 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2827,6 +2827,15 @@ } } }, + "within": { + "doc": "Determines whether feature is within provided geometry boundary or not.", + "group": "Decision", + "sdk-support": { + "basic functionality": { + "js": "1.6.0" + } + } + }, "number-format": { "doc": "Converts the input number into a string representation using the providing formatting rules. If set, the `locale` argument specifies the locale to use, as a BCP 47 language tag. If set, the `currency` argument specifies an ISO 4217 code to use for currency-style formatting. If set, the `min-fraction-digits` and `max-fraction-digits` arguments specify the minimum and maximum number of fractional digits to include.", "group": "Types", diff --git a/src/style-spec/types.js b/src/style-spec/types.js index 94b42bd1c2b..c5d623e89be 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -19,6 +19,7 @@ export type FilterSpecification = | ['>=', string, string | number | boolean] | ['<', string, string | number | boolean] | ['<=', string, string | number | boolean] + | ['within', {}] | Array; // Can't type in, !in, all, any, none -- https://github.com/facebook/flow/issues/2443 export type TransitionSpecification = { diff --git a/src/style/properties.js b/src/style/properties.js index 99311fc78d7..d53a713851d 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -1,7 +1,7 @@ // @flow import assert from 'assert'; - +import type {CanonicalTileID} from '../source/tile_id'; import {clone, extend, easeCubicInOut} from '../util/util'; import * as interpolate from '../style-spec/util/interpolate'; import {normalizePropertyExpression} from '../style-spec/expression'; @@ -67,7 +67,7 @@ export type CrossFaded = { */ export interface Property { specification: StylePropertySpecification; - possiblyEvaluate(value: PropertyValue, parameters: EvaluationParameters, availableImages?: Array): R; + possiblyEvaluate(value: PropertyValue, parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): R; interpolate(a: R, b: R, t: number): R; } @@ -105,8 +105,8 @@ export class PropertyValue { return this.expression.kind === 'source' || this.expression.kind === 'composite'; } - possiblyEvaluate(parameters: EvaluationParameters, availableImages?: Array): R { - return this.property.possiblyEvaluate(this, parameters, availableImages); + possiblyEvaluate(parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): R { + return this.property.possiblyEvaluate(this, parameters, canonical, availableImages); } } @@ -264,9 +264,9 @@ class TransitioningPropertyValue { } } - possiblyEvaluate(parameters: EvaluationParameters, availableImages: Array): R { + possiblyEvaluate(parameters: EvaluationParameters, canonical: CanonicalTileID, availableImages: Array): R { const now = parameters.now || 0; - const finalValue = this.value.possiblyEvaluate(parameters, availableImages); + const finalValue = this.value.possiblyEvaluate(parameters, canonical, availableImages); const prior = this.prior; if (!prior) { // No prior value. @@ -283,11 +283,11 @@ class TransitioningPropertyValue { return finalValue; } else if (now < this.begin) { // Transition hasn't started yet. - return prior.possiblyEvaluate(parameters, availableImages); + return prior.possiblyEvaluate(parameters, canonical, availableImages); } else { // Interpolate between recursively-calculated prior value and final. const t = (now - this.begin) / (this.end - this.begin); - return this.property.interpolate(prior.possiblyEvaluate(parameters, availableImages), finalValue, easeCubicInOut(t)); + return this.property.interpolate(prior.possiblyEvaluate(parameters, canonical, availableImages), finalValue, easeCubicInOut(t)); } } } @@ -453,8 +453,8 @@ export class PossiblyEvaluatedPropertyValue { } } - evaluate(feature: Feature, featureState: FeatureState, availableImages?: Array): T { - return this.property.evaluate(this.value, this.parameters, feature, featureState, availableImages); + evaluate(feature: Feature, featureState: FeatureState, canonical?: CanonicalTileID, availableImages?: Array): T { + return this.property.evaluate(this.value, this.parameters, feature, featureState, canonical, availableImages); } } @@ -542,9 +542,9 @@ export class DataDrivenProperty implements Property>, parameters: EvaluationParameters, availableImages?: Array): PossiblyEvaluatedPropertyValue { + possiblyEvaluate(value: PropertyValue>, parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): PossiblyEvaluatedPropertyValue { if (value.expression.kind === 'constant' || value.expression.kind === 'camera') { - return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: value.expression.evaluate(parameters, (null: any), {}, availableImages)}, parameters); + return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: value.expression.evaluate(parameters, (null: any), {}, canonical, availableImages)}, parameters); } else { return new PossiblyEvaluatedPropertyValue(this, value.expression, parameters); } @@ -577,11 +577,11 @@ export class DataDrivenProperty implements Property, parameters: EvaluationParameters, feature: Feature, featureState: FeatureState, availableImages?: Array): T { + evaluate(value: PossiblyEvaluatedValue, parameters: EvaluationParameters, feature: Feature, featureState: FeatureState, canonical?: CanonicalTileID, availableImages?: Array): T { if (value.kind === 'constant') { return value.value; } else { - return value.evaluate(parameters, feature, featureState, availableImages); + return value.evaluate(parameters, feature, featureState, canonical, availableImages); } } } @@ -595,11 +595,11 @@ export class DataDrivenProperty implements Property extends DataDrivenProperty> { - possiblyEvaluate(value: PropertyValue, PossiblyEvaluatedPropertyValue>>, parameters: EvaluationParameters, availableImages?: Array): PossiblyEvaluatedPropertyValue> { + possiblyEvaluate(value: PropertyValue, PossiblyEvaluatedPropertyValue>>, parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): PossiblyEvaluatedPropertyValue> { if (value.value === undefined) { return new PossiblyEvaluatedPropertyValue(this, {kind: 'constant', value: undefined}, parameters); } else if (value.expression.kind === 'constant') { - const evaluatedValue = value.expression.evaluate(parameters, (null: any), {}, availableImages); + const evaluatedValue = value.expression.evaluate(parameters, (null: any), {}, canonical, availableImages); const isImageExpression = value.property.specification.type === 'resolvedImage'; const constantValue = isImageExpression && typeof evaluatedValue !== 'string' ? evaluatedValue.name : evaluatedValue; const constant = this._calculate(constantValue, constantValue, constantValue, parameters); @@ -617,9 +617,9 @@ export class CrossFadedDataDrivenProperty extends DataDrivenProperty>, globals: EvaluationParameters, feature: Feature, featureState: FeatureState, availableImages?: Array): ?CrossFaded { + evaluate(value: PossiblyEvaluatedValue>, globals: EvaluationParameters, feature: Feature, featureState: FeatureState, canonical?: CanonicalTileID, availableImages?: Array): ?CrossFaded { if (value.kind === 'source') { - const constant = value.evaluate(globals, feature, featureState, availableImages); + const constant = value.evaluate(globals, feature, featureState, canonical, availableImages); return this._calculate(constant, constant, constant, globals); } else if (value.kind === 'composite') { return this._calculate( @@ -654,11 +654,11 @@ export class CrossFadedProperty implements Property> { this.specification = specification; } - possiblyEvaluate(value: PropertyValue>, parameters: EvaluationParameters, availableImages?: Array): ?CrossFaded { + possiblyEvaluate(value: PropertyValue>, parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): ?CrossFaded { if (value.value === undefined) { return undefined; } else if (value.expression.kind === 'constant') { - const constant = value.expression.evaluate(parameters, (null: any), {}, availableImages); + const constant = value.expression.evaluate(parameters, (null: any), {}, canonical, availableImages); return this._calculate(constant, constant, constant, parameters); } else { assert(!value.isDataDriven()); @@ -695,8 +695,8 @@ export class ColorRampProperty implements Property { this.specification = specification; } - possiblyEvaluate(value: PropertyValue, parameters: EvaluationParameters, availableImages?: Array): boolean { - return !!value.expression.evaluate(parameters, (null: any), {}, availableImages); + possiblyEvaluate(value: PropertyValue, parameters: EvaluationParameters, canonical?: CanonicalTileID, availableImages?: Array): boolean { + return !!value.expression.evaluate(parameters, (null: any), {}, canonical, availableImages); } interpolate(): boolean { return false; } diff --git a/src/style/style_layer/circle_style_layer.js b/src/style/style_layer/circle_style_layer.js index e5168f56a29..a15081ccd34 100644 --- a/src/style/style_layer/circle_style_layer.js +++ b/src/style/style_layer/circle_style_layer.js @@ -15,7 +15,7 @@ import type Transform from '../../geo/transform'; import type {Bucket, BucketParameters} from '../../data/bucket'; import type {LayoutProps, PaintProps} from './circle_style_layer_properties'; import type {LayerSpecification} from '../../style-spec/types'; - +import loadGeometry from '../../data/load_geometry'; class CircleStyleLayer extends StyleLayer { _unevaluatedLayout: Layout; layout: PossiblyEvaluated; @@ -40,7 +40,7 @@ class CircleStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - feature: VectorTileFeature, + vFeature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -51,6 +51,7 @@ class CircleStyleLayer extends StyleLayer { this.paint.get('circle-translate'), this.paint.get('circle-translate-anchor'), transform.angle, pixelsToTileUnits); + const feature = {type: vFeature.type, properties: vFeature.properties, geometry: loadGeometry(vFeature)}; const radius = this.paint.get('circle-radius').evaluate(feature, featureState); const stroke = this.paint.get('circle-stroke-width').evaluate(feature, featureState); const size = radius + stroke; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 533fb8978f8..8e595b7dc61 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -15,6 +15,7 @@ import type {BucketParameters} from '../../data/bucket'; import type {PaintProps} from './fill_extrusion_style_layer_properties'; import type Transform from '../../geo/transform'; import type {LayerSpecification} from '../../style-spec/types'; +import loadGeometry from '../../data/load_geometry'; class FillExtrusionStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; @@ -38,7 +39,7 @@ class FillExtrusionStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - feature: VectorTileFeature, + vfeature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -50,7 +51,7 @@ class FillExtrusionStyleLayer extends StyleLayer { this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), transform.angle, pixelsToTileUnits); - + const feature = {type: vfeature.type, properties: vfeature.properties, geometry: loadGeometry(vfeature)}; const height = this.paint.get('fill-extrusion-height').evaluate(feature, featureState); const base = this.paint.get('fill-extrusion-base').evaluate(feature, featureState); diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index 336e60d45ec..3afc5fb4cfa 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -19,6 +19,7 @@ import type {LayoutProps, PaintProps} from './line_style_layer_properties'; import type Transform from '../../geo/transform'; import type Texture from '../../render/texture'; import type {LayerSpecification} from '../../style-spec/types'; +import loadGeometry from '../../data/load_geometry'; class LineFloorwidthProperty extends DataDrivenProperty { useIntegerZoom: true; @@ -90,7 +91,7 @@ class LineStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - feature: VectorTileFeature, + vfeature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -100,6 +101,7 @@ class LineStyleLayer extends StyleLayer { this.paint.get('line-translate'), this.paint.get('line-translate-anchor'), transform.angle, pixelsToTileUnits); + const feature = {type: vfeature.type, properties: vfeature.properties, geometry: loadGeometry(vfeature)}; const halfWidth = pixelsToTileUnits / 2 * getLineWidth( this.paint.get('line-width').evaluate(feature, featureState), this.paint.get('line-gap-width').evaluate(feature, featureState)); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index f3f5fec0d62..60a116f6765 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -19,7 +19,7 @@ import SymbolBucket from '../data/bucket/symbol_bucket'; import EvaluationParameters from '../style/evaluation_parameters'; import {SIZE_PACK_FACTOR} from './symbol_size'; import ONE_EM from './one_em'; - +import type {CanonicalTileID} from '../source/tile_id'; import type {Shaping, PositionedIcon, TextJustify} from './shaping'; import type {CollisionBoxArray} from '../data/array_types'; import type {SymbolFeature} from '../data/bucket/symbol_bucket'; @@ -148,11 +148,12 @@ export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, numb } export function performSymbolLayout(bucket: SymbolBucket, - glyphMap: {[_: string]: {[_: number]: ?StyleGlyph}}, - glyphPositions: {[_: string]: {[_: number]: GlyphPosition}}, - imageMap: {[_: string]: StyleImage}, - imagePositions: {[_: string]: ImagePosition}, - showCollisionBoxes: boolean) { + glyphMap: {[string]: {[number]: ?StyleGlyph}}, + glyphPositions: {[string]: {[number]: GlyphPosition}}, + imageMap: {[string]: StyleImage}, + imagePositions: {[string]: ImagePosition}, + showCollisionBoxes: boolean, + canonical: CanonicalTileID) { bucket.createArrays(); const tileSize = 512 * bucket.overscaling; @@ -316,7 +317,7 @@ export function performSymbolLayout(bucket: SymbolBucket, const shapedText = getDefaultHorizontalShaping(shapedTextOrientations.horizontal) || shapedTextOrientations.vertical; bucket.iconsInText = shapedText ? shapedText.iconsInText : false; if (shapedText || shapedIcon) { - addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon); + addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, canonical); } } @@ -356,7 +357,7 @@ function addFeature(bucket: SymbolBucket, layoutTextSize: number, layoutIconSize: number, textOffset: [number, number], - isSDFIcon: boolean) { + isSDFIcon: boolean, canonical: CanonicalTileID) { // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. // bucket calculates text-size at a high zoom level so that all tiles can @@ -365,7 +366,6 @@ function addFeature(bucket: SymbolBucket, if (textMaxSize === undefined) { textMaxSize = layoutTextSize; } - const layout = bucket.layers[0].layout; const iconOffset = layout.get('icon-offset').evaluate(feature, {}); const defaultHorizontalShaping = getDefaultHorizontalShaping(shapedTextOrientations.horizontal); @@ -409,7 +409,7 @@ function addFeature(bucket: SymbolBucket, bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textBoxScale, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, - feature, sizes, isSDFIcon); + feature, sizes, isSDFIcon, canonical); }; if (symbolPlacement === 'line') { @@ -486,7 +486,8 @@ function addTextVertices(bucket: SymbolBucket, placementTypes: Array<'vertical' | 'center' | 'left' | 'right'>, placedTextSymbolIndices: {[_: string]: number}, placedIconIndex: number, - sizes: Sizes) { + sizes: Sizes, + canonical: CanonicalTileID) { const glyphQuads = getGlyphQuads(anchor, shapedText, textOffset, layer, textAlongLine, feature, imageMap, bucket.allowVerticalPlacement); @@ -521,7 +522,8 @@ function addTextVertices(bucket: SymbolBucket, anchor, lineArray.lineStartIndex, lineArray.lineLength, - placedIconIndex); + placedIconIndex, + canonical); // The placedSymbolArray is used at render time in drawTileSymbols // These indices allow access to the array at collision detection time @@ -568,7 +570,8 @@ function addSymbol(bucket: SymbolBucket, iconOffset: [number, number], feature: SymbolFeature, sizes: Sizes, - isSDFIcon: boolean) { + isSDFIcon: boolean, + canonical: CanonicalTileID) { const lineArray = bucket.addToLineVertexArray(anchor, line); let textCollisionFeature, iconCollisionFeature, verticalTextCollisionFeature, verticalIconCollisionFeature; @@ -647,7 +650,7 @@ function addSymbol(bucket: SymbolBucket, lineArray.lineStartIndex, lineArray.lineLength, // The icon itself does not have an associated symbol since the text isnt placed yet - -1); + -1, canonical); placedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; @@ -666,7 +669,7 @@ function addSymbol(bucket: SymbolBucket, lineArray.lineStartIndex, lineArray.lineLength, // The icon itself does not have an associated symbol since the text isnt placed yet - -1); + -1, canonical); verticalPlacedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; } @@ -688,7 +691,7 @@ function addSymbol(bucket: SymbolBucket, bucket, anchor, shaping, imageMap, layer, textAlongLine, feature, textOffset, lineArray, shapedTextOrientations.vertical ? WritingMode.horizontal : WritingMode.horizontalOnly, singleLine ? (Object.keys(shapedTextOrientations.horizontal): any) : [justification], - placedTextSymbolIndices, placedIconSymbolIndex, sizes); + placedTextSymbolIndices, placedIconSymbolIndex, sizes, canonical); if (singleLine) { break; @@ -698,7 +701,7 @@ function addSymbol(bucket: SymbolBucket, if (shapedTextOrientations.vertical) { numVerticalGlyphVertices += addTextVertices( bucket, anchor, shapedTextOrientations.vertical, imageMap, layer, textAlongLine, feature, - textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, verticalPlacedIconSymbolIndex, sizes); + textOffset, lineArray, WritingMode.vertical, ['vertical'], placedTextSymbolIndices, verticalPlacedIconSymbolIndex, sizes, canonical); } const textBoxStartIndex = textCollisionFeature ? textCollisionFeature.boxStartIndex : bucket.collisionBoxArray.length; diff --git a/src/ui/map.js b/src/ui/map.js index 2f06d6f87e8..0c490a8ac11 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -349,7 +349,6 @@ class Map extends Camera { PerformanceUtils.mark(PerformanceMarkers.create); options = extend({}, defaultOptions, options); - if (options.minZoom != null && options.maxZoom != null && options.minZoom > options.maxZoom) { throw new Error(`maxZoom must be greater than or equal to minZoom`); } diff --git a/src/util/web_worker_transfer.js b/src/util/web_worker_transfer.js index a3625eccc42..80576389b28 100644 --- a/src/util/web_worker_transfer.js +++ b/src/util/web_worker_transfer.js @@ -88,7 +88,6 @@ register('Grid', Grid); register('Color', Color); register('Error', Error); register('ResolvedImage', ResolvedImage); - register('StylePropertyFunction', StylePropertyFunction); register('StyleExpression', StyleExpression, {omit: ['_evaluator']}); diff --git a/test/ignores.json b/test/ignores.json index 2af526d52c3..f97f8333592 100644 --- a/test/ignores.json +++ b/test/ignores.json @@ -20,16 +20,5 @@ "render-tests/text-variable-anchor/all-anchors-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", "render-tests/fill-pattern/update-feature-state": "https://github.com/mapbox/mapbox-gl-js/issues/7207", "render-tests/text-size/zero": "https://github.com/mapbox/mapbox-gl-js/issues/9161", - "render-tests/text-variable-anchor/left-top-right-bottom-offset-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", - "render-tests/tile-mode/streets-v11": "skip - mapbox-gl-js does not support tile-mode", - "render-tests/within/filter-with-inlined-geojson": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "render-tests/within/paint-circle": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "render-tests/within/paint-icon": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "render-tests/within/paint-text": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "render-tests/within/layout-text": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16194", - "render-tests/within/paint-line": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16220", - "expression-tests/within/point-within-polygon": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "expression-tests/within/invalid-geojson": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "expression-tests/within/non-supported": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16157", - "expression-tests/within/line-within-polygon": "skip - port https://github.com/mapbox/mapbox-gl-native/pull/16220" + "render-tests/text-variable-anchor/left-top-right-buttom-offset-tile-map-mode": "skip - mapbox-gl-js does not need to render tiles" } From 221b0f3664a33e4e6e3be02cd4cfc38fe94a3139 Mon Sep 17 00:00:00 2001 From: zmiao Date: Tue, 25 Feb 2020 20:16:14 +0200 Subject: [PATCH 02/14] Add support for filter --- src/data/bucket/circle_bucket.js | 6 +++--- src/data/bucket/fill_bucket.js | 4 ++-- src/data/bucket/fill_extrusion_bucket.js | 3 ++- src/data/bucket/line_bucket.js | 4 ++-- src/data/bucket/symbol_bucket.js | 4 ++-- src/data/feature_index.js | 4 ++-- src/source/tile.js | 5 +++-- src/style-spec/expression/definitions/within.js | 4 ++-- src/style-spec/feature_filter/index.js | 4 ++-- src/style-spec/package.json | 1 + 10 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index f1e81701d0b..8b0d93ae2ef 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -86,9 +86,9 @@ class CircleBucket implements Bucke } for (const {feature, id, index, sourceLayerIndex} of features) { - if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) { + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) { const geometry = loadGeometry(feature); - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = circleSortKey ? circleSortKey.evaluate(newFeature, {}, canonical) : undefined; @@ -187,7 +187,7 @@ class CircleBucket implements Bucke segment.primitiveLength += 2; } } - // debugger; + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, {}); } } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 687ba3ef93b..83d915fd131 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -79,10 +79,10 @@ class FillBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; const geometry = loadGeometry(feature); - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = fillSortKey ? fillSortKey.evaluate(newFeature, {}, canonical, options.availableImages) : undefined; diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index e75db5d7a40..57552660805 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -92,7 +92,8 @@ class FillExtrusionBucket implements Bucket { this.hasPattern = hasPattern('fill-extrusion', this.layers, options); for (const {feature, id, index, sourceLayerIndex} of features) { - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; const geometry = loadGeometry(feature); diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 3251ef122ba..88a212d68d1 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -122,10 +122,10 @@ class LineBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; const geometry = loadGeometry(feature); - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; const sortKey = lineSortKey ? lineSortKey.evaluate(newFeature, {}, canonical) : undefined; diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 1d353122e97..ff70c594218 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -430,10 +430,10 @@ class SymbolBucket implements Bucket { const globalProperties = new EvaluationParameters(this.zoom); for (const {feature, id, index, sourceLayerIndex} of features) { - if (!layer._featureFilter(globalProperties, feature)) { + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (!layer._featureFilter(globalProperties, newFeature)) { continue; } - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; let text: Formatted | void; if (hasText) { // Expression evaluation will automatically coerce to Formatted diff --git a/src/data/feature_index.js b/src/data/feature_index.js index 2937d314d3c..cf750873e84 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -184,8 +184,8 @@ class FeatureIndex { const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); const sourceLayer = this.vtLayers[sourceLayerName]; const feature = sourceLayer.feature(featureIndex); - - if (!filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (!filter(new EvaluationParameters(this.tileID.overscaledZ), newFeature)) return; const id = this.getId(feature, sourceLayerName); diff --git a/src/source/tile.js b/src/source/tile.js index d9bd4b51889..7163147b040 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -12,7 +12,7 @@ import browser from '../util/browser'; import EvaluationParameters from '../style/evaluation_parameters'; import SourceFeatureState from '../source/source_state'; import {lazyLoadRTLTextPlugin} from './rtl_text_plugin'; - +import loadGeometry from '../data/load_geometry'; const CLOCK_SKEW_RETRY_TIMEOUT = 30000; import type {Bucket} from '../data/bucket'; @@ -307,7 +307,8 @@ class Tile { for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); - if (filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) { + const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; + if (filter(new EvaluationParameters(this.tileID.overscaledZ), newFeature)) { const id = featureIndex.getId(feature, sourceLayer); const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id); (geojsonFeature: any).tile = coord; diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 9600df63447..75c7081ed17 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -86,8 +86,8 @@ class Within implements Expression { if (type === 'Polygon' || type === 'MultiPolygon') { return new Within(geojson.feature.geometry); } - } else if (geojson.geometry.type === 'Polygon' || geojson.geometry.type === 'MultiPolygon') { - return new Within(geojson.geometry); + } else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') { + return new Within(geojson); } } return context.error(`'within' expression requires valid geojson source that contains polygon geometry type.`); diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index 109f5ab33e1..7752142bfdf 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -1,9 +1,9 @@ // @flow import {createExpression} from '../expression'; - +import type {Feature} from '../expression'; import type {GlobalProperties} from '../expression'; -export type FeatureFilter = (globalProperties: GlobalProperties, feature: VectorTileFeature) => boolean; +export type FeatureFilter = (globalProperties: GlobalProperties, feature: Feature) => boolean; export default createFilter; export {isExpressionFilter}; diff --git a/src/style-spec/package.json b/src/style-spec/package.json index 1254536e866..2a18082a312 100644 --- a/src/style-spec/package.json +++ b/src/style-spec/package.json @@ -30,6 +30,7 @@ "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.0", + "@mapbox/point-geometry": "^0.1.0", "csscolorparser": "~1.0.2", "json-stringify-pretty-compact": "^2.0.0", "minimist": "0.0.8", From ac60543770eb3b950b31c5beb8881cf4e44e8132 Mon Sep 17 00:00:00 2001 From: zmiao Date: Wed, 26 Feb 2020 13:16:32 +0200 Subject: [PATCH 03/14] fix expression test --- .../expression/definitions/within.js | 2 - src/style-spec/feature_filter/index.js | 3 +- test/expression.test.js | 68 ++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 75c7081ed17..b0165221898 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -50,13 +50,11 @@ function getMercatorPoint(coord: Point, canonical: CanonicalTileID) { } function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons) { - for (const points of feature.geometry) { for (const point of points) { if (!pointWithinPolygons(polygonGeometry, getMercatorPoint(point, canonical).toLngLat())) return false; } } - return true; } diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index 7752142bfdf..c5e452f96b4 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -1,8 +1,7 @@ // @flow import {createExpression} from '../expression'; -import type {Feature} from '../expression'; -import type {GlobalProperties} from '../expression'; +import type {GlobalProperties, Feature} from '../expression'; export type FeatureFilter = (globalProperties: GlobalProperties, feature: Feature) => boolean; export default createFilter; diff --git a/test/expression.test.js b/test/expression.test.js index ad46463f72a..47e955d0393 100644 --- a/test/expression.test.js +++ b/test/expression.test.js @@ -4,6 +4,57 @@ import {isFunction} from '../src/style-spec/function'; import convertFunction from '../src/style-spec/function/convert'; import {toString} from '../src/style-spec/expression/types'; import ignores from './ignores.json'; +import {CanonicalTileID} from '../src/source/tile_id'; +import MercatorCoordinate from '../src/geo/mercator_coordinate'; + +function convertPoint(coord, canonical, out) { + const p = canonical.getTilePoint(MercatorCoordinate.fromLngLat({lng: coord[0], lat: coord[1]}, 0)); + out.push([p]); +} + +function convertPoints(coords, canonical, out) { + for (let i = 0; i < coords.length; i++) { + convertPoint(coords[i], canonical, out); + } +} + +function convertLines(lines, canonical, out) { + for (let i = 0; i < lines.length; i++) { + const geom = []; + const ring = lines[i]; + for (let j = 0; j < ring.length; j++) { + convertPoint(ring[j], canonical, geom); + } + out.push(geom); + } +} + +function getGeomtry(feature, geometry, canonical) { + if (geometry.coordinates) { + const coords = geometry.coordinates; + const type = geometry.type; + feature.type = type; + feature.geometry = []; + if (type === 'Point') { + convertPoint(coords, canonical, feature.geometry); + } else if (type === 'MultiPoint') { + convertPoints(coords, canonical, feature.geometry); + } else if (type === 'LineString') { + convertPoints(coords, canonical, feature.geometry); + } else if (type === 'MultiLineString') { + convertLines(coords, canonical, feature.geometry); + } else if (type === 'Polygon') { + convertLines(coords, canonical, feature.geometry); + + } else if (type === 'MultiPolygon') { + for (let i = 0; i < coords.length; i++) { + const polygon = []; + convertLines(coords[i], canonical, polygon); + feature.geometry.push(polygon); + } + } + } +} let tests; @@ -14,6 +65,7 @@ if (process.argv[1] === __filename && process.argv.length > 2) { run('js', {ignores, tests}, (fixture) => { const spec = Object.assign({}, fixture.propertySpec); let availableImages; + let canonical; if (!spec['property-type']) { spec['property-type'] = 'data-driven'; @@ -50,14 +102,26 @@ run('js', {ignores, tests}, (fixture) => { try { const feature = {properties: input[1].properties || {}}; availableImages = input[0].availableImages || []; + if ('canonicalID' in input[0]) { + const id = input[0].canonicalID; + canonical = new CanonicalTileID(id.z, id.x, id.y); + } else { + canonical = null; + } if ('id' in input[1]) { feature.id = input[1].id; } if ('geometry' in input[1]) { - feature.type = input[1].geometry.type; + if (canonical !== null) { + getGeomtry(feature, input[1].geometry, canonical); + } else { + feature.type = input[1].geometry.type; + } } - let value = expression.evaluateWithoutErrorHandling(input[0], feature, {}, availableImages); + + let value = expression.evaluateWithoutErrorHandling(input[0], feature, {}, canonical, availableImages); + if (type.kind === 'color') { value = [value.r, value.g, value.b, value.a]; } From 486cc0ba4cfc4d1950f408e9bd4b64c0c594264a Mon Sep 17 00:00:00 2001 From: zmiao Date: Wed, 26 Feb 2020 16:20:05 +0200 Subject: [PATCH 04/14] Add support for using within with filter --- src/data/bucket/circle_bucket.js | 4 ++-- src/data/bucket/fill_bucket.js | 2 +- src/data/bucket/fill_extrusion_bucket.js | 2 +- src/data/bucket/line_bucket.js | 2 +- src/data/bucket/symbol_bucket.js | 4 ++-- .../expression/definitions/within.js | 18 ++++++++++-------- src/style-spec/feature_filter/index.js | 5 +++-- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index 8b0d93ae2ef..e1de99e72a2 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -84,10 +84,10 @@ class CircleBucket implements Bucke if (styleLayer.type === 'circle') { circleSortKey = ((styleLayer: any): CircleStyleLayer).layout.get('circle-sort-key'); } - for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) { + debugger; + if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) { const geometry = loadGeometry(feature); const sortKey = circleSortKey ? circleSortKey.evaluate(newFeature, {}, canonical) : diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 83d915fd131..c62fd28ece5 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -80,7 +80,7 @@ class FillBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; const geometry = loadGeometry(feature); const sortKey = fillSortKey ? diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 57552660805..7f913c08743 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -93,7 +93,7 @@ class FillExtrusionBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; const geometry = loadGeometry(feature); diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 88a212d68d1..6e073e81121 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -123,7 +123,7 @@ class LineBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature)) continue; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; const geometry = loadGeometry(feature); const sortKey = lineSortKey ? diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index ff70c594218..d37431143d2 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -399,7 +399,7 @@ class SymbolBucket implements Bucket { } } - populate(features: Array, options: PopulateParameters) { + populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID) { const layer = this.layers[0]; const layout = layer.layout; @@ -431,7 +431,7 @@ class SymbolBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!layer._featureFilter(globalProperties, newFeature)) { + if (!layer._featureFilter(globalProperties, newFeature, canonical)) { continue; } let text: Formatted | void; diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index b0165221898..8c46cb157d2 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -7,7 +7,7 @@ import type {Expression} from '../expression'; import type ParsingContext from '../parsing_context'; import type EvaluationContext from '../evaluation_context'; import type {CanonicalTileID} from '../../../source/tile_id'; -import type {GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types'; +import type {GeoJSON, GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types'; import type {Feature} from '../index'; import MercatorCoordinate from '../../../geo/mercator_coordinate'; import EXTENT from '../../../data/extent'; @@ -60,11 +60,13 @@ function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, poly class Within implements Expression { type: Type; - geojson: GeoJSONPolygons; + geojson: GeoJSON + geometries: GeoJSONPolygons; - constructor(geojson: GeoJSONPolygons) { + constructor(geojson: GeoJSON, geometries: GeoJSONPolygons) { this.type = BooleanType; this.geojson = geojson; + this.geometries = geometries; } static parse(args: $ReadOnlyArray, context: ParsingContext) { @@ -76,16 +78,16 @@ class Within implements Expression { for (let i = 0; i < geojson.features.length; ++i) { const type = geojson.features[i].geometry.type; if (type === 'Polygon' || type === 'MultiPolygon') { - return new Within(geojson.features[i].geometry); + return new Within(geojson, geojson.features[i].geometry); } } } else if (geojson.type === 'Feature') { - const type = geojson.feature.geometry.type; + const type = geojson.geometry.type; if (type === 'Polygon' || type === 'MultiPolygon') { - return new Within(geojson.feature.geometry); + return new Within(geojson, geojson.geometry); } } else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') { - return new Within(geojson); + return new Within(geojson, geojson); } } return context.error(`'within' expression requires valid geojson source that contains polygon geometry type.`); @@ -93,7 +95,7 @@ class Within implements Expression { evaluate(ctx: EvaluationContext) { if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'Point') { - return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geojson); + return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geometries); } else if (ctx.geometryType() === 'LineString') { return true; } diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index c5e452f96b4..9aeb9494cc2 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -2,7 +2,8 @@ import {createExpression} from '../expression'; import type {GlobalProperties, Feature} from '../expression'; -export type FeatureFilter = (globalProperties: GlobalProperties, feature: Feature) => boolean; +import type {CanonicalTileID} from '../../source/tile_id'; +export type FeatureFilter = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => boolean; export default createFilter; export {isExpressionFilter}; @@ -83,7 +84,7 @@ function createFilter(filter: any): FeatureFilter { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - return (globalProperties: GlobalProperties, feature: Feature) => compiled.value.evaluate(globalProperties, feature); + return (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, null, canonical); } } From b771dd090d7b77da598fa4fa93f785902f0adbaa Mon Sep 17 00:00:00 2001 From: zmiao Date: Wed, 26 Feb 2020 16:40:21 +0200 Subject: [PATCH 05/14] Update render tests to avoid rendering difference between gl-js and gl-native --- src/data/bucket/circle_bucket.js | 1 - src/style-spec/feature_filter/index.js | 2 +- .../filter-with-inlined-geojson/expected.png | Bin 377 -> 393 bytes .../filter-with-inlined-geojson/style.json | 2 +- .../within/paint-circle/expected.png | Bin 401 -> 476 bytes .../render-tests/within/paint-circle/style.json | 2 +- .../render-tests/within/paint-icon/expected.png | Bin 623 -> 598 bytes .../render-tests/within/paint-icon/style.json | 2 +- .../render-tests/within/paint-text/expected.png | Bin 853 -> 870 bytes .../render-tests/within/paint-text/style.json | 2 +- 10 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index e1de99e72a2..f6c737447ca 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -86,7 +86,6 @@ class CircleBucket implements Bucke } for (const {feature, id, index, sourceLayerIndex} of features) { const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - debugger; if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) { const geometry = loadGeometry(feature); const sortKey = circleSortKey ? diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index 9aeb9494cc2..91a5ed5db79 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -84,7 +84,7 @@ function createFilter(filter: any): FeatureFilter { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - return (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, null, canonical); + return (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical); } } diff --git a/test/integration/render-tests/within/filter-with-inlined-geojson/expected.png b/test/integration/render-tests/within/filter-with-inlined-geojson/expected.png index 61b6f1796b751b0e52a3e6cd738feb7747c5f455..7f3f172cee2d680e5ec6ae12740877da4e81e079 100644 GIT binary patch literal 393 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU<~kdaSW+oe0xwa`%0k<`-A6p zzol8*#Ud?I9eLThk6u?`Yfgw1Xui85+UNGCadCmjh zW1|0mJl^;JxuL`rvrRAdT{S-N(4a^h2b9qF*gvzEr|OP+=CiuIkSpYU0 z?Fj-1AV9b{OYK8TRR9DKK!9*@mfDAwssIQefB@m*EVU0UReu2xKmY;4#aU_}TB-sd zfB*u7i?h@|v{Z)$*zX?r2Ja&n-NM5<999_J?{EMw@H&A>-ZQvt9^f3VqVd1ofTN}X z##6?p-oazD086klZG7q%d^Za)4_ki$e86Y30IA(8{xl8X08X0*ScScA=r|gmK8uyc z4+oIC)*;-(b60=-1zgY78=pG-pWp&)ufT4Fu~GdMMgRc>2p4CmeQ2o)fB*sr5H8MA w`_NJq009ILAY7cK_MxRJ00IagK)AS diff --git a/test/integration/render-tests/within/filter-with-inlined-geojson/style.json b/test/integration/render-tests/within/filter-with-inlined-geojson/style.json index 19b184eec78..be4e8fefdd5 100644 --- a/test/integration/render-tests/within/filter-with-inlined-geojson/style.json +++ b/test/integration/render-tests/within/filter-with-inlined-geojson/style.json @@ -7,7 +7,7 @@ } }, "zoom": 3, - "center": [3, 3], + "center": [2.5, 2.5], "sources": { "points": { "type": "geojson", diff --git a/test/integration/render-tests/within/paint-circle/expected.png b/test/integration/render-tests/within/paint-circle/expected.png index 2be9f737ca47c032660c18747e71b3664bc6b48f..8906ad136fb4ca2b083a5475f1f798112741b335 100644 GIT binary patch delta 450 zcmbQpe2000O8sI_7srqa#B=JWgG_xZmMtVN`KwozGdlw7R&kM+$`-R3BBw$Yg9&fBidFzSeI2^gE$aSLGJX za&b+c6)|Ckm$H@UyW}(T(+V42wY#*qmAp$l;x?HfeuL2*U6H5!XO4;?jCu z%xIlEC)Z*TqweeN8IN-=O_;C~6<%0iSEVK)u`GU3lvF@s-(+Q;pHH>#*Pko=%(Y&1 z$?;!3i#K&`pL6dh-~P;t#Zw-t-aGr@;(o~kxx5^vyHCtp%M*1p)z8CY#Z67OwF{@; z+nY17BVcpguM)!(X`LLV)yJe`&wTJyayomi;JVba%C9ru3kha@YkKN^=acyRp9Y6Y zIWsd4RL+Y_{;2iXIVb+vY2Ih&KDi`CSxbGIvy^k0ie3sbT$lQnPrGZ81_b@{w`WX9 WcHilfeN>MD2s~Z=T-G@yGywp$j@Ksu delta 374 zcmV-+0g3+H1CaxeB!5^*L_t(|ob8!0P6IIzMZc&hski|YG$?nXU9k(@0Rvd zh4{AkvfJ9zb&Pus1gNwp>=<$RFaI?$0oWRzZhY0*fNwSe>;MP74uFV{z!Z3CJ1b}>z!7ks`RUi%34qTC7c0K}s`cM3Uu!GC z!U1r&51b?{zR+fXj50vJ@zY@dFaQHE0D2za+Iw2iQIXAlz(7Gl2qA3}BRsaA107*qoM6N<$f?&dz>% diff --git a/test/integration/render-tests/within/paint-circle/style.json b/test/integration/render-tests/within/paint-circle/style.json index e393ce0032f..c7f27d00766 100644 --- a/test/integration/render-tests/within/paint-circle/style.json +++ b/test/integration/render-tests/within/paint-circle/style.json @@ -7,7 +7,7 @@ } }, "zoom": 2, - "center": [3.5, 3.5], + "center": [3.25, 3.25], "sources": { "points": { "type": "geojson", diff --git a/test/integration/render-tests/within/paint-icon/expected.png b/test/integration/render-tests/within/paint-icon/expected.png index f24c546a9a89534fd31c88085062be62a70a41b3..c3a5dec36108e05efa962e751f3b1963fcdf35ac 100644 GIT binary patch delta 573 zcmV-D0>b_81l9zQB!3-AL_t(|0qwy(YgShP0O04I+}9cf2O~s{ng#<>5iGTWsAw&U z{)0|V9ok6|cbgXx2R9d?EeLKkpd#wzBB&8MMLVb{et{`MJ0yO^;Co$L42gzw<;XqH zQ>azdOSObz5|1dRB&JY{YAyC-7kV&>Tlfv9DLN8Simuv(6My*H|9y|m7{F19=n_@c z!tdB(dL=e8G)nuP1O#zy#SSxDgdZd#h-)26v#JKE=1D-%a6w$7m@=#49>p682;#bq z%Vsu?KO~|{RP7E!cd-G>yze!RP@I&AF4480GISL!EJh0xxPe3Xm3Ac&g<4g;=*4S1 zpqP?nS(as4mVaeQD9p_-81U)zVV4?|=I)_Sr==!A!vzf&G+fYdLBjGL07v3eV84shuL&m}nNkkCQ&#W0>g&C}6@FxZ(B7cbJTUMGypjgjRi3lRM8I1G%JP#xyh-jFx^NhTuV;WPu9p>6O{+5U?bMqYv z)40f!KHjWi@dDISJcyX2|cX=r> zrJ9T~F*eE_i3=JoXt@t|nf+bw00000 LNkvXXu0mjfEBFCE delta 598 zcmV-c0;&Dh1n&fpB!4zZL_t(|ob8!Uh)rP_hacA@VPzJUBqb|ZSj^6bl%*&uD?%2m zW+j72$)6H67L2GN#loLSiN%zfurU@GOc6zg9F+RUfZs~UCe0SV)&Ua4Ur=H!s z^?uKtbI-XQFpQLO-EyZKkT>KhxlYcBy;%;p9yu&slT~?AE`N-@VTQP+@>z?+H~c9N z#@;eRTyMhR8-9|jVsDxOt|Eup2;kiod)o|f^W{jJ0d~dSHUs=Oyt~!L;r;i(=Gfb2 z#P3^QD8QIp7JJi-aEs;Lgu~b5p4eMvj9V_R%X-t{Kgj)ZZtM+nz;(&>^1z>`z`b%s z?5zyLFbu;mjDHlUM6fWdmZS1vj=sy~WNqRDT$6n{`Y65iJI4ZG0W5$8zyer+z8tjx z>8;;6!2pZk$ZojY5BKEIKDgQqN0Vj(mM6egxbOnTe!%o!uY#su>v47mT$*VvU^xP8 zg7Y8Y^Gt6=zQETl(BEn{U^xQlSIg7Td$A|_AvF&UwSSsVu=oL1!ifo(2nF~C-+SOp z^L)VK2iO4TCZQGzFa`C0xD1LPU@e^XpKd6CB28{otmgTE#Sc)?b^RP(g#wJj`z3I! zc|KtA19*F3u$FKc)M4s4+-x-)up9xrXW@1gs;&O@ufT(@+1~-YR4?}n0@k@C7 z2FCBh$WL~-m^2fxTmc&T`~3nq+|J(yEN6fewE*d@-#Hcl3t$2M`vAjoFh@soIoT{& k6G6i;48t%C!!Xk27lHf+HLb4BrvLx|07*qoM6N<$f=gK_Pyhe` diff --git a/test/integration/render-tests/within/paint-icon/style.json b/test/integration/render-tests/within/paint-icon/style.json index ca58a1f59cf..6bdeaa21840 100644 --- a/test/integration/render-tests/within/paint-icon/style.json +++ b/test/integration/render-tests/within/paint-icon/style.json @@ -7,7 +7,7 @@ } }, "zoom": 2, - "center": [3.5, 3.5], + "center": [3.25, 3.25], "sources": { "points": { "type": "geojson", diff --git a/test/integration/render-tests/within/paint-text/expected.png b/test/integration/render-tests/within/paint-text/expected.png index c44aed8ae6110d13e0e4d52486182890c3ef66b1..d0eeffe0d4977c7f347db709d8c673dfb97a9602 100644 GIT binary patch delta 847 zcmV-V1F-zn2IdBkB!4YQL_t(|0qwzCXq9yU0Px@UKWCeHnVG_>!!OMe} zLTDl(f)W%|5ZFT($sSr4@X|wsJxTOn%6f^2LV8Fo5XDN&n3krcS!${C!se!~oquDA zU>TC$em|L%OB5|8cnZPQ&XIiRy2YSRQcwXw@p2fCNq=}Ab1(^Yu86_^dztu{ z3Q63sn8!wZPV$aEg$9zP7>Au$gEjb`f zS8_3sBqV#$fq(B%@Q)lK@<`kSf|5&)5_e+)e#Eb6BhJEnEWs!|fgW7IND32GKu{V- zQP3y3gyk5HHN;oZfM>9tWEbAXT#Q2tW|O=|9O~8-6Jp{&Vhu5;PYkXSgDYY+F(;GB zWHOmdCX>mOOQJZ<7^{5hF#E8bz{Q>C8-DHo(%kbiPP$^|JGq+F14LCOUwmsg}g zc?TtQG9s!C4iMQ=FLx=-j}b zuQ@!Q@vpG)MYe6?WFxh&)3}JSIl8;(S;^!`_W{noK*It?H*nkIOsk@+k=m`Ca&5!$ zv3?NTfPXf+&(W<{f!Id(NNQSW8qF}h-etjLhK^xaGt-)=TgIHTTzG&x2U0VHntBGS zfS|;hsIO(Z=vXWz4Ys{(=&yPfJ9s;4t% zI%9sL?KE9`Xg|pDk=(VNBa!GPkXK@@oYAX=y?>iIQAf?6Tw2b3joiDIO;0j)Gsk;~ z5rfFxP0cYbDzm&2xWGl1eZn8}*|nN^r)eKR)jGae!juo#{syg6xTl$i7V^a*j;-dA zqnw(_XUZ&5oMt30o5+w~=xnFQWs|8LLG=;3ex`E*gZI$ULmb6`A>{YcF@owwYJaC| zA4un2TLTvB2Z`b|BT+sLSgaoeDHo(%ka9uF1t}M#T##~k#hW3M$z(E_OeT}bWHPs= ZzX5Zs;C9}BwVFI5g`|c&H-GnWo%jx#Wo>IUGw5W6 zk1XE7Ak)ZPQbd-Mv5uD0by;&rC5bloQfwZ{B)5r& z#F*W&(-i)itTiz$dAvPtTN|aR)4yIj4`oZ0|q#jzW?$uU?X`$Mw;^u4EWmlaNC{rxJFZg0&eE6D^h-1G#O8J|q1 znCtOmMQfu}sZ=VJN~Kb%y1QHs&~3!u@u(vCHd1d(Wq%!%7;uxgZKNP@>H9A=0BV35 zpawt0a2lN&L=s^Bk;M2cE0Ef`Q!Ui&S18}hevE9#r?ZDMN z{LEr<{HO9ou9JXU+y(o?fP)KwGqJ#N#=4_`Bje1q5C@zHXN=4#11cv1$swGFU@`zE zxisL>2Y;Y0)D$oeNO=d;RB|^o08OWWoP9vnXPUGGIJ+6R!slO;fqOFk9cXzE)NTRN z(Gv`~50sPu6(fM7yMfFmpg9IO_853s2$V}~I*=zTp2=e*J3wMxJz)n(5$priTRKs2k=wABLPT7Fd86l z16f_PNjMR>EU!sYunWjI0A%wOa976qv-?ZD%p1m|a308&xOg^51fu~qp#}slegCBf zKsOCg1JnSh0lnLRB67nt<8 diff --git a/test/integration/render-tests/within/paint-text/style.json b/test/integration/render-tests/within/paint-text/style.json index 8749edb1721..09a9571d34f 100644 --- a/test/integration/render-tests/within/paint-text/style.json +++ b/test/integration/render-tests/within/paint-text/style.json @@ -7,7 +7,7 @@ } }, "zoom": 2, - "center": [3.5, 3.5], + "center": [3.25, 3.25], "sources": { "points": { "type": "geojson", From dea2862f51ee4f6d7ab1450a92f7603d4dcaa4e8 Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 27 Feb 2020 11:35:31 +0200 Subject: [PATCH 06/14] Support Line within Polygon --- .../expression/definitions/within.js | 181 ++++++++++++++++-- 1 file changed, 165 insertions(+), 16 deletions(-) diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 8c46cb157d2..e661060bc25 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -15,46 +15,192 @@ import Point from '@mapbox/point-geometry'; type GeoJSONPolygons =| GeoJSONPolygon | GeoJSONMultiPolygon; +type BBox = [number, number, number, number]; + +function calcBBox(bbox: BBox, geom, type) { + if (type === 'Point') { + updateBBox(bbox, geom); + } else if (type === 'MultiPoint' || type === 'LineString') { + for (let i = 0; i < geom.length; ++i) { + updateBBox(bbox, geom[i]); + } + } else if (type === 'Polygon' || type === 'MultiLineString') { + for (let i = 0; i < geom.length; i++) { + for (let j = 0; j < geom[i].length; j++) { + updateBBox(bbox, geom[i][j]); + } + } + + } else if (type === 'MultiPolygon') { + for (let i = 0; i < geom.length; i++) { + for (let j = 0; j < geom[i].length; j++) { + for (let k = 0; k < geom[i][j].length; k++) { + updateBBox(bbox, geom[i][j][k]); + } + } + } + } +} + +function updateBBox(bbox: BBox, coord: Point) { + bbox[0] = Math.min(bbox[0], coord[0]); + bbox[1] = Math.min(bbox[1], coord[1]); + bbox[2] = Math.max(bbox[2], coord[0]); + bbox[3] = Math.max(bbox[3], coord[1]); +} + +function boxWithinBox(bbox1, bbox2) { + if (bbox1[0] <= bbox2[0]) return false; + if (bbox1[2] >= bbox2[2]) return false; + if (bbox1[1] <= bbox2[1]) return false; + if (bbox1[3] >= bbox2[3]) return false; + return true; +} + +function getLngLatPoint(coord: Point, canonical) { + const tilesAtZoom = Math.pow(2, canonical.z); + const x = (coord.x / EXTENT + canonical.x) / tilesAtZoom; + const y = (coord.y / EXTENT + canonical.y) / tilesAtZoom; + const p = new MercatorCoordinate(x, y).toLngLat(); + + return [p.lng, p.lat]; +} + +function getLngLatPoints(line, canonical) { + const coords = []; + for (let i = 0; i < line.length; ++i) { + coords.push(getLngLatPoint(line[i], canonical)); + } + return coords; +} + function rayIntersect(p, p1, p2) { return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]); } // ray casting algorithm for detecting if point is in polygon -function pointWithinPolygon(rings, p) { +function pointWithinPolygon(point, rings) { let inside = false; for (let i = 0, len = rings.length; i < len; i++) { const ring = rings[i]; for (let j = 0, len2 = ring.length, k = len2 - 1; j < len2; k = j++) { - if (rayIntersect(p, ring[j], ring[k])) inside = !inside; + if (rayIntersect(point, ring[j], ring[k])) inside = !inside; } } return inside; } -function pointWithinPolygons(polygons, lngLat) { - const p = [lngLat.lng, lngLat.lat]; +function pointWithinPolygons(point, polygons) { if (polygons.type === 'Polygon') { - return pointWithinPolygon(polygons.coordinates, p); + return pointWithinPolygon(point, polygons.coordinates); } for (let i = 0; i < polygons.coordinates.length; i++) { - if (!pointWithinPolygon(polygons.coordinates[i], p)) return false; + if (!pointWithinPolygon(point, polygons.coordinates[i])) return false; } return true; } -function getMercatorPoint(coord: Point, canonical: CanonicalTileID) { - const tilesAtZoom = Math.pow(2, canonical.z); - const x = (coord.x / EXTENT + canonical.x) / tilesAtZoom; - const y = (coord.y / EXTENT + canonical.y) / tilesAtZoom; - return new MercatorCoordinate(x, y); +// a, b are end points for line segment1, c and d are end points for line segment2 +function lineIntersectLine(a, b, c, d) { + const perp = (v1, v2) => { return (v1[0] * v2[1] - v1[1] * v2[0]); }; + + // check if two segments are parallel or not + // precondition is end point a, b is inside polygon, if line a->b is + // parallel to polygon edge c->d, then a->b won't intersect with c->d + const vectorP = [b[0] - a[0], b[1] - a[1]]; + const vectorQ = [d[0] - c[0], d[1] - c[1]]; + if (perp(vectorQ, vectorP) === 0) return false; + + // check if p1 and p2 are in different sides of line segment q1->q2 + const twoSided = (p1, p2, q1, q2) => { + // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3) + const x1 = p1[0] - q1[0]; + const y1 = p1[1] - q1[1]; + const x2 = p2[0] - q1[0]; + const y2 = p2[1] - q1[1]; + const x3 = q2[0] - q1[0]; + const y3 = q2[1] - q1[1]; + if ((x1 * y3 - x3 * y1) * (x2 * y3 - x3 * y2) < 0) return true; + return false; + }; + + // If lines are intersecting with each other, the relative location should be: + // a and b lie in different sides of segment c->d + // c and d lie in different sides of segment a->b + if (twoSided(a, b, c, d) && twoSided(c, d, a, b)) return true; + return false; +} + +function lineIntersectPolygon(p1, p2, polygon) { + for (let i = 0; i < polygon.length; ++i) { + const ring = polygon[i]; + // loop through every edge of the ring + for (let j = 0; j < ring.length - 1; ++j) { + if (lineIntersectLine(p1, p2, ring[j], ring[j + 1])) { + return true; + } + } + } + return false; +} + +function lineStringWithinPolygon(line, polygon) { + // First, check if geometry points of line segments are all inside polygon + for (let i = 0; i < line.length; ++i) { + if (!pointWithinPolygon(line[i], polygon)) { + return false; + } + } + + // Second, check if there is line segment intersecting polygon edge + for (let i = 0; i < line.length - 1; ++i) { + if (lineIntersectPolygon(line[i], line[i + 1], polygon)) { + return false; + } + } + return true; +} + +function lineStringWithinPolygons(line, polygons) { + if (polygons.type === 'Polygon') { + return lineStringWithinPolygon(line, polygons.coordinates); + } + for (let i = 0; i < polygons.coordinates.length; i++) { + if (!lineStringWithinPolygon(line, polygons.coordinates[i])) return false; + } + return true; } -function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons) { +function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { + const pointBBox = [Infinity, Infinity, -Infinity, -Infinity]; + const lngLatPoints = []; for (const points of feature.geometry) { for (const point of points) { - if (!pointWithinPolygons(polygonGeometry, getMercatorPoint(point, canonical).toLngLat())) return false; + lngLatPoints.push(getLngLatPoint(point, canonical)); + updateBBox(pointBBox, point); } } + if (!boxWithinBox(pointBBox, polyBBox)) return false; + for (const points of feature.geometry) { + for (const point of points) { + if (!pointWithinPolygons(getLngLatPoint(point, canonical), polygonGeometry)) return false; + } + } + return true; +} + +function linesWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { + const lineBBox = [Infinity, Infinity, -Infinity, -Infinity]; + const lineCoords = []; + for (const line of feature.geometry) { + const lineCoord = getLngLatPoints(line, canonical); + lineCoords.push(lineCoord); + calcBBox(lineBBox, lineCoord, 'LineString'); + } + if (!boxWithinBox(lineBBox, polyBBox)) return false; + for (let i = 0; i < lineCoords.length; ++i) { + if (!lineStringWithinPolygons(lineCoords[i], polygonGeometry)) return false; + } return true; } @@ -62,11 +208,14 @@ class Within implements Expression { type: Type; geojson: GeoJSON geometries: GeoJSONPolygons; + polyBBox: BBox; constructor(geojson: GeoJSON, geometries: GeoJSONPolygons) { this.type = BooleanType; this.geojson = geojson; this.geometries = geometries; + this.polyBBox = [Infinity, Infinity, -Infinity, -Infinity]; + calcBBox(this.polyBBox, this.geometries.coordinates, this.geometries.type); } static parse(args: $ReadOnlyArray, context: ParsingContext) { @@ -95,9 +244,9 @@ class Within implements Expression { evaluate(ctx: EvaluationContext) { if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'Point') { - return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geometries); - } else if (ctx.geometryType() === 'LineString') { - return true; + return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geometries, this.polyBBox); + } else if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'LineString') { + return linesWithinPolygons(ctx.feature, ctx.canonical, this.geometries, this.polyBBox); } return false; } From ba634b94ae3011fb66b75bda99f06676745d392d Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 27 Feb 2020 11:55:39 +0200 Subject: [PATCH 07/14] Add support for using layout property with within expression --- src/data/bucket/symbol_bucket.js | 8 +-- src/style/style_layer/symbol_style_layer.js | 5 +- src/symbol/symbol_layout.js | 54 ++++++++++----------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index d37431143d2..4f237842f6d 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -439,7 +439,7 @@ class SymbolBucket implements Bucket { // Expression evaluation will automatically coerce to Formatted // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('text-field', newFeature, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('text-field', newFeature, canonical, availableImages); const formattedText = Formatted.factory(resolvedTokens); if (containsRTLText(formattedText)) { this.hasRTLText = true; @@ -458,7 +458,7 @@ class SymbolBucket implements Bucket { // Expression evaluation will automatically coerce to Image // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('icon-image', newFeature, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('icon-image', newFeature, canonical, availableImages); if (resolvedTokens instanceof ResolvedImage) { icon = resolvedTokens; } else { @@ -470,7 +470,7 @@ class SymbolBucket implements Bucket { continue; } const sortKey = this.sortFeaturesByKey ? - symbolSortKey.evaluate(newFeature, {}) : + symbolSortKey.evaluate(newFeature, {}, canonical) : undefined; const symbolFeature: SymbolFeature = { @@ -491,7 +491,7 @@ class SymbolBucket implements Bucket { } if (text) { - const fontStack = textFont.evaluate(newFeature, {}).join(','); + const fontStack = textFont.evaluate(newFeature, {}, canonical).join(','); const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; this.allowVerticalPlacement = this.writingModes && this.writingModes.indexOf(WritingMode.vertical) >= 0; for (const section of text.sections) { diff --git a/src/style/style_layer/symbol_style_layer.js b/src/style/style_layer/symbol_style_layer.js index 2963e6aca95..249f0f4b878 100644 --- a/src/style/style_layer/symbol_style_layer.js +++ b/src/style/style_layer/symbol_style_layer.js @@ -29,6 +29,7 @@ import type EvaluationParameters from '../evaluation_parameters'; import type {LayerSpecification} from '../../style-spec/types'; 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'; @@ -92,8 +93,8 @@ class SymbolStyleLayer extends StyleLayer { this._setPaintOverrides(); } - getValueAndResolveTokens(name: *, feature: Feature, availableImages: Array) { - const value = this.layout.get(name).evaluate(feature, {}, availableImages); + getValueAndResolveTokens(name: *, feature: Feature, canonical: CanonicalTileID, availableImages: Array) { + 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); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index 60a116f6765..d3e808801be 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -169,21 +169,21 @@ export function performSymbolLayout(bucket: SymbolBucket, if (bucket.textSizeData.kind === 'composite') { const {minZoom, maxZoom} = bucket.textSizeData; sizes.compositeTextSizes = [ - unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(minZoom)), - unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(maxZoom)) + unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(minZoom), canonical), + unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(maxZoom), canonical) ]; } if (bucket.iconSizeData.kind === 'composite') { const {minZoom, maxZoom} = bucket.iconSizeData; sizes.compositeIconSizes = [ - unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(minZoom)), - unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(maxZoom)) + unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(minZoom), canonical), + unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(maxZoom), canonical) ]; } - sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1)); - sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1)); + sizes.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1), canonical); + sizes.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate(new EvaluationParameters(bucket.zoom + 1), canonical); sizes.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate(new EvaluationParameters(18)); const lineHeight = layout.get('text-line-height') * ONE_EM; @@ -192,10 +192,10 @@ export function performSymbolLayout(bucket: SymbolBucket, const textSize = layout.get('text-size'); for (const feature of bucket.features) { - const fontstack = layout.get('text-font').evaluate(feature, {}).join(','); - const layoutTextSizeThisZoom = textSize.evaluate(feature, {}); - const layoutTextSize = sizes.layoutTextSize.evaluate(feature, {}); - const layoutIconSize = sizes.layoutIconSize.evaluate(feature, {}); + const fontstack = layout.get('text-font').evaluate(feature, {}, canonical).join(','); + const layoutTextSizeThisZoom = textSize.evaluate(feature, {}, canonical); + const layoutTextSize = sizes.layoutTextSize.evaluate(feature, {}, canonical); + const layoutIconSize = sizes.layoutIconSize.evaluate(feature, {}, canonical); const shapedTextOrientations = { horizontal: {}, @@ -205,14 +205,14 @@ export function performSymbolLayout(bucket: SymbolBucket, let textOffset: [number, number] = [0, 0]; if (text) { const unformattedText = text.toString(); - const spacing = layout.get('text-letter-spacing').evaluate(feature, {}) * ONE_EM; + const spacing = layout.get('text-letter-spacing').evaluate(feature, {}, canonical) * ONE_EM; const spacingIfAllowed = allowsLetterSpacing(unformattedText) ? spacing : 0; - const textAnchor = layout.get('text-anchor').evaluate(feature, {}); + const textAnchor = layout.get('text-anchor').evaluate(feature, {}, canonical); const variableTextAnchor = layout.get('text-variable-anchor'); if (!variableTextAnchor) { - const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}); + const radialOffset = layout.get('text-radial-offset').evaluate(feature, {}, canonical); // Layers with variable anchors use the `text-radial-offset` property and the [x, y] offset vector // is calculated at placement time instead of layout time if (radialOffset) { @@ -220,17 +220,17 @@ export function performSymbolLayout(bucket: SymbolBucket, // but doesn't actually specify what happens if you use both. We go with the radial offset. textOffset = evaluateVariableOffset(textAnchor, [radialOffset * ONE_EM, INVALID_TEXT_OFFSET]); } else { - textOffset = (layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any); + textOffset = (layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM): any); } } let textJustify = textAlongLine ? "center" : - layout.get('text-justify').evaluate(feature, {}); + layout.get('text-justify').evaluate(feature, {}, canonical); const symbolPlacement = layout.get('symbol-placement'); const maxWidth = symbolPlacement === 'point' ? - layout.get('text-max-width').evaluate(feature, {}) * ONE_EM : + layout.get('text-max-width').evaluate(feature, {}, canonical) * ONE_EM : 0; const addVerticalShapingForPointLabelIfNeeded = () => { @@ -298,8 +298,8 @@ export function performSymbolLayout(bucket: SymbolBucket, if (image) { shapedIcon = shapeIcon( imagePositions[feature.icon.name], - layout.get('icon-offset').evaluate(feature, {}), - layout.get('icon-anchor').evaluate(feature, {})); + layout.get('icon-offset').evaluate(feature, {}, canonical), + layout.get('icon-anchor').evaluate(feature, {}, canonical)); isSDFIcon = image.sdf; if (bucket.sdfIcons === undefined) { bucket.sdfIcons = image.sdf; @@ -367,7 +367,7 @@ function addFeature(bucket: SymbolBucket, textMaxSize = layoutTextSize; } const layout = bucket.layers[0].layout; - const iconOffset = layout.get('icon-offset').evaluate(feature, {}); + const iconOffset = layout.get('icon-offset').evaluate(feature, {}, canonical); const defaultHorizontalShaping = getDefaultHorizontalShaping(shapedTextOrientations.horizontal); const glyphSize = 24, fontScale = layoutTextSize / glyphSize, @@ -503,8 +503,8 @@ function addTextVertices(bucket: SymbolBucket, } } else if (sizeData.kind === 'composite') { textSizeData = [ - SIZE_PACK_FACTOR * sizes.compositeTextSizes[0].evaluate(feature, {}), - SIZE_PACK_FACTOR * sizes.compositeTextSizes[1].evaluate(feature, {}) + SIZE_PACK_FACTOR * sizes.compositeTextSizes[0].evaluate(feature, {}, canonical), + SIZE_PACK_FACTOR * sizes.compositeTextSizes[1].evaluate(feature, {}, canonical) ]; if (textSizeData[0] > MAX_PACKED_SIZE || textSizeData[1] > MAX_PACKED_SIZE) { warnOnce(`${bucket.layerIds[0]}: Value for "text-size" is >= ${MAX_GLYPH_ICON_SIZE}. Reduce your "text-size".`); @@ -588,14 +588,14 @@ function addSymbol(bucket: SymbolBucket, let textOffset0 = 0; let textOffset1 = 0; if (layer._unevaluatedLayout.getValue('text-radial-offset') === undefined) { - [textOffset0, textOffset1] = (layer.layout.get('text-offset').evaluate(feature, {}).map(t => t * ONE_EM): any); + [textOffset0, textOffset1] = (layer.layout.get('text-offset').evaluate(feature, {}, canonical).map(t => t * ONE_EM): any); } else { - textOffset0 = layer.layout.get('text-radial-offset').evaluate(feature, {}) * ONE_EM; + textOffset0 = layer.layout.get('text-radial-offset').evaluate(feature, {}, canonical) * ONE_EM; textOffset1 = INVALID_TEXT_OFFSET; } if (bucket.allowVerticalPlacement && shapedTextOrientations.vertical) { - const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}); + const textRotation = layer.layout.get('text-rotate').evaluate(feature, {}, canonical); const verticalTextRotation = textRotation + 90.0; const verticalShaping = shapedTextOrientations.vertical; verticalTextCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, verticalShaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, verticalTextRotation); @@ -630,8 +630,8 @@ function addSymbol(bucket: SymbolBucket, } } else if (sizeData.kind === 'composite') { iconSizeData = [ - SIZE_PACK_FACTOR * sizes.compositeIconSizes[0].evaluate(feature, {}), - SIZE_PACK_FACTOR * sizes.compositeIconSizes[1].evaluate(feature, {}) + SIZE_PACK_FACTOR * sizes.compositeIconSizes[0].evaluate(feature, {}, canonical), + SIZE_PACK_FACTOR * sizes.compositeIconSizes[1].evaluate(feature, {}, canonical) ]; if (iconSizeData[0] > MAX_PACKED_SIZE || iconSizeData[1] > MAX_PACKED_SIZE) { warnOnce(`${bucket.layerIds[0]}: Value for "icon-size" is >= ${MAX_GLYPH_ICON_SIZE}. Reduce your "icon-size".`); @@ -680,7 +680,7 @@ function addSymbol(bucket: SymbolBucket, if (!textCollisionFeature) { key = murmur3(shaping.text); - const textRotate = layer.layout.get('text-rotate').evaluate(feature, {}); + const textRotate = layer.layout.get('text-rotate').evaluate(feature, {}, canonical); // As a collision approximation, we can use either the vertical or any of the horizontal versions of the feature // We're counting on all versions having similar dimensions textCollisionFeature = new CollisionFeature(collisionBoxArray, line, anchor, featureIndex, sourceLayerIndex, bucketIndex, shaping, textBoxScale, textPadding, textAlongLine, bucket.overscaling, textRotate); From 7cd12fd3f05487ddc755a21d9d46539b8c00e6a2 Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 27 Feb 2020 11:59:43 +0200 Subject: [PATCH 08/14] Update render tests for within layout style tests --- test/ignores.json | 3 ++- .../within/layout-text/expected.png | Bin 645 -> 631 bytes .../render-tests/within/layout-text/style.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ignores.json b/test/ignores.json index f97f8333592..c901c204d4b 100644 --- a/test/ignores.json +++ b/test/ignores.json @@ -20,5 +20,6 @@ "render-tests/text-variable-anchor/all-anchors-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", "render-tests/fill-pattern/update-feature-state": "https://github.com/mapbox/mapbox-gl-js/issues/7207", "render-tests/text-size/zero": "https://github.com/mapbox/mapbox-gl-js/issues/9161", - "render-tests/text-variable-anchor/left-top-right-buttom-offset-tile-map-mode": "skip - mapbox-gl-js does not need to render tiles" + "render-tests/text-variable-anchor/left-top-right-buttom-offset-tile-map-mode": "skip - mapbox-gl-js does not need to render tiles", + "render-tests/within/paint-line": "https://github.com/mapbox/mapbox-gl-js/issues/7023" } diff --git a/test/integration/render-tests/within/layout-text/expected.png b/test/integration/render-tests/within/layout-text/expected.png index 1fdeb343485ca2625bd61f3d3363ca02fd63e781..ea25b5a0a40265bcc9e44ce99e30c8a86eb26829 100644 GIT binary patch delta 606 zcmV-k0-^nd1@{DyB!50hL_t(|0qw!RYh6_U0O0SO`;r%n;;#yJD56BoAksmKTMB}r zYpO14!9Sn~E}i7Q_79Na;^rTqL#)NYsT3T71hv)0YPC}eMO*NZCh6-vK4_ML5p3>z zx#gViQ)Dh^QNy!=*YIfKx4_qd8~#J03f#6wDlKVvHSlR-4u8YI65go!X24B5s5xD; zKH!Qy5VRmsCB6(?!iRw-aR6uV8Lr_K9LFtuj7!+UM>wCj9ylIoOi|R1#{t2|XM_bjQ=^<9Dp%OyG+^W6Gj-REXGgx0{%;vsEPg88~MTMD4hNzp!9iVg~cL z>}~;l9Z_k)fPYcVLwL95dd0bGELYxUeuL2`{Ctt24u~e~F=m!He2PV5M_4$`5gidt$>&^nk8jU% z;UN1TAnJf<3Py~sv0-eBrXoaw4u~=rO-OP_L)zq4AQ_9;WTYd4dv^kJb}-MrM7qqz zi#+%f!&lp+Cz*YM*^3PQe?MSZi+U#HSJrL&k-u%b%GOo3bVQW7D05NfqRd5^i!v8w su4i`^O;^IQq9}@@D2k#eieh*E0kW-&m!&8&Gynhq07*qoM6N<$f+%PlGynhq delta 620 zcmV-y0+aps1ce2VB!5gvL_t(|ob8&;OH^SL#-FBIB$Fm0T4@MHZ31y&N}Il{Y?0zR zS_K8|Y9mOPSor~gP$IU9LebJ9NTfDu##EF_qL>Or79y?u6Y_cKT@1mUN$>T(UFP}0 zkC}VszHrVn=gw*XA%tIk1KIQdEv7No{e%h!zDPgPahjk#w0|VcncehiNG)z29HNwd zp_gdB-GTx7&};%dLksM^h4y+Iy-a)Ub)M@vHVm7ux8MBsq*^tdrw8eKT4U?Ac2iE$ z5jsU5(+0tB54Ea=r8(&o^?HkZ#cvudd_Y{c2a$ZeI!oNy|mKSO>jre@N}2E&V>*{2qA=6 z4>1S+H0G Date: Thu, 27 Feb 2020 16:18:56 +0200 Subject: [PATCH 09/14] Fix feature conversion --- src/data/bucket.js | 3 +- src/data/bucket/circle_bucket.js | 11 ++++--- src/data/bucket/fill_bucket.js | 15 ++++----- src/data/bucket/fill_extrusion_bucket.js | 12 +++---- src/data/bucket/line_bucket.js | 17 +++++----- src/data/bucket/symbol_bucket.js | 17 +++++----- src/data/feature_index.js | 3 +- src/data/program_configuration.js | 25 ++++++++------- src/source/tile.js | 6 ++-- src/source/worker_tile.js | 3 ++ .../expression/definitions/within.js | 31 +++++++++---------- .../expression/evaluation_context.js | 8 +++++ src/style-spec/expression/index.js | 6 ++-- src/style-spec/expression/parsing_context.js | 1 - src/style-spec/reference/v8.json | 18 +++++------ src/style-spec/types.js | 1 - src/style/style_layer/circle_style_layer.js | 5 ++- .../style_layer/fill_extrusion_style_layer.js | 4 +-- src/style/style_layer/line_style_layer.js | 4 +-- src/symbol/symbol_layout.js | 2 +- src/ui/map.js | 1 + src/util/web_worker_transfer.js | 1 + 22 files changed, 101 insertions(+), 93 deletions(-) diff --git a/src/data/bucket.js b/src/data/bucket.js index 710e9e08dd6..5bbe276d4b2 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -8,6 +8,7 @@ import type Context from '../gl/context'; import type {FeatureStates} from '../source/source_state'; import type {ImagePosition} from '../render/image_atlas'; import type {CanonicalTileID} from '../source/tile_id'; + export type BucketParameters = { index: number, layers: Array, @@ -75,7 +76,7 @@ export interface Bucket { +stateDependentLayers: Array; +stateDependentLayerIds: Array; populate(features: Array, options: PopulateParameters, canonical: CanonicalTileID): void; - update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[string]: ImagePosition}): void; + update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[_: string]: ImagePosition}): void; isEmpty(): boolean; upload(context: Context): void; diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index f6c737447ca..50abc07c21f 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -10,6 +10,7 @@ import loadGeometry from '../load_geometry'; import EXTENT from '../extent'; import {register} from '../../util/web_worker_transfer'; import EvaluationParameters from '../../style/evaluation_parameters'; + import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, @@ -85,11 +86,11 @@ class CircleBucket implements Bucke circleSortKey = ((styleLayer: any): CircleStyleLayer).layout.get('circle-sort-key'); } for (const {feature, id, index, sourceLayerIndex} of features) { - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) { - const geometry = loadGeometry(feature); + const geometry = loadGeometry(feature); + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; + if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) { const sortKey = circleSortKey ? - circleSortKey.evaluate(newFeature, {}, canonical) : + circleSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const bucketFeature: BucketFeature = { @@ -187,7 +188,7 @@ class CircleBucket implements Bucke } } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, {}); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, {}, canonical); } } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index c62fd28ece5..0f11c373e04 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -14,6 +14,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; + import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, @@ -79,12 +80,12 @@ class FillBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; - const geometry = loadGeometry(feature); + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + const sortKey = fillSortKey ? - fillSortKey.evaluate(newFeature, {}, canonical, options.availableImages) : + fillSortKey.evaluate(evaluationFeature, {}, canonical, options.availableImages) : undefined; const bucketFeature: BucketFeature = { @@ -130,7 +131,7 @@ class FillBucket implements Bucket { this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { for (const feature of this.patternFeatures) { this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } @@ -163,7 +164,7 @@ class FillBucket implements Bucket { this.segments2.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { @@ -217,7 +218,7 @@ class FillBucket implements Bucket { triangleSegment.vertexLength += numVertices; triangleSegment.primitiveLength += indices.length / 3; } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } } diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 7f913c08743..93c628d9628 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -17,6 +17,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; + import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, @@ -92,10 +93,9 @@ class FillExtrusionBucket implements Bucket { this.hasPattern = hasPattern('fill-extrusion', this.layers, options); for (const {feature, id, index, sourceLayerIndex} of features) { - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; - const geometry = loadGeometry(feature); + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const patternFeature: BucketFeature = { id, @@ -121,7 +121,7 @@ class FillExtrusionBucket implements Bucket { } } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { for (const feature of this.features) { const {geometry} = feature; this.addFeature(feature, geometry, feature.index, canonical, imagePositions); @@ -158,7 +158,7 @@ class FillExtrusionBucket implements Bucket { this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { for (const polygon of classifyRings(geometry, EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { @@ -264,7 +264,7 @@ class FillExtrusionBucket implements Bucket { segment.vertexLength += numVertices; } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } } diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 6e073e81121..fd4a18e2918 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -13,6 +13,7 @@ import {register} from '../../util/web_worker_transfer'; import {hasPattern, addPatternDependencies} from './pattern_bucket_features'; import loadGeometry from '../load_geometry'; import EvaluationParameters from '../../style/evaluation_parameters'; + import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, @@ -122,12 +123,12 @@ class LineBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), newFeature, canonical)) continue; - const geometry = loadGeometry(feature); + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; + if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + const sortKey = lineSortKey ? - lineSortKey.evaluate(newFeature, {}, canonical) : + lineSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const bucketFeature: BucketFeature = { @@ -173,7 +174,7 @@ class LineBucket implements Bucket { this.programConfigurations.updatePaintArrays(states, vtLayer, this.stateDependentLayers, imagePositions); } - addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeatures(options: PopulateParameters, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { for (const feature of this.patternFeatures) { this.addFeature(feature, feature.geometry, feature.index, canonical, imagePositions); } @@ -204,7 +205,7 @@ class LineBucket implements Bucket { this.segments.destroy(); } - addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addFeature(feature: BucketFeature, geometry: Array>, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { const layout = this.layers[0].layout; const join = layout.get('line-join').evaluate(feature, {}); const cap = layout.get('line-cap'); @@ -216,7 +217,7 @@ class LineBucket implements Bucket { } } - addLine(vertices: Array, feature: BucketFeature, join: string, cap: string, miterLimit: number, roundLimit: number, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + addLine(vertices: Array, feature: BucketFeature, join: string, cap: string, miterLimit: number, roundLimit: number, index: number, canonical: CanonicalTileID, imagePositions: {[_: string]: ImagePosition}) { this.distance = 0; this.scaledDistance = 0; this.totalDistance = 0; @@ -462,7 +463,7 @@ class LineBucket implements Bucket { } } - this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, canonical, imagePositions); + this.programConfigurations.populatePaintArrays(this.layoutVertexArray.length, feature, index, imagePositions, canonical); } /** diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 4f237842f6d..7d6ed7f7e77 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -39,6 +39,7 @@ import EvaluationParameters from '../../style/evaluation_parameters'; import Formatted from '../../style-spec/expression/types/formatted'; import ResolvedImage from '../../style-spec/expression/types/resolved_image'; import {plugin as globalRTLTextPlugin, getRTLTextPluginStatus} from '../../source/rtl_text_plugin'; + import type {CanonicalTileID} from '../../source/tile_id'; import type { Bucket, @@ -430,8 +431,8 @@ class SymbolBucket implements Bucket { const globalProperties = new EvaluationParameters(this.zoom); for (const {feature, id, index, sourceLayerIndex} of features) { - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!layer._featureFilter(globalProperties, newFeature, canonical)) { + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry: loadGeometry(feature)}; + if (!layer._featureFilter(globalProperties, evaluationFeature, canonical)) { continue; } let text: Formatted | void; @@ -439,7 +440,7 @@ class SymbolBucket implements Bucket { // Expression evaluation will automatically coerce to Formatted // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('text-field', newFeature, canonical, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('text-field', evaluationFeature, canonical, availableImages); const formattedText = Formatted.factory(resolvedTokens); if (containsRTLText(formattedText)) { this.hasRTLText = true; @@ -449,7 +450,7 @@ class SymbolBucket implements Bucket { getRTLTextPluginStatus() === 'unavailable' || // We don't intend to lazy-load the rtl text plugin, so proceed with incorrect shaping this.hasRTLText && globalRTLTextPlugin.isParsed() // Use the rtlText plugin to shape text ) { - text = transformText(formattedText, layer, newFeature); + text = transformText(formattedText, layer, evaluationFeature); } } @@ -458,7 +459,7 @@ class SymbolBucket implements Bucket { // Expression evaluation will automatically coerce to Image // but plain string token evaluation skips that pathway so do the // conversion here. - const resolvedTokens = layer.getValueAndResolveTokens('icon-image', newFeature, canonical, availableImages); + const resolvedTokens = layer.getValueAndResolveTokens('icon-image', evaluationFeature, canonical, availableImages); if (resolvedTokens instanceof ResolvedImage) { icon = resolvedTokens; } else { @@ -470,7 +471,7 @@ class SymbolBucket implements Bucket { continue; } const sortKey = this.sortFeaturesByKey ? - symbolSortKey.evaluate(newFeature, {}, canonical) : + symbolSortKey.evaluate(evaluationFeature, {}, canonical) : undefined; const symbolFeature: SymbolFeature = { @@ -491,7 +492,7 @@ class SymbolBucket implements Bucket { } if (text) { - const fontStack = textFont.evaluate(newFeature, {}, canonical).join(','); + const fontStack = textFont.evaluate(evaluationFeature, {}, canonical).join(','); const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; this.allowVerticalPlacement = this.writingModes && this.writingModes.indexOf(WritingMode.vertical) >= 0; for (const section of text.sections) { @@ -639,7 +640,7 @@ class SymbolBucket implements Bucket { this.glyphOffsetArray.emplaceBack(glyphOffset[0]); if (i === quads.length - 1 || sectionIndex !== quads[i + 1].sectionIndex) { - arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, canonical, {}, sections && sections[sectionIndex]); + arrays.programConfigurations.populatePaintArrays(layoutVertexArray.length, feature, feature.index, {}, canonical, sections && sections[sectionIndex]); } } diff --git a/src/data/feature_index.js b/src/data/feature_index.js index cf750873e84..a113d96ba55 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -184,8 +184,7 @@ class FeatureIndex { const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); const sourceLayer = this.vtLayers[sourceLayerName]; const feature = sourceLayer.feature(featureIndex); - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (!filter(new EvaluationParameters(this.tileID.overscaledZ), newFeature)) + if (!filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) return; const id = this.getId(feature, sourceLayerName); diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 123a20706c4..77ad3f75959 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -7,8 +7,8 @@ import {register} from '../util/web_worker_transfer'; import {PossiblyEvaluatedPropertyValue} from '../style/properties'; import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray} from './array_types'; import {clamp} from '../util/util'; + import loadGeometry from './load_geometry'; -import type {CanonicalTileID} from '../source/tile_id'; import EvaluationParameters from '../style/evaluation_parameters'; import FeaturePositionMap from './feature_position_map'; import { @@ -19,6 +19,7 @@ import { type UniformLocations } from '../render/uniform_binding'; +import type {CanonicalTileID} from '../source/tile_id'; import type Context from '../gl/context'; import type {TypedStyleLayer} from '../style/style_layer/typed_style_layer'; import type {CrossfadeParameters} from '../style/evaluation_parameters'; @@ -78,8 +79,8 @@ function packColor(color: Color): [number, number] { */ interface AttributeBinder { - populatePaintArray(length: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection): void; - updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[string]: ImagePosition}): void; + populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection): void; + updatePaintArray(start: number, length: number, feature: Feature, featureState: FeatureState, imagePositions: {[_: string]: ImagePosition}): void; upload(Context): void; destroy(): void; } @@ -162,7 +163,7 @@ class SourceExpressionBinder implements AttributeBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { const start = this.paintVertexArray.length; const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, canonical, [], formattedSection); this.paintVertexArray.resize(newLength); @@ -233,7 +234,7 @@ class CompositeExpressionBinder implements AttributeBinder, UniformBinder { this.paintVertexArray = new PaintVertexArray(); } - populatePaintArray(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArray(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, canonical, [], formattedSection); const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, canonical, [], formattedSection); const start = this.paintVertexArray.length; @@ -320,7 +321,7 @@ class CrossFadedCompositeBinder implements AttributeBinder { this.zoomOutPaintVertexArray = new PaintVertexArray(); } - populatePaintArray(length: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}) { + populatePaintArray(length: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}) { const start = this.zoomInPaintVertexArray.length; this.zoomInPaintVertexArray.resize(length); this.zoomOutPaintVertexArray.resize(length); @@ -443,11 +444,11 @@ export default class ProgramConfiguration { return binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder ? binder.maxValue : 0; } - populatePaintArrays(newLength: number, feature: Feature, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArrays(newLength: number, feature: Feature, imagePositions: {[_: string]: ImagePosition}, canonical?: CanonicalTileID, formattedSection?: FormattedSection) { for (const property in this.binders) { const binder = this.binders[property]; if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder || binder instanceof CrossFadedCompositeBinder) - (binder: AttributeBinder).populatePaintArray(newLength, feature, canonical, imagePositions, formattedSection); + (binder: AttributeBinder).populatePaintArray(newLength, feature, imagePositions, canonical, formattedSection); } } setConstantPatternPositions(posTo: ImagePosition, posFrom: ImagePosition) { @@ -473,8 +474,8 @@ export default class ProgramConfiguration { //AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255 const value = layer.paint.get(property); (binder: any).expression = value.value; - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, newFeature, featureStates[id], imagePositions); + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry: loadGeometry(feature)}; + (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, evaluationFeature, featureStates[id], imagePositions); dirty = true; } } @@ -571,9 +572,9 @@ export class ProgramConfigurationSet { this._bufferOffset = 0; } - populatePaintArrays(length: number, feature: Feature, index: number, canonical: CanonicalTileID, imagePositions: {[string]: ImagePosition}, formattedSection?: FormattedSection) { + populatePaintArrays(length: number, feature: Feature, index: number, imagePositions: {[_: string]: ImagePosition}, canonical: CanonicalTileID, formattedSection?: FormattedSection) { for (const key in this.programConfigurations) { - this.programConfigurations[key].populatePaintArrays(length, feature, canonical, imagePositions, formattedSection); + this.programConfigurations[key].populatePaintArrays(length, feature, imagePositions, canonical, formattedSection); } if (feature.id !== undefined) { diff --git a/src/source/tile.js b/src/source/tile.js index 7163147b040..2ad7dbf182d 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -12,7 +12,7 @@ import browser from '../util/browser'; import EvaluationParameters from '../style/evaluation_parameters'; import SourceFeatureState from '../source/source_state'; import {lazyLoadRTLTextPlugin} from './rtl_text_plugin'; -import loadGeometry from '../data/load_geometry'; + const CLOCK_SKEW_RETRY_TIMEOUT = 30000; import type {Bucket} from '../data/bucket'; @@ -307,8 +307,8 @@ class Tile { for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); - const newFeature = {type: feature.type, id: feature.id, properties: feature.properties, geometry: loadGeometry(feature)}; - if (filter(new EvaluationParameters(this.tileID.overscaledZ), newFeature)) { + const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties}; + if (filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature)) { const id = featureIndex.getId(feature, sourceLayer); const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id); (geojsonFeature: any).tile = coord; diff --git a/src/source/worker_tile.js b/src/source/worker_tile.js index 909541754bb..fd5e8d5d502 100644 --- a/src/source/worker_tile.js +++ b/src/source/worker_tile.js @@ -27,6 +27,7 @@ import type { WorkerTileCallback, } from '../source/worker_source'; import type {PromoteIdSpecification} from '../style-spec/types'; + class WorkerTile { tileID: OverscaledTileID; uid: string; @@ -65,6 +66,7 @@ class WorkerTile { parse(data: VectorTile, layerIndex: StyleLayerIndex, availableImages: Array, actor: Actor, callback: WorkerTileCallback) { this.status = 'parsing'; this.data = data; + this.collisionBoxArray = new CollisionBoxArray(); const sourceLayerCoder = new DictionaryCoder(Object.keys(data.layers).sort()); @@ -121,6 +123,7 @@ class WorkerTile { sourceLayerIndex, sourceID: this.source }); + bucket.populate(features, options, this.tileID.canonical); featureIndex.bucketLayerIDs.push(family.map((l) => l.id)); } diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index e661060bc25..ede9cd82a0b 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -6,9 +6,7 @@ import {BooleanType} from '../types'; import type {Expression} from '../expression'; import type ParsingContext from '../parsing_context'; import type EvaluationContext from '../evaluation_context'; -import type {CanonicalTileID} from '../../../source/tile_id'; import type {GeoJSON, GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types'; -import type {Feature} from '../index'; import MercatorCoordinate from '../../../geo/mercator_coordinate'; import EXTENT from '../../../data/extent'; import Point from '@mapbox/point-geometry'; @@ -30,7 +28,6 @@ function calcBBox(bbox: BBox, geom, type) { updateBBox(bbox, geom[i][j]); } } - } else if (type === 'MultiPolygon') { for (let i = 0; i < geom.length; i++) { for (let j = 0; j < geom[i].length; j++) { @@ -171,29 +168,27 @@ function lineStringWithinPolygons(line, polygons) { return true; } -function pointsWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { +function pointsWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { const pointBBox = [Infinity, Infinity, -Infinity, -Infinity]; const lngLatPoints = []; - for (const points of feature.geometry) { + for (const points of ctx.geometry()) { for (const point of points) { - lngLatPoints.push(getLngLatPoint(point, canonical)); + lngLatPoints.push(getLngLatPoint(point, ctx.canonicalID())); updateBBox(pointBBox, point); } } if (!boxWithinBox(pointBBox, polyBBox)) return false; - for (const points of feature.geometry) { - for (const point of points) { - if (!pointWithinPolygons(getLngLatPoint(point, canonical), polygonGeometry)) return false; - } + for (let i = 0; i < lngLatPoints.length; ++i) { + if (!pointWithinPolygons(lngLatPoints[i], polygonGeometry)) return false; } return true; } -function linesWithinPolygons(feature: Feature, canonical: CanonicalTileID, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { +function linesWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons, polyBBox: BBox) { const lineBBox = [Infinity, Infinity, -Infinity, -Infinity]; const lineCoords = []; - for (const line of feature.geometry) { - const lineCoord = getLngLatPoints(line, canonical); + for (const line of ctx.geometry()) { + const lineCoord = getLngLatPoints(line, ctx.canonicalID()); lineCoords.push(lineCoord); calcBBox(lineBBox, lineCoord, 'LineString'); } @@ -243,10 +238,12 @@ class Within implements Expression { } evaluate(ctx: EvaluationContext) { - if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'Point') { - return pointsWithinPolygons(ctx.feature, ctx.canonical, this.geometries, this.polyBBox); - } else if (ctx.feature != null && ctx.canonical != null && ctx.geometryType() === 'LineString') { - return linesWithinPolygons(ctx.feature, ctx.canonical, this.geometries, this.polyBBox); + if (ctx.geometry() != null && ctx.canonicalID() != null) { + if (ctx.geometryType() === 'Point') { + return pointsWithinPolygons(ctx, this.geometries, this.polyBBox); + } else if (ctx.geometryType() === 'LineString') { + return linesWithinPolygons(ctx, this.geometries, this.polyBBox); + } } return false; } diff --git a/src/style-spec/expression/evaluation_context.js b/src/style-spec/expression/evaluation_context.js index 3ea192a2566..1e7a6d587e1 100644 --- a/src/style-spec/expression/evaluation_context.js +++ b/src/style-spec/expression/evaluation_context.js @@ -35,6 +35,14 @@ class EvaluationContext { return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null; } + geometry() { + return this.feature && 'geometry' in this.feature ? this.feature.geometry : null; + } + + canonicalID() { + return this.canonical; + } + properties() { return this.feature && this.feature.properties || {}; } diff --git a/src/style-spec/expression/index.js b/src/style-spec/expression/index.js index fd9e2da3662..736ad8fdc21 100644 --- a/src/style-spec/expression/index.js +++ b/src/style-spec/expression/index.js @@ -31,9 +31,9 @@ import type {CanonicalTileID} from '../../source/tile_id'; export type Feature = { +type: 1 | 2 | 3 | 'Unknown' | 'Point' | 'MultiPoint' | 'LineString' | 'MultiLineString' | 'Polygon' | 'MultiPolygon', +id?: any, - +properties: {[string]: any}, - +patterns?: {[string]: {"min": string, "mid": string, "max": string}}, - +geometry: Array> + +properties: {[_: string]: any}, + +patterns?: {[_: string]: {"min": string, "mid": string, "max": string}}, + +geometry?: Array> }; export type FeatureState = {[_: string]: any}; diff --git a/src/style-spec/expression/parsing_context.js b/src/style-spec/expression/parsing_context.js index 7ee28791564..7cf8ce94e8c 100644 --- a/src/style-spec/expression/parsing_context.js +++ b/src/style-spec/expression/parsing_context.js @@ -95,7 +95,6 @@ class ParsingContext { } const Expr = this.registry[op]; - if (Expr) { let parsed = Expr.parse(expr, this); if (!parsed) return null; diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index d3a8f964160..458e7d038a6 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -2827,15 +2827,6 @@ } } }, - "within": { - "doc": "Determines whether feature is within provided geometry boundary or not.", - "group": "Decision", - "sdk-support": { - "basic functionality": { - "js": "1.6.0" - } - } - }, "number-format": { "doc": "Converts the input number into a string representation using the providing formatting rules. If set, the `locale` argument specifies the locale to use, as a BCP 47 language tag. If set, the `currency` argument specifies an ISO 4217 code to use for currency-style formatting. If set, the `min-fraction-digits` and `max-fraction-digits` arguments specify the minimum and maximum number of fractional digits to include.", "group": "Types", @@ -3463,6 +3454,15 @@ } } }, + "within": { + "doc": "Returns `true` if the feature being evaluated is inside the pre-defined geometry boundary, `false` otherwise. The arguments are required to be valid GeoJSON object which contains `Polygon` geometries. Currently only features with `Point` or `LineString` geometry types are supported.", + "group": "Decision", + "sdk-support": { + "basic functionality": { + "js": "1.9.0" + } + } + }, "is-supported-script": { "doc": "Returns `true` if the input string is expected to render legibly. Returns `false` if the input string contains sections that cannot be rendered without potential loss of meaning (e.g. Indic scripts that require complex text shaping, or right-to-left scripts if the the `mapbox-gl-rtl-text` plugin is not in use in Mapbox GL JS).", "group": "String", diff --git a/src/style-spec/types.js b/src/style-spec/types.js index c5d623e89be..94b42bd1c2b 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -19,7 +19,6 @@ export type FilterSpecification = | ['>=', string, string | number | boolean] | ['<', string, string | number | boolean] | ['<=', string, string | number | boolean] - | ['within', {}] | Array; // Can't type in, !in, all, any, none -- https://github.com/facebook/flow/issues/2443 export type TransitionSpecification = { diff --git a/src/style/style_layer/circle_style_layer.js b/src/style/style_layer/circle_style_layer.js index a15081ccd34..e5168f56a29 100644 --- a/src/style/style_layer/circle_style_layer.js +++ b/src/style/style_layer/circle_style_layer.js @@ -15,7 +15,7 @@ import type Transform from '../../geo/transform'; import type {Bucket, BucketParameters} from '../../data/bucket'; import type {LayoutProps, PaintProps} from './circle_style_layer_properties'; import type {LayerSpecification} from '../../style-spec/types'; -import loadGeometry from '../../data/load_geometry'; + class CircleStyleLayer extends StyleLayer { _unevaluatedLayout: Layout; layout: PossiblyEvaluated; @@ -40,7 +40,7 @@ class CircleStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - vFeature: VectorTileFeature, + feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -51,7 +51,6 @@ class CircleStyleLayer extends StyleLayer { this.paint.get('circle-translate'), this.paint.get('circle-translate-anchor'), transform.angle, pixelsToTileUnits); - const feature = {type: vFeature.type, properties: vFeature.properties, geometry: loadGeometry(vFeature)}; const radius = this.paint.get('circle-radius').evaluate(feature, featureState); const stroke = this.paint.get('circle-stroke-width').evaluate(feature, featureState); const size = radius + stroke; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 8e595b7dc61..23f9b8afb5a 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -15,7 +15,6 @@ import type {BucketParameters} from '../../data/bucket'; import type {PaintProps} from './fill_extrusion_style_layer_properties'; import type Transform from '../../geo/transform'; import type {LayerSpecification} from '../../style-spec/types'; -import loadGeometry from '../../data/load_geometry'; class FillExtrusionStyleLayer extends StyleLayer { _transitionablePaint: Transitionable; @@ -39,7 +38,7 @@ class FillExtrusionStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - vfeature: VectorTileFeature, + feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -51,7 +50,6 @@ class FillExtrusionStyleLayer extends StyleLayer { this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), transform.angle, pixelsToTileUnits); - const feature = {type: vfeature.type, properties: vfeature.properties, geometry: loadGeometry(vfeature)}; const height = this.paint.get('fill-extrusion-height').evaluate(feature, featureState); const base = this.paint.get('fill-extrusion-base').evaluate(feature, featureState); diff --git a/src/style/style_layer/line_style_layer.js b/src/style/style_layer/line_style_layer.js index 3afc5fb4cfa..336e60d45ec 100644 --- a/src/style/style_layer/line_style_layer.js +++ b/src/style/style_layer/line_style_layer.js @@ -19,7 +19,6 @@ import type {LayoutProps, PaintProps} from './line_style_layer_properties'; import type Transform from '../../geo/transform'; import type Texture from '../../render/texture'; import type {LayerSpecification} from '../../style-spec/types'; -import loadGeometry from '../../data/load_geometry'; class LineFloorwidthProperty extends DataDrivenProperty { useIntegerZoom: true; @@ -91,7 +90,7 @@ class LineStyleLayer extends StyleLayer { } queryIntersectsFeature(queryGeometry: Array, - vfeature: VectorTileFeature, + feature: VectorTileFeature, featureState: FeatureState, geometry: Array>, zoom: number, @@ -101,7 +100,6 @@ class LineStyleLayer extends StyleLayer { this.paint.get('line-translate'), this.paint.get('line-translate-anchor'), transform.angle, pixelsToTileUnits); - const feature = {type: vfeature.type, properties: vfeature.properties, geometry: loadGeometry(vfeature)}; const halfWidth = pixelsToTileUnits / 2 * getLineWidth( this.paint.get('line-width').evaluate(feature, featureState), this.paint.get('line-gap-width').evaluate(feature, featureState)); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index d3e808801be..c0aa59e01ed 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -151,7 +151,7 @@ export function performSymbolLayout(bucket: SymbolBucket, glyphMap: {[string]: {[number]: ?StyleGlyph}}, glyphPositions: {[string]: {[number]: GlyphPosition}}, imageMap: {[string]: StyleImage}, - imagePositions: {[string]: ImagePosition}, + imagePositions: {[_: string]: ImagePosition}, showCollisionBoxes: boolean, canonical: CanonicalTileID) { bucket.createArrays(); diff --git a/src/ui/map.js b/src/ui/map.js index 0c490a8ac11..2f06d6f87e8 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -349,6 +349,7 @@ class Map extends Camera { PerformanceUtils.mark(PerformanceMarkers.create); options = extend({}, defaultOptions, options); + if (options.minZoom != null && options.maxZoom != null && options.minZoom > options.maxZoom) { throw new Error(`maxZoom must be greater than or equal to minZoom`); } diff --git a/src/util/web_worker_transfer.js b/src/util/web_worker_transfer.js index 80576389b28..aa24f7600dc 100644 --- a/src/util/web_worker_transfer.js +++ b/src/util/web_worker_transfer.js @@ -89,6 +89,7 @@ register('Color', Color); register('Error', Error); register('ResolvedImage', ResolvedImage); register('StylePropertyFunction', StylePropertyFunction); + register('StyleExpression', StyleExpression, {omit: ['_evaluator']}); register('ZoomDependentExpression', ZoomDependentExpression); From 53e3eab81e965da8a7e3e0fb02b8a0f6c56e61f6 Mon Sep 17 00:00:00 2001 From: zmiao Date: Thu, 27 Feb 2020 22:28:03 +0200 Subject: [PATCH 10/14] Add render test --- .../expected.png | Bin 0 -> 7265 bytes .../paint-line-with-simple-polygon/style.json | 281 ++++++++++++++++++ 2 files changed, 281 insertions(+) create mode 100644 test/integration/render-tests/within/paint-line-with-simple-polygon/expected.png create mode 100644 test/integration/render-tests/within/paint-line-with-simple-polygon/style.json diff --git a/test/integration/render-tests/within/paint-line-with-simple-polygon/expected.png b/test/integration/render-tests/within/paint-line-with-simple-polygon/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..9bf12be16d4d1ac4c232780f0c94e735c3a0b26f GIT binary patch literal 7265 zcmcI}bySqy*S07iAl)Sm0s~5pG$JA0<A_z;9K31bPEpn?Bg$k$suggEyQ1~W}w5=8sx-b zJyUpgwdCq%Utv|tD=2r+u-FZ|(pA2)dt(H$qg0oJ$5<6++ZF`ih;-t(nG;3`@(~WT zGA?c{HoP$39!fvSyp)b+!h!$)_ythn!llW2C%hi{lFN%>$;2u?g16PthU7dDhG#40 zVHKGl!DeOGv{zt~dn`+(Cp*a!<3)z<^O}+aTNw*y8$LvELu)ZV*EBOfOaYyFQNE_Xd#ON5iE!9+xMO~ZyR-?G?DWc zaPzSOETma#0PRJ|bf}Gs*=*n(Jo+%tQ@E0FRC*s{2VO1g}kl9L)KAHb-vOm6QU!A&(r+|>l z@fL*Jc-ZnFzGUlcY~kXU?A)4BC86ViCa7?m5ev(_!seN~+3%q3$-Q}@92^3-I2g)Kniq#I8COQ2S0(5KPB5#1I0Mk z9)9?QXwHbO$2Rmxc+-+VWN<6MXf_&bGk?fQM>}5)hxJeS6RiT^z5ue#$(Is^`K?Qx zZRsOL3_$-8Lr$Gb?oO#w(ernxS2$H|F~M;qgE1hrUUhK4W*C>3 z+}N{NCNoM%%BQ*;Gtu9J<=-s)9U#BOn>H{&Kajc#8OV!S!JnKxi~|=`wwJtg`qdW9 zbcxmS(6=;MB&V-IF+^_Fs{ISiksJ1}TRQJ0_d@CBPVZIxUT1EQ=%SRcY9z^j3cjYJ zk&bNp^R`f56A`s-hFIHixR9bV%`VG*xmQ|7|9(O%}w+ItXf4|}hU0aT zhoE!9<;4Kzdk?N28qyI46>H2DQ^4rg_LXv*Nx!33j6)+aMzgh)*~iQ}Z^Ke&JW>;D z_0}qSpFms8+X^t>3lGL1axwz#3HkD;L$efq?;C-9p@ z($nsj5*B@iizDB~yn)dh!0KHL5CL4{@HDRc5@Uy(fe4ag-j zh7tD@QXu0Fl%^dmu5!s;KfHLhM#O6mv2K+IU;n`MRsYO9AG zeEjvXgyOK`M6M@W^^Z_x0Di~LK9}?w{tdL~tydw@&|*X~GB#X; zK#JqyP8)7eL#%-VTWpGi*8kc4XMZFraFz!CIM=#M2@=GF`dRXDowoW^%VmGJ09Fkqi@|%*^t#L_hgYFNq3%VaxwT#FQ4_oi22@H`a;{LWI zOvJtIN`)y*?-lK;HispI4~bu{r6s#lyy~yrd!q*{E9O&Okv9Z~s^H!(O^0IoZKuE# zX7)n7>1T%u6+jdzdJb)wSd2SMVY>Z!C!^=}E4NE0;zfIxbc7S0Du_AaMAGXLfY#Hw zz^z4*;=~$7#$oWpxnYUo`%FEe&JH9rv$0KT@bS^)tkRHiGYL)36@})BdYk(S~`PKTpZ)GdNZ=D z$-Ahi|I)Z~5@2|8PjG9Vhf*W^*2lE$F0)IkX^2?U+BPka3iHa&M{;AY=|uD`e^mIa zrex62ZiH`jw^gs{=3z5YS;{$=bOMv-(UNew51UnFU0+_3T}x`p%4V43!GxcO2w|IU z$3$>t841mOZtncb=7I6&-zhnv5^#U{w8eQ`R8EG}$=qrq~)YXAwcTWCnsmB5xT zewC#hMJIqp3ci+rzaryFCEhSupHVqL%X*+M^%8XdeKOX{#&NepQxerye`I+^f7Q>` zh{=n9YijgKMVbH=#>ExLK-^4b9SRTm$ zn@AXdO|&F7(3?0O%&E>QGy^B5RSf{&e!p=eAd9-@0~{3fD9raLoGVuQfiH1Y4f{lG z#yK^>>wb{&UXd#W{hc{>X>J#SZ7zO0*-;*e9L$K^>-#-71RK$irL~AE^Eok~gzO;i zH5h|8#H`qLSwhG8>pY~fT0Kkhl9ZLZM5Wd-iP@4v+IV;?YrCFt$Ikm0OFyGC*3+$r zMra^QUFq9h%bO2@p@5rc@`(+)2 zlLtjk?~bDcFZTo%Hv2gT44~abUaKF=6U~Yssid($cb*;rTN(xYMz`H7zk|SGd^qhidso4p4TeqTG*la9i+%`e)1&qq$2fN|SCHGq49wRU(+b z5P%VzV?oFwpZh-!6mi)<_j|HBnq~!VU3!RY=0ND^^BgWPc+0-)xwOy4k?{RYb^>1ZaBij(lne-!}MNR9h?|xa&479~T6ERf)j;P}( zwxm+us?j}Rs@H4P2?Uz}kbo9aM0$uD!hg)xu9Dg9Fi0$Z8eQ6(O?$pSqhUXPl*QAW zD(JLbcKnNkr(G3Suh88j92!3MC>rExny~TgE?3;iY%RzX90oNec=C8eO@pq(cO+2* zDE_0aQSA2s;LF;deMuf72FuAs(F6LP-L+|y z{a??&I3>N(IG%V%X);KIt-zC`tvtEH4JIpIRt}K^)o0WQ^ypD{aA@*deTIyPO+Id1 z0%u5E5d#_~ys4by{SY6NxzrFWFq~Y2XFr|C4~&+Bae-+{pN}ykfR2!t70i3!!lhx?z)e;j zAB}~S6B}{6Q`^o{vnsTIE#wHX(%DI7MCzhw(+IIU|3+`j=U_7$HlneGY0KU#sbA&W z*0pE<2#Zr7K&1xbT!@)^!R8racdR(7>JBErzr>!J{bfj9zBGNxCAcbA2VAkxQw)+F zvM&}3njNqNMHd5?9peCI-|*7mw&K>6VXX@1oJB)cu0fk3>m19$o2O{>gTKH|AsD{nV^UR zNV1Y60!x-vL%If?HpV~?HY_DRsI=Dbm^%8K^yExOTnLSFS6c(sZ4Nf-_FEWGyW*q3 z3C=4SnP5HwHV9Vg-?lP*F$}9cq`8_2%_FEj7r6A*=e$3PIP!>?mi~gu%TrxWFl&i# zEMeH})Ckh!j}0lf9I0v?^7e7E@`LXLQ(vMWw+UO}?aDxgVZ)nA04j?{`Q&+I&mOre zx9%*`nC!Pd4xB`gICmQ_<@^e!dttzD#Xs0stwVCbl}1Zl<~OshM#@=n8V>+_=vL&( zu2(E56wcg=-rqQsZ8h;tpnG8>u=UbOhs{^DA$Wkb*tiq+J}L2qUBQ!rP(7U+BNV-4 zKc=R=t)6T!8-rJQzdHOmr32id>-DjTTqjcx|6TZfyv)bt9!|qSqn} z0TF*Pb+KX^ZCc*-`XVA=+RER!Jzm_fvQJ{Phm5AoZ-8ABv>QfI5~k}pmSgGQxHBh~ z`L6$0J){s>Pr$l4|4@`g{**ma$I`dECr(uLE}vafd3RSYKPumcE**?fJCC+LD4&tc@(5pg z;6OiW_}+j6#lawcxz-2N_nFJo^T=!Ek8N=d{8Ia#xu!xS8k&ZW|CFHm&6Q({q6m9o z{rhzjUxFG=Xv&-i*ff>xN_6u}%k<_Q=Ulcv?F$W^wRNe<5#W`nN$VMu)foAFsQX~p z+oMDMS2|@vfF_S3XKPSW9GY7y>LyVQ4yyRH!%ZLAdT$l=j6Y<9*r_ETTk{r6|y^xCRl8J-S{Bx(+e(?3Uj6F}xcGgs1Y!tR)QAo_HT>>EC@&QD0}0 zUo{hXLvUw98RslE_+T*UYWVHSKJ4MsPpH?Zz&Pd^s#kb#|1z+{C|oh~OQ4mR&CZRn zEA9$NQ6Hlp%}4_5cZeqh|4%=4fE7H`f;}^>a8#Phf>`eP$bp2(R;FG zvrB%h4%$tPzBGgvz;6(8D?&Lk7VjVKSX5xR^S1po-%0Ya5%%;;wb|-_w|q#b?j61} zYH^Edwp_rt@%n!X*JMQqG;n_xK6<_v*OXE$v!+LbnPsE*&~vH1d;0otL!yFFYVhdS z+EirSm#mgwx%2HG*)JgL%lVKT~H-+h~z-09us;H?3!2uDy}Q#!8nVhHLkR zw~GNH1yos9;{@eEFO)T4ooeqZ{+EZZiY%IM?K+Iem-zy}6Q(YH-q=TDuDqsvo8nR1 z0veSu+gZ~%tV2?%WY^{FxOo98+eh&opI9{eenjVtX^PD6_rT%xC}${?ee2h{#ak#x z-j2&2qV-Rk*Tv1X_S&g33b<>!^ihM!(N+w@X7FP%E*bf9pdy_m(Krm~xjeeqrgF)X zctO^yedDd3F|zsV=+OPAUy5lEg-W)7?4p2byXc&`?Il)bJe8ZL%a7hBn3P)!`G_UH z!968+Rw9m7olH9@ zy9*5N7hw4bQ6+5{^BAx#pG$=dXZ`vHv$qvFqM9`ozg3!X+?iK5{pqplb0~i~t15w^ z!IjQqTH)V-7({~aDnO?SU#mpANjZ5hbWZpgRjPGKY;83+h!i4{g<|(j)zsJ%^xQqh z=asHtrhAt5PmCIB3*u&eI2>5sz-ByZO5-w$EK&o;<);3v1^y$_E3@0ff@j-ll7(Jp zD2B~7{z)IMZ2QWiQ+rd0yIN#;wD0K*Bed)eYRUde_o!5vumr5io>%5|W?QJMZ6vS8 zF07}kN8;Gy93u~?>0?n@QjV9Wd~F%#^P`Pp9XrUNr2U_olh<^nn5|g->?1<8CL`1> zte1F}Y&wqtqN!o5p&RWJAtSjLi`GDD69$S!X;|mc0vvhE6_I^0vf%Avg})aiq*6{+ ztd^DvC;7B%*gA~yQK$Hob;{W>gOr+`$-P}o4}$2q*Ih-6X0Orbc* zY|6C7-_z3gsi-jZ>1mOh_~VQ^&l*Q@Bk}o~jNy#drVJ`^@0_@u#ucCaM^$fUTU4+5 zH6*0m3K;?KU#*hj#!;SjXEc$y5pzkQ-c01|2W*E=o?6d==79z<=BT`2~vcV+@Q+irDN_x{RD<&pd`ZF8D&M?>4De{t%;DQQt+TQU0leYBg9qvKn zAFw4VUA_jM%rzwTE_%0q^ELcjbFkhY_xOwNm&8m4#6%;db4MU48}Ik3%j9Tw!_sQ? z_IW%GCyvzY!&3TeE*+n*!f)b_d5sQ)-1FI0m1CYwR#Wr>(yz#Mr#_%lhN!^-F*&CF z)IuBnt{H=BRVc9PaU)kbhY)A01^h+}{XBs0u{eGv!#k}tVF>yr_g^1G<+&*3^`9GvCl=6+~z`R2CQ$)gkpNYW5b8a#> z8(>pa*0g=TdOv+TAtU&6xEUF$2!8(eIZq)ZL{c1gl3f<{bXgnuHC`z>K0EmxApjbF z2faU%NI#sMeb|h7_30BzJK{5yjd{{ai$<>8aPgB$O~b|iEHpEWt^xZeQ7qKIu71Xa z8%IiBE6%PXNJ~EB>C&{CAcu+`=eP=0M@2e67$S2WJ`ufqHyvDxDVVSrra0w7IPsnEK-4nfhvF3Fj~R z2L>K%kzekJB$8r!qb@lvJ=<2^@`Rt_!6m{ly{UQ!j)Y2~FUjW}Lc<^lT9z$LeR*}} zOxKc84U5meLi?*OddT`nc4AluatK5>V`K?Ma|zj*!X2nEy%Q<%KVXHD)Yng&1X{1- zYhlww^g{cX0`Uc?m5hVwT~V;m1vFxvUW?GNk2!>1D~XBIBG&vx7Xqh*b7I54t20&x zg<=~awu(Uw)??*pEKvhVTNDUw%Mq=@wGVAM6ow0Dp~6*GG!BaW*G9TbFelnr7{0)F zIYQC8zp^MRP&%!@HlvEwi19xu5?`wuhu|x)phQu?f53gKOn+n997Frb90EkmqKZw3 z>HQUnPgT-iV$)cL3d*sfy+a4~cM$R7pCF)7#ebQI5_vThOg|1o#*pE{;bDXPq<=v) zmVBrII-CeOY?A*b;jgJK(Oi00DE$rVb~hqThpQGVj06`h7ygP#Hl3MPDV=!$&0ZI4 z7}`^OffiUnNJ7HdMPLo*1VHga9g||d?L%00H^$yBS6f?o`HqAHrMwRo>Kdz7M4#*b g&4rtC-`xYq&wX82J~|@Q)!{uQd3CwUm*(*Q0 Date: Fri, 28 Feb 2020 12:21:33 +0200 Subject: [PATCH 11/14] Add pre-checking before loading feature geometry for filter expression --- src/data/bucket/circle_bucket.js | 47 ++++++++++++++---------- src/data/bucket/fill_bucket.js | 14 +++++-- src/data/bucket/fill_extrusion_bucket.js | 16 +++++--- src/data/bucket/line_bucket.js | 14 +++++-- src/data/bucket/symbol_bucket.js | 13 ++++++- src/data/feature_index.js | 3 +- src/data/program_configuration.js | 4 +- src/source/tile.js | 3 +- src/style-spec/feature_filter/convert.js | 3 +- src/style-spec/feature_filter/index.js | 22 +++++++---- src/style/style_layer.js | 2 +- src/util/web_worker_transfer.js | 2 +- 12 files changed, 90 insertions(+), 53 deletions(-) diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index 50abc07c21f..e9ebceec2d2 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -85,27 +85,34 @@ class CircleBucket implements Bucke if (styleLayer.type === 'circle') { circleSortKey = ((styleLayer: any): CircleStyleLayer).layout.get('circle-sort-key'); } + for (const {feature, id, index, sourceLayerIndex} of features) { - const geometry = loadGeometry(feature); - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; - if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) { - const sortKey = circleSortKey ? - circleSortKey.evaluate(evaluationFeature, {}, canonical) : - undefined; - - const bucketFeature: BucketFeature = { - id, - properties: feature.properties, - type: feature.type, - sourceLayerIndex, - index, - geometry, - patterns: {}, - sortKey - }; - - bucketFeatures.push(bucketFeature); - } + const needGeometry = this.layers[0]._featureFilter.needGeometry; + const evaluationFeature = {type: feature.type, + id: ('id' in feature ? feature.id : null), + properties: feature.properties, + geometry: needGeometry ? loadGeometry(feature) : []}; + + if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + + if (!needGeometry) evaluationFeature.geometry = loadGeometry(feature); + const sortKey = circleSortKey ? + circleSortKey.evaluate(evaluationFeature, {}, canonical) : + undefined; + + const bucketFeature: BucketFeature = { + id, + properties: feature.properties, + type: feature.type, + sourceLayerIndex, + index, + geometry : evaluationFeature.geometry, + patterns: {}, + sortKey + }; + + bucketFeatures.push(bucketFeature); + } if (circleSortKey) { diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 0f11c373e04..667363e51f7 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -80,9 +80,15 @@ class FillBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - const geometry = loadGeometry(feature); - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + const needGeometry = this.layers[0]._featureFilter.needGeometry; + const evaluationFeature = {type: feature.type, + id: ('id' in feature ? feature.id : null), + properties: feature.properties, + geometry: needGeometry ? loadGeometry(feature) : []}; + + if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + + if (!needGeometry) evaluationFeature.geometry = loadGeometry(feature); const sortKey = fillSortKey ? fillSortKey.evaluate(evaluationFeature, {}, canonical, options.availableImages) : @@ -94,7 +100,7 @@ class FillBucket implements Bucket { type: feature.type, sourceLayerIndex, index, - geometry, + geometry: evaluationFeature.geometry, patterns: {}, sortKey }; diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 93c628d9628..e0fbe4bf702 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -93,15 +93,19 @@ class FillExtrusionBucket implements Bucket { this.hasPattern = hasPattern('fill-extrusion', this.layers, options); for (const {feature, id, index, sourceLayerIndex} of features) { - const geometry = loadGeometry(feature); - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + const needGeometry = this.layers[0]._featureFilter.needGeometry; + const evaluationFeature = {type: feature.type, + id: ('id' in feature ? feature.id : null), + properties: feature.properties, + geometry: needGeometry ? loadGeometry(feature) : []}; + + if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; const patternFeature: BucketFeature = { id, sourceLayerIndex, index, - geometry, + geometry: needGeometry ? evaluationFeature.geometry : loadGeometry(feature), properties: feature.properties, type: feature.type, patterns: {} @@ -114,10 +118,10 @@ class FillExtrusionBucket implements Bucket { if (this.hasPattern) { this.features.push(addPatternDependencies('fill-extrusion', this.layers, patternFeature, this.zoom, options)); } else { - this.addFeature(patternFeature, geometry, index, canonical, {}); + this.addFeature(patternFeature, patternFeature.geometry, index, canonical, {}); } - options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index, true); + options.featureIndex.insert(feature, patternFeature.geometry, index, sourceLayerIndex, this.index, true); } } diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index fd4a18e2918..deb86f54b4b 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -123,9 +123,15 @@ class LineBucket implements Bucket { const bucketFeatures = []; for (const {feature, id, index, sourceLayerIndex} of features) { - const geometry = loadGeometry(feature); - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry}; - if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + const needGeometry = this.layers[0]._featureFilter.needGeometry; + const evaluationFeature = {type: feature.type, + id: ('id' in feature ? feature.id : null), + properties: feature.properties, + geometry: needGeometry ? loadGeometry(feature) : []}; + + if (!this.layers[0]._featureFilter.filter(new EvaluationParameters(this.zoom), evaluationFeature, canonical)) continue; + + if (!needGeometry) evaluationFeature.geometry = loadGeometry(feature); const sortKey = lineSortKey ? lineSortKey.evaluate(evaluationFeature, {}, canonical) : @@ -137,7 +143,7 @@ class LineBucket implements Bucket { type: feature.type, sourceLayerIndex, index, - geometry, + geometry: evaluationFeature.geometry, patterns: {}, sortKey }; diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 7d6ed7f7e77..a9c51a64dbf 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -431,10 +431,19 @@ class SymbolBucket implements Bucket { const globalProperties = new EvaluationParameters(this.zoom); for (const {feature, id, index, sourceLayerIndex} of features) { - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry: loadGeometry(feature)}; - if (!layer._featureFilter(globalProperties, evaluationFeature, canonical)) { + + const needGeometry = layer._featureFilter.needGeometry; + const evaluationFeature = {type: feature.type, + id: ('id' in feature ? feature.id : null), + properties: feature.properties, + geometry: needGeometry ? loadGeometry(feature) : []}; + + if (!layer._featureFilter.filter(globalProperties, evaluationFeature, canonical)) { continue; } + + if (!needGeometry) evaluationFeature.geometry = loadGeometry(feature); + let text: Formatted | void; if (hasText) { // Expression evaluation will automatically coerce to Formatted diff --git a/src/data/feature_index.js b/src/data/feature_index.js index a113d96ba55..558bd8384e0 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -183,8 +183,9 @@ class FeatureIndex { const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); const sourceLayer = this.vtLayers[sourceLayerName]; + const feature = sourceLayer.feature(featureIndex); - if (!filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) + if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) return; const id = this.getId(feature, sourceLayerName); diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 77ad3f75959..febd4779f2d 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -8,7 +8,6 @@ import {PossiblyEvaluatedPropertyValue} from '../style/properties'; import {StructArrayLayout1f4, StructArrayLayout2f8, StructArrayLayout4f16, PatternLayoutArray} from './array_types'; import {clamp} from '../util/util'; -import loadGeometry from './load_geometry'; import EvaluationParameters from '../style/evaluation_parameters'; import FeaturePositionMap from './feature_position_map'; import { @@ -474,8 +473,7 @@ export default class ProgramConfiguration { //AHM: Remove after https://github.com/mapbox/mapbox-gl-js/issues/6255 const value = layer.paint.get(property); (binder: any).expression = value.value; - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties, geometry: loadGeometry(feature)}; - (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, evaluationFeature, featureStates[id], imagePositions); + (binder: AttributeBinder).updatePaintArray(pos.start, pos.end, feature, featureStates[id], imagePositions); dirty = true; } } diff --git a/src/source/tile.js b/src/source/tile.js index 2ad7dbf182d..1b20598212e 100644 --- a/src/source/tile.js +++ b/src/source/tile.js @@ -307,8 +307,7 @@ class Tile { for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); - const evaluationFeature = {type: feature.type, id: ('id' in feature ? feature.id : null), properties: feature.properties}; - if (filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature)) { + if (filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) { const id = featureIndex.getId(feature, sourceLayer); const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id); (geojsonFeature: any).tile = coord; diff --git a/src/style-spec/feature_filter/convert.js b/src/style-spec/feature_filter/convert.js index d7d473c2c4a..ed5100f947c 100644 --- a/src/style-spec/feature_filter/convert.js +++ b/src/style-spec/feature_filter/convert.js @@ -64,7 +64,8 @@ export default function convertFilter(filter: FilterSpecification): mixed { * produce a `false` result. */ function _convertFilter(filter: FilterSpecification, expectedTypes: ExpectedTypes): mixed { - if (isExpressionFilter(filter)) { return filter; } + const needGeo = false; + if (isExpressionFilter(filter, needGeo)) { return filter; } if (!filter) return true; const op = filter[0]; diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index 91a5ed5db79..ccf45d0ab10 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -3,12 +3,15 @@ import {createExpression} from '../expression'; import type {GlobalProperties, Feature} from '../expression'; import type {CanonicalTileID} from '../../source/tile_id'; -export type FeatureFilter = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => boolean; + +type FilterExpression = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => boolean; +export type FeatureFilter ={filter: FilterExpression, + needGeometry: boolean}; export default createFilter; export {isExpressionFilter}; -function isExpressionFilter(filter: any) { +function isExpressionFilter(filter: any, needGeometry: boolean) { if (filter === true || filter === false) { return true; } @@ -39,12 +42,14 @@ function isExpressionFilter(filter: any) { case 'any': case 'all': for (const f of filter.slice(1)) { - if (!isExpressionFilter(f) && typeof f !== 'boolean') { + if (!isExpressionFilter(f, needGeometry) && typeof f !== 'boolean') { return false; } } return true; - + case 'within': + needGeometry = true; + return true; default: return true; } @@ -72,10 +77,11 @@ const filterSpec = { */ function createFilter(filter: any): FeatureFilter { if (filter === null || filter === undefined) { - return () => true; + return {filter: () => true, needGeometry: false}; } - if (!isExpressionFilter(filter)) { + const withinExpr = false; + if (!isExpressionFilter(filter, withinExpr)) { filter = convertFilter(filter); } @@ -83,8 +89,8 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - - return (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical); + return {filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), + needGeometry: withinExpr}; } } diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 37ce4c510d4..82c3f68df4d 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -69,7 +69,7 @@ class StyleLayer extends Evented { this.id = layer.id; this.type = layer.type; - this._featureFilter = () => true; + this._featureFilter = {filter: () => true, needGeometry: false}; if (layer.type === 'custom') return; diff --git a/src/util/web_worker_transfer.js b/src/util/web_worker_transfer.js index aa24f7600dc..a3625eccc42 100644 --- a/src/util/web_worker_transfer.js +++ b/src/util/web_worker_transfer.js @@ -88,8 +88,8 @@ register('Grid', Grid); register('Color', Color); register('Error', Error); register('ResolvedImage', ResolvedImage); -register('StylePropertyFunction', StylePropertyFunction); +register('StylePropertyFunction', StylePropertyFunction); register('StyleExpression', StyleExpression, {omit: ['_evaluator']}); register('ZoomDependentExpression', ZoomDependentExpression); From 3a675ee53f7eb1378b74481cb6b5484f5c83b77e Mon Sep 17 00:00:00 2001 From: zmiao Date: Fri, 28 Feb 2020 16:19:38 +0200 Subject: [PATCH 12/14] Update unit tests, fix point within polygon --- src/data/feature_index.js | 2 +- .../expression/definitions/within.js | 20 ++- src/style-spec/feature_filter/convert.js | 3 +- src/style-spec/feature_filter/index.js | 27 ++-- src/style-spec/reference/v8.json | 2 +- src/style/properties.js | 2 +- .../style_layer/fill_extrusion_style_layer.js | 1 + src/symbol/symbol_layout.js | 6 +- test/expression.test.js | 4 +- .../within/invalid-geojson/test.json | 2 +- test/unit/style-spec/feature_filter.test.js | 151 ++++++++++-------- 11 files changed, 130 insertions(+), 90 deletions(-) diff --git a/src/data/feature_index.js b/src/data/feature_index.js index 558bd8384e0..ae2eca0f595 100644 --- a/src/data/feature_index.js +++ b/src/data/feature_index.js @@ -183,8 +183,8 @@ class FeatureIndex { const sourceLayerName = this.sourceLayerCoder.decode(sourceLayerIndex); const sourceLayer = this.vtLayers[sourceLayerName]; - const feature = sourceLayer.feature(featureIndex); + if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) return; diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index ede9cd82a0b..4391d5b5173 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -71,6 +71,14 @@ function getLngLatPoints(line, canonical) { return coords; } +function onBoundary(p, p1, p2) { + const x1 = p[0] - p1[0]; + const y1 = p[1] - p1[1]; + const x2 = p[0] - p2[0]; + const y2 = p[1] - p2[1]; + return (x1 * y2 - x2 * y1 === 0) && (x1 * x2 <= 0) && (y1 * y2 <= 0); +} + function rayIntersect(p, p1, p2) { return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]); } @@ -80,8 +88,9 @@ function pointWithinPolygon(point, rings) { let inside = false; for (let i = 0, len = rings.length; i < len; i++) { const ring = rings[i]; - for (let j = 0, len2 = ring.length, k = len2 - 1; j < len2; k = j++) { - if (rayIntersect(point, ring[j], ring[k])) inside = !inside; + for (let j = 0, len2 = ring.length; j < len2 - 1; j++) { + if (onBoundary(point, ring[j], ring[j + 1])) return false; + if (rayIntersect(point, ring[j], ring[j + 1])) inside = !inside; } } return inside; @@ -173,8 +182,9 @@ function pointsWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPo const lngLatPoints = []; for (const points of ctx.geometry()) { for (const point of points) { - lngLatPoints.push(getLngLatPoint(point, ctx.canonicalID())); - updateBBox(pointBBox, point); + const p = getLngLatPoint(point, ctx.canonicalID()); + lngLatPoints.push(p); + updateBBox(pointBBox, p); } } if (!boxWithinBox(pointBBox, polyBBox)) return false; @@ -234,7 +244,7 @@ class Within implements Expression { return new Within(geojson, geojson); } } - return context.error(`'within' expression requires valid geojson source that contains polygon geometry type.`); + return context.error(`'within' expression requires valid geojson object that contains polygon geometry type.`); } evaluate(ctx: EvaluationContext) { diff --git a/src/style-spec/feature_filter/convert.js b/src/style-spec/feature_filter/convert.js index ed5100f947c..d7d473c2c4a 100644 --- a/src/style-spec/feature_filter/convert.js +++ b/src/style-spec/feature_filter/convert.js @@ -64,8 +64,7 @@ export default function convertFilter(filter: FilterSpecification): mixed { * produce a `false` result. */ function _convertFilter(filter: FilterSpecification, expectedTypes: ExpectedTypes): mixed { - const needGeo = false; - if (isExpressionFilter(filter, needGeo)) { return filter; } + if (isExpressionFilter(filter)) { return filter; } if (!filter) return true; const op = filter[0]; diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index ccf45d0ab10..d1b08082c2d 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -5,13 +5,12 @@ import type {GlobalProperties, Feature} from '../expression'; import type {CanonicalTileID} from '../../source/tile_id'; type FilterExpression = (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => boolean; -export type FeatureFilter ={filter: FilterExpression, - needGeometry: boolean}; +export type FeatureFilter ={filter: FilterExpression, needGeometry: boolean}; export default createFilter; export {isExpressionFilter}; -function isExpressionFilter(filter: any, needGeometry: boolean) { +function isExpressionFilter(filter: any) { if (filter === true || filter === false) { return true; } @@ -42,14 +41,12 @@ function isExpressionFilter(filter: any, needGeometry: boolean) { case 'any': case 'all': for (const f of filter.slice(1)) { - if (!isExpressionFilter(f, needGeometry) && typeof f !== 'boolean') { + if (!isExpressionFilter(f) && typeof f !== 'boolean') { return false; } } return true; - case 'within': - needGeometry = true; - return true; + default: return true; } @@ -80,8 +77,7 @@ function createFilter(filter: any): FeatureFilter { return {filter: () => true, needGeometry: false}; } - const withinExpr = false; - if (!isExpressionFilter(filter, withinExpr)) { + if (!isExpressionFilter(filter)) { filter = convertFilter(filter); } @@ -89,9 +85,20 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { + const needGeometry = filterNeedGeometry(filter); return {filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), - needGeometry: withinExpr}; + needGeometry}; + } +} + +function filterNeedGeometry(filter) { + if (filter === true || filter === false) { + return false; + } + if (!Array.isArray(filter) || filter.length === 0) { + return false; } + return filter[0] === 'within'; } // Comparison function to sort numbers and strings diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 458e7d038a6..0b87c463a11 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -3455,7 +3455,7 @@ } }, "within": { - "doc": "Returns `true` if the feature being evaluated is inside the pre-defined geometry boundary, `false` otherwise. The arguments are required to be valid GeoJSON object which contains `Polygon` geometries. Currently only features with `Point` or `LineString` geometry types are supported.", + "doc": "Returns `true` if the feature being evaluated is inside the pre-defined geometry boundary, `false` otherwise. The expression has one argument which must be a valid GeoJSON Polygon/Multi-Polygon object. The expression only evaluates on `Point` or `LineString` feature. For `Point` feature, The expression will return false if any point of the feature is on the boundary or outside the boundary. For `LineString` feature, the expression will return false if the line is fully outside the boundary, or the line is partially intersecting the boundary, which means either part of the line is outside of the boundary, or end point of the line lies on the boundary.", "group": "Decision", "sdk-support": { "basic functionality": { diff --git a/src/style/properties.js b/src/style/properties.js index d53a713851d..1f6f971ec0f 100644 --- a/src/style/properties.js +++ b/src/style/properties.js @@ -1,7 +1,6 @@ // @flow import assert from 'assert'; -import type {CanonicalTileID} from '../source/tile_id'; import {clone, extend, easeCubicInOut} from '../util/util'; import * as interpolate from '../style-spec/util/interpolate'; import {normalizePropertyExpression} from '../style-spec/expression'; @@ -9,6 +8,7 @@ import Color from '../style-spec/util/color'; import {register} from '../util/web_worker_transfer'; import EvaluationParameters from './evaluation_parameters'; +import type {CanonicalTileID} from '../source/tile_id'; import type {StylePropertySpecification} from '../style-spec/style-spec'; import type { TransitionSpecification, diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index 23f9b8afb5a..533fb8978f8 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -50,6 +50,7 @@ class FillExtrusionStyleLayer extends StyleLayer { this.paint.get('fill-extrusion-translate'), this.paint.get('fill-extrusion-translate-anchor'), transform.angle, pixelsToTileUnits); + const height = this.paint.get('fill-extrusion-height').evaluate(feature, featureState); const base = this.paint.get('fill-extrusion-base').evaluate(feature, featureState); diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index c0aa59e01ed..9dec812e28d 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -148,9 +148,9 @@ export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, numb } export function performSymbolLayout(bucket: SymbolBucket, - glyphMap: {[string]: {[number]: ?StyleGlyph}}, - glyphPositions: {[string]: {[number]: GlyphPosition}}, - imageMap: {[string]: StyleImage}, + glyphMap: {[_: string]: {[number]: ?StyleGlyph}}, + glyphPositions: {[_: string]: {[number]: GlyphPosition}}, + imageMap: {[_: string]: StyleImage}, imagePositions: {[_: string]: ImagePosition}, showCollisionBoxes: boolean, canonical: CanonicalTileID) { diff --git a/test/expression.test.js b/test/expression.test.js index 47e955d0393..4465ca27886 100644 --- a/test/expression.test.js +++ b/test/expression.test.js @@ -29,7 +29,7 @@ function convertLines(lines, canonical, out) { } } -function getGeomtry(feature, geometry, canonical) { +function getGeometry(feature, geometry, canonical) { if (geometry.coordinates) { const coords = geometry.coordinates; const type = geometry.type; @@ -114,7 +114,7 @@ run('js', {ignores, tests}, (fixture) => { } if ('geometry' in input[1]) { if (canonical !== null) { - getGeomtry(feature, input[1].geometry, canonical); + getGeometry(feature, input[1].geometry, canonical); } else { feature.type = input[1].geometry.type; } diff --git a/test/integration/expression-tests/within/invalid-geojson/test.json b/test/integration/expression-tests/within/invalid-geojson/test.json index 5a6ccfa38fb..bd944da7d9f 100644 --- a/test/integration/expression-tests/within/invalid-geojson/test.json +++ b/test/integration/expression-tests/within/invalid-geojson/test.json @@ -44,7 +44,7 @@ "compiled": { "errors": [{ "key": "", - "error": "'within' expression requires valid geojson source that contains polygon geometry type." + "error": "'within' expression requires valid geojson object that contains polygon geometry type." }], "result": "error" } diff --git a/test/unit/style-spec/feature_filter.test.js b/test/unit/style-spec/feature_filter.test.js index 03438a70962..d81c9103683 100644 --- a/test/unit/style-spec/feature_filter.test.js +++ b/test/unit/style-spec/feature_filter.test.js @@ -2,10 +2,13 @@ import {test} from '../../util/test'; import {default as createFilter, isExpressionFilter} from '../../../src/style-spec/feature_filter'; import convertFilter from '../../../src/style-spec/feature_filter/convert'; +import Point from '@mapbox/point-geometry'; +import MercatorCoordinate from '../../../src/geo/mercator_coordinate'; +import EXTENT from '../../../src/data/extent'; test('filter', t => { t.test('expression, zoom', (t) => { - const f = createFilter(['>=', ['number', ['get', 'x']], ['zoom']]); + const f = createFilter(['>=', ['number', ['get', 'x']], ['zoom']]).filter; t.equal(f({zoom: 1}, {properties: {x: 0}}), false); t.equal(f({zoom: 1}, {properties: {x: 1.5}}), true); t.equal(f({zoom: 1}, {properties: {x: 2.5}}), true); @@ -17,7 +20,7 @@ test('filter', t => { t.test('expression, compare two properties', (t) => { t.stub(console, 'warn'); - const f = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']]]); + const f = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']]]).filter; t.equal(f({zoom: 0}, {properties: {x: 1, y: 1}}), false); t.equal(f({zoom: 0}, {properties: {x: '1', y: '1'}}), true); t.equal(f({zoom: 0}, {properties: {x: 'same', y: 'same'}}), true); @@ -27,12 +30,12 @@ test('filter', t => { }); t.test('expression, collator comparison', (t) => { - const caseSensitive = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': true}]]); + const caseSensitive = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': true}]]).filter; t.equal(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}}), false); t.equal(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}}), false); t.equal(caseSensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}}), true); - const caseInsensitive = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': false}]]); + const caseInsensitive = createFilter(['==', ['string', ['get', 'x']], ['string', ['get', 'y']], ['collator', {'case-sensitive': false}]]).filter; t.equal(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'b'}}), false); t.equal(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'A'}}), true); t.equal(caseInsensitive({zoom: 0}, {properties: {x: 'a', y: 'a'}}), true); @@ -40,14 +43,14 @@ test('filter', t => { }); t.test('expression, any/all', (t) => { - t.equal(createFilter(['all'])(), true); - t.equal(createFilter(['all', true])(), true); - t.equal(createFilter(['all', true, false])(), false); - t.equal(createFilter(['all', true, true])(), true); - t.equal(createFilter(['any'])(), false); - t.equal(createFilter(['any', true])(), true); - t.equal(createFilter(['any', true, false])(), true); - t.equal(createFilter(['any', false, false])(), false); + t.equal(createFilter(['all']).filter(), true); + t.equal(createFilter(['all', true]).filter(), true); + t.equal(createFilter(['all', true, false]).filter(), false); + t.equal(createFilter(['all', true, true]).filter(), true); + t.equal(createFilter(['any']).filter(), false); + t.equal(createFilter(['any', true]).filter(), true); + t.equal(createFilter(['any', true, false]).filter(), true); + t.equal(createFilter(['any', false, false]).filter(), false); t.end(); }); @@ -67,6 +70,26 @@ test('filter', t => { t.end(); }); + t.test('expression, within', (t) => { + const getPointFromLngLat = (lng, lat, canonical) => { + const p = MercatorCoordinate.fromLngLat({lng, lat}, 0); + const tilesAtZoom = Math.pow(2, canonical.z); + return new Point( + (p.x * tilesAtZoom - canonical.x) * EXTENT, + (p.y * tilesAtZoom - canonical.y) * EXTENT); + }; + const withinFilter = createFilter(['within', {'type': 'Polygon', 'coordinates': [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]]}]); + t.equal(withinFilter.needGeometry, true); + const canonical = {z: 3, x: 3, y:3}; + t.equal(withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(2, 2, canonical)]]}, canonical), true); + t.equal(withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(6, 6, canonical)]]}, canonical), false); + t.equal(withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(5, 5, canonical)]]}, canonical), false); + t.equal(withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(2, 2, canonical), getPointFromLngLat(3, 3, canonical)]]}, canonical), true); + t.equal(withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(6, 6, canonical), getPointFromLngLat(2, 2, canonical)]]}, canonical), false); + t.equal(withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(5, 5, canonical), getPointFromLngLat(2, 2, canonical)]]}, canonical), false); + t.end(); + }); + legacyFilterTests(t, createFilter); t.end(); @@ -120,7 +143,7 @@ test('convert legacy filters to expressions', t => { ]; const converted = convertFilter(filter); - const f = createFilter(converted); + const f = createFilter(converted).filter; t.equal(f({zoom: 0}, {properties: {x: 0, y: 1, z: 1}}), true); t.equal(f({zoom: 0}, {properties: {x: 1, y: 0, z: 1}}), true); @@ -197,23 +220,23 @@ test('convert legacy filters to expressions', t => { t.end(); }); -function legacyFilterTests(t, filter) { +function legacyFilterTests(t, createFilterExpr) { t.test('degenerate', (t) => { - t.equal(filter()(), true); - t.equal(filter(undefined)(), true); - t.equal(filter(null)(), true); + t.equal(createFilterExpr().filter(), true); + t.equal(createFilterExpr(undefined).filter(), true); + t.equal(createFilterExpr(null).filter(), true); t.end(); }); t.test('==, string', (t) => { - const f = filter(['==', 'foo', 'bar']); + const f = createFilterExpr(['==', 'foo', 'bar']).filter; t.equal(f({zoom: 0}, {properties: {foo: 'bar'}}), true); t.equal(f({zoom: 0}, {properties: {foo: 'baz'}}), false); t.end(); }); t.test('==, number', (t) => { - const f = filter(['==', 'foo', 0]); + const f = createFilterExpr(['==', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); @@ -226,7 +249,7 @@ function legacyFilterTests(t, filter) { }); t.test('==, null', (t) => { - const f = filter(['==', 'foo', null]); + const f = createFilterExpr(['==', 'foo', null]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); @@ -239,14 +262,14 @@ function legacyFilterTests(t, filter) { }); t.test('==, $type', (t) => { - const f = filter(['==', '$type', 'LineString']); + const f = createFilterExpr(['==', '$type', 'LineString']).filter; t.equal(f({zoom: 0}, {type: 1}), false); t.equal(f({zoom: 0}, {type: 2}), true); t.end(); }); t.test('==, $id', (t) => { - const f = filter(['==', '$id', 1234]); + const f = createFilterExpr(['==', '$id', 1234]).filter; t.equal(f({zoom: 0}, {id: 1234}), true); t.equal(f({zoom: 0}, {id: '1234'}), false); @@ -256,14 +279,14 @@ function legacyFilterTests(t, filter) { }); t.test('!=, string', (t) => { - const f = filter(['!=', 'foo', 'bar']); + const f = createFilterExpr(['!=', 'foo', 'bar']).filter; t.equal(f({zoom: 0}, {properties: {foo: 'bar'}}), false); t.equal(f({zoom: 0}, {properties: {foo: 'baz'}}), true); t.end(); }); t.test('!=, number', (t) => { - const f = filter(['!=', 'foo', 0]); + const f = createFilterExpr(['!=', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); @@ -276,7 +299,7 @@ function legacyFilterTests(t, filter) { }); t.test('!=, null', (t) => { - const f = filter(['!=', 'foo', null]); + const f = createFilterExpr(['!=', 'foo', null]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); @@ -289,14 +312,14 @@ function legacyFilterTests(t, filter) { }); t.test('!=, $type', (t) => { - const f = filter(['!=', '$type', 'LineString']); + const f = createFilterExpr(['!=', '$type', 'LineString']).filter; t.equal(f({zoom: 0}, {type: 1}), true); t.equal(f({zoom: 0}, {type: 2}), false); t.end(); }); t.test('<, number', (t) => { - const f = filter(['<', 'foo', 0]); + const f = createFilterExpr(['<', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: -1}}), true); @@ -312,7 +335,7 @@ function legacyFilterTests(t, filter) { }); t.test('<, string', (t) => { - const f = filter(['<', 'foo', '0']); + const f = createFilterExpr(['<', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); @@ -327,7 +350,7 @@ function legacyFilterTests(t, filter) { }); t.test('<=, number', (t) => { - const f = filter(['<=', 'foo', 0]); + const f = createFilterExpr(['<=', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: -1}}), true); @@ -343,7 +366,7 @@ function legacyFilterTests(t, filter) { }); t.test('<=, string', (t) => { - const f = filter(['<=', 'foo', '0']); + const f = createFilterExpr(['<=', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); @@ -358,7 +381,7 @@ function legacyFilterTests(t, filter) { }); t.test('>, number', (t) => { - const f = filter(['>', 'foo', 0]); + const f = createFilterExpr(['>', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); @@ -374,7 +397,7 @@ function legacyFilterTests(t, filter) { }); t.test('>, string', (t) => { - const f = filter(['>', 'foo', '0']); + const f = createFilterExpr(['>', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); @@ -389,7 +412,7 @@ function legacyFilterTests(t, filter) { }); t.test('>=, number', (t) => { - const f = filter(['>=', 'foo', 0]); + const f = createFilterExpr(['>=', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); @@ -405,7 +428,7 @@ function legacyFilterTests(t, filter) { }); t.test('>=, string', (t) => { - const f = filter(['>=', 'foo', '0']); + const f = createFilterExpr(['>=', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: -1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); @@ -420,13 +443,13 @@ function legacyFilterTests(t, filter) { }); t.test('in, degenerate', (t) => { - const f = filter(['in', 'foo']); + const f = createFilterExpr(['in', 'foo']).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.end(); }); t.test('in, string', (t) => { - const f = filter(['in', 'foo', '0']); + const f = createFilterExpr(['in', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); t.equal(f({zoom: 0}, {properties: {foo: true}}), false); @@ -438,7 +461,7 @@ function legacyFilterTests(t, filter) { }); t.test('in, number', (t) => { - const f = filter(['in', 'foo', 0]); + const f = createFilterExpr(['in', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); t.equal(f({zoom: 0}, {properties: {foo: true}}), false); @@ -449,7 +472,7 @@ function legacyFilterTests(t, filter) { }); t.test('in, null', (t) => { - const f = filter(['in', 'foo', null]); + const f = createFilterExpr(['in', 'foo', null]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); t.equal(f({zoom: 0}, {properties: {foo: true}}), false); @@ -460,7 +483,7 @@ function legacyFilterTests(t, filter) { }); t.test('in, multiple', (t) => { - const f = filter(['in', 'foo', 0, 1]); + const f = createFilterExpr(['in', 'foo', 0, 1]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: 3}}), false); @@ -470,7 +493,7 @@ function legacyFilterTests(t, filter) { t.test('in, large_multiple', (t) => { const values = Array.from({length: 2000}).map(Number.call, Number); values.reverse(); - const f = filter(['in', 'foo'].concat(values)); + const f = createFilterExpr(['in', 'foo'].concat(values)).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1999}}), true); @@ -482,7 +505,7 @@ function legacyFilterTests(t, filter) { const values = Array.from({length: 2000}).map(Number.call, Number); values.push('a'); values.unshift('b'); - const f = filter(['in', 'foo'].concat(values)); + const f = createFilterExpr(['in', 'foo'].concat(values)).filter; t.equal(f({zoom: 0}, {properties: {foo: 'b'}}), true); t.equal(f({zoom: 0}, {properties: {foo: 'a'}}), true); t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); @@ -493,12 +516,12 @@ function legacyFilterTests(t, filter) { }); t.test('in, $type', (t) => { - const f = filter(['in', '$type', 'LineString', 'Polygon']); + const f = createFilterExpr(['in', '$type', 'LineString', 'Polygon']).filter; t.equal(f({zoom: 0}, {type: 1}), false); t.equal(f({zoom: 0}, {type: 2}), true); t.equal(f({zoom: 0}, {type: 3}), true); - const f1 = filter(['in', '$type', 'Polygon', 'LineString', 'Point']); + const f1 = createFilterExpr(['in', '$type', 'Polygon', 'LineString', 'Point']).filter; t.equal(f1({zoom: 0}, {type: 1}), true); t.equal(f1({zoom: 0}, {type: 2}), true); t.equal(f1({zoom: 0}, {type: 3}), true); @@ -507,13 +530,13 @@ function legacyFilterTests(t, filter) { }); t.test('!in, degenerate', (t) => { - const f = filter(['!in', 'foo']); + const f = createFilterExpr(['!in', 'foo']).filter; t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.end(); }); t.test('!in, string', (t) => { - const f = filter(['!in', 'foo', '0']); + const f = createFilterExpr(['!in', 'foo', '0']).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); t.equal(f({zoom: 0}, {properties: {foo: null}}), true); @@ -523,7 +546,7 @@ function legacyFilterTests(t, filter) { }); t.test('!in, number', (t) => { - const f = filter(['!in', 'foo', 0]); + const f = createFilterExpr(['!in', 'foo', 0]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); t.equal(f({zoom: 0}, {properties: {foo: null}}), true); @@ -532,7 +555,7 @@ function legacyFilterTests(t, filter) { }); t.test('!in, null', (t) => { - const f = filter(['!in', 'foo', null]); + const f = createFilterExpr(['!in', 'foo', null]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); t.equal(f({zoom: 0}, {properties: {foo: null}}), false); @@ -541,7 +564,7 @@ function legacyFilterTests(t, filter) { }); t.test('!in, multiple', (t) => { - const f = filter(['!in', 'foo', 0, 1]); + const f = createFilterExpr(['!in', 'foo', 0, 1]).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 3}}), true); @@ -549,7 +572,7 @@ function legacyFilterTests(t, filter) { }); t.test('!in, large_multiple', (t) => { - const f = filter(['!in', 'foo'].concat(Array.from({length: 2000}).map(Number.call, Number))); + const f = createFilterExpr(['!in', 'foo'].concat(Array.from({length: 2000}).map(Number.call, Number))).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1999}}), false); @@ -558,7 +581,7 @@ function legacyFilterTests(t, filter) { }); t.test('!in, $type', (t) => { - const f = filter(['!in', '$type', 'LineString', 'Polygon']); + const f = createFilterExpr(['!in', '$type', 'LineString', 'Polygon']).filter; t.equal(f({zoom: 0}, {type: 1}), true); t.equal(f({zoom: 0}, {type: 2}), false); t.equal(f({zoom: 0}, {type: 3}), false); @@ -566,55 +589,55 @@ function legacyFilterTests(t, filter) { }); t.test('any', (t) => { - const f1 = filter(['any']); + const f1 = createFilterExpr(['any']).filter; t.equal(f1({zoom: 0}, {properties: {foo: 1}}), false); - const f2 = filter(['any', ['==', 'foo', 1]]); + const f2 = createFilterExpr(['any', ['==', 'foo', 1]]).filter; t.equal(f2({zoom: 0}, {properties: {foo: 1}}), true); - const f3 = filter(['any', ['==', 'foo', 0]]); + const f3 = createFilterExpr(['any', ['==', 'foo', 0]]).filter; t.equal(f3({zoom: 0}, {properties: {foo: 1}}), false); - const f4 = filter(['any', ['==', 'foo', 0], ['==', 'foo', 1]]); + const f4 = createFilterExpr(['any', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; t.equal(f4({zoom: 0}, {properties: {foo: 1}}), true); t.end(); }); t.test('all', (t) => { - const f1 = filter(['all']); + const f1 = createFilterExpr(['all']).filter; t.equal(f1({zoom: 0}, {properties: {foo: 1}}), true); - const f2 = filter(['all', ['==', 'foo', 1]]); + const f2 = createFilterExpr(['all', ['==', 'foo', 1]]).filter; t.equal(f2({zoom: 0}, {properties: {foo: 1}}), true); - const f3 = filter(['all', ['==', 'foo', 0]]); + const f3 = createFilterExpr(['all', ['==', 'foo', 0]]).filter; t.equal(f3({zoom: 0}, {properties: {foo: 1}}), false); - const f4 = filter(['all', ['==', 'foo', 0], ['==', 'foo', 1]]); + const f4 = createFilterExpr(['all', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; t.equal(f4({zoom: 0}, {properties: {foo: 1}}), false); t.end(); }); t.test('none', (t) => { - const f1 = filter(['none']); + const f1 = createFilterExpr(['none']).filter; t.equal(f1({zoom: 0}, {properties: {foo: 1}}), true); - const f2 = filter(['none', ['==', 'foo', 1]]); + const f2 = createFilterExpr(['none', ['==', 'foo', 1]]).filter; t.equal(f2({zoom: 0}, {properties: {foo: 1}}), false); - const f3 = filter(['none', ['==', 'foo', 0]]); + const f3 = createFilterExpr(['none', ['==', 'foo', 0]]).filter; t.equal(f3({zoom: 0}, {properties: {foo: 1}}), true); - const f4 = filter(['none', ['==', 'foo', 0], ['==', 'foo', 1]]); + const f4 = createFilterExpr(['none', ['==', 'foo', 0], ['==', 'foo', 1]]).filter; t.equal(f4({zoom: 0}, {properties: {foo: 1}}), false); t.end(); }); t.test('has', (t) => { - const f = filter(['has', 'foo']); + const f = createFilterExpr(['has', 'foo']).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), true); t.equal(f({zoom: 0}, {properties: {foo: 1}}), true); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), true); @@ -627,7 +650,7 @@ function legacyFilterTests(t, filter) { }); t.test('!has', (t) => { - const f = filter(['!has', 'foo']); + const f = createFilterExpr(['!has', 'foo']).filter; t.equal(f({zoom: 0}, {properties: {foo: 0}}), false); t.equal(f({zoom: 0}, {properties: {foo: 1}}), false); t.equal(f({zoom: 0}, {properties: {foo: '0'}}), false); From 2d02fe57cd4721881c810c21108452ad98f57750 Mon Sep 17 00:00:00 2001 From: zmiao Date: Tue, 10 Mar 2020 14:20:47 +0200 Subject: [PATCH 13/14] Fix review findings --- .../expression/definitions/within.js | 38 +++++++++---------- src/style-spec/feature_filter/index.js | 12 +----- test/ignores.json | 3 +- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/style-spec/expression/definitions/within.js b/src/style-spec/expression/definitions/within.js index 4391d5b5173..0d7d14d452e 100644 --- a/src/style-spec/expression/definitions/within.js +++ b/src/style-spec/expression/definitions/within.js @@ -106,10 +106,24 @@ function pointWithinPolygons(point, polygons) { return true; } +function perp(v1, v2) { + return (v1[0] * v2[1] - v1[1] * v2[0]); +} + +// check if p1 and p2 are in different sides of line segment q1->q2 +function twoSided(p1, p2, q1, q2) { + // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3) + const x1 = p1[0] - q1[0]; + const y1 = p1[1] - q1[1]; + const x2 = p2[0] - q1[0]; + const y2 = p2[1] - q1[1]; + const x3 = q2[0] - q1[0]; + const y3 = q2[1] - q1[1]; + if ((x1 * y3 - x3 * y1) * (x2 * y3 - x3 * y2) < 0) return true; + return false; +} // a, b are end points for line segment1, c and d are end points for line segment2 function lineIntersectLine(a, b, c, d) { - const perp = (v1, v2) => { return (v1[0] * v2[1] - v1[1] * v2[0]); }; - // check if two segments are parallel or not // precondition is end point a, b is inside polygon, if line a->b is // parallel to polygon edge c->d, then a->b won't intersect with c->d @@ -117,19 +131,6 @@ function lineIntersectLine(a, b, c, d) { const vectorQ = [d[0] - c[0], d[1] - c[1]]; if (perp(vectorQ, vectorP) === 0) return false; - // check if p1 and p2 are in different sides of line segment q1->q2 - const twoSided = (p1, p2, q1, q2) => { - // q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3) - const x1 = p1[0] - q1[0]; - const y1 = p1[1] - q1[1]; - const x2 = p2[0] - q1[0]; - const y2 = p2[1] - q1[1]; - const x3 = q2[0] - q1[0]; - const y3 = q2[1] - q1[1]; - if ((x1 * y3 - x3 * y1) * (x2 * y3 - x3 * y2) < 0) return true; - return false; - }; - // If lines are intersecting with each other, the relative location should be: // a and b lie in different sides of segment c->d // c and d lie in different sides of segment a->b @@ -138,8 +139,7 @@ function lineIntersectLine(a, b, c, d) { } function lineIntersectPolygon(p1, p2, polygon) { - for (let i = 0; i < polygon.length; ++i) { - const ring = polygon[i]; + for (const ring of polygon) { // loop through every edge of the ring for (let j = 0; j < ring.length - 1; ++j) { if (lineIntersectLine(p1, p2, ring[j], ring[j + 1])) { @@ -260,8 +260,8 @@ class Within implements Expression { eachChild() {} - possibleOutputs() { - return [true, false]; + outputDefined(): boolean { + return true; } serialize(): Array { diff --git a/src/style-spec/feature_filter/index.js b/src/style-spec/feature_filter/index.js index d1b08082c2d..ffe1662d3b5 100644 --- a/src/style-spec/feature_filter/index.js +++ b/src/style-spec/feature_filter/index.js @@ -85,22 +85,12 @@ function createFilter(filter: any): FeatureFilter { if (compiled.result === 'error') { throw new Error(compiled.value.map(err => `${err.key}: ${err.message}`).join(', ')); } else { - const needGeometry = filterNeedGeometry(filter); + const needGeometry = Array.isArray(filter) && filter.length !== 0 && filter[0] === 'within'; return {filter: (globalProperties: GlobalProperties, feature: Feature, canonical?: CanonicalTileID) => compiled.value.evaluate(globalProperties, feature, {}, canonical), needGeometry}; } } -function filterNeedGeometry(filter) { - if (filter === true || filter === false) { - return false; - } - if (!Array.isArray(filter) || filter.length === 0) { - return false; - } - return filter[0] === 'within'; -} - // Comparison function to sort numbers and strings function compare(a, b) { return a < b ? -1 : a > b ? 1 : 0; diff --git a/test/ignores.json b/test/ignores.json index c901c204d4b..025342d3e2f 100644 --- a/test/ignores.json +++ b/test/ignores.json @@ -20,6 +20,7 @@ "render-tests/text-variable-anchor/all-anchors-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", "render-tests/fill-pattern/update-feature-state": "https://github.com/mapbox/mapbox-gl-js/issues/7207", "render-tests/text-size/zero": "https://github.com/mapbox/mapbox-gl-js/issues/9161", - "render-tests/text-variable-anchor/left-top-right-buttom-offset-tile-map-mode": "skip - mapbox-gl-js does not need to render tiles", + "render-tests/text-variable-anchor/left-top-right-bottom-offset-tile-map-mode": "skip - mapbox-gl-js does not support tile-mode", + "render-tests/tile-mode/streets-v11": "skip - mapbox-gl-js does not support tile-mode", "render-tests/within/paint-line": "https://github.com/mapbox/mapbox-gl-js/issues/7023" } From 9b2c935df7e8757f555df4acda37d8c3e6e8029d Mon Sep 17 00:00:00 2001 From: zmiao Date: Wed, 11 Mar 2020 15:44:09 +0200 Subject: [PATCH 14/14] Add extra comparison to undefined value for filter-has-id evaluation --- src/data/bucket/circle_bucket.js | 2 +- src/data/bucket/fill_bucket.js | 2 +- src/data/bucket/fill_extrusion_bucket.js | 2 +- src/data/bucket/line_bucket.js | 2 +- src/data/bucket/symbol_bucket.js | 2 +- src/style-spec/expression/definitions/index.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index e9ebceec2d2..e9f86df667c 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -89,7 +89,7 @@ class CircleBucket implements Bucke for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = {type: feature.type, - id: ('id' in feature ? feature.id : null), + id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : []}; diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 667363e51f7..297ee0a872c 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -82,7 +82,7 @@ class FillBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = {type: feature.type, - id: ('id' in feature ? feature.id : null), + id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : []}; diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index e0fbe4bf702..bf507349946 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -95,7 +95,7 @@ class FillExtrusionBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = {type: feature.type, - id: ('id' in feature ? feature.id : null), + id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : []}; diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index deb86f54b4b..db661fa67a3 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -125,7 +125,7 @@ class LineBucket implements Bucket { for (const {feature, id, index, sourceLayerIndex} of features) { const needGeometry = this.layers[0]._featureFilter.needGeometry; const evaluationFeature = {type: feature.type, - id: ('id' in feature ? feature.id : null), + id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : []}; diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index a9c51a64dbf..2e57143c896 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -434,7 +434,7 @@ class SymbolBucket implements Bucket { const needGeometry = layer._featureFilter.needGeometry; const evaluationFeature = {type: feature.type, - id: ('id' in feature ? feature.id : null), + id, properties: feature.properties, geometry: needGeometry ? loadGeometry(feature) : []}; diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index e8fb1fdbc37..3114f7c059c 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -457,7 +457,7 @@ CompoundExpression.register(expressions, { 'filter-has-id': [ BooleanType, [], - (ctx) => ctx.id() !== null + (ctx) => (ctx.id() !== null && ctx.id() !== undefined) ], 'filter-type-in': [ BooleanType,