diff --git a/src/data/bucket.js b/src/data/bucket.js index eb964ab3ab8..9c5fbec5533 100644 --- a/src/data/bucket.js +++ b/src/data/bucket.js @@ -39,7 +39,8 @@ export type BucketFeature = {| properties: Object, type: 1 | 2 | 3, id?: any, - +patterns: {[string]: {"min": string, "mid": string, "max": string}} + +patterns: {[string]: {"min": string, "mid": string, "max": string}}, + sortKey?: number |}; /** diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index 8dd3f5e2109..d515446cab9 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -14,6 +14,7 @@ import EvaluationParameters from '../../style/evaluation_parameters'; import type { Bucket, BucketParameters, + BucketFeature, IndexedFeature, PopulateParameters } from '../bucket'; @@ -74,17 +75,54 @@ class CircleBucket implements Bucke this.segments = new SegmentVector(); this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); - } populate(features: Array, options: PopulateParameters) { + const styleLayer = this.layers[0]; + const bucketFeatures = []; + let circleSortKey = null; + + // Heatmap layers are handled in this bucket and have no evaluated properties, so we check our access + if (styleLayer.type === 'circle') { + circleSortKey = ((styleLayer: any): CircleStyleLayer).layout.get('circle-sort-key'); + } + for (const {feature, index, sourceLayerIndex} of features) { if (this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) { const geometry = loadGeometry(feature); - this.addFeature(feature, geometry, index); - options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + const sortKey = circleSortKey ? + circleSortKey.evaluate(feature, {}) : + undefined; + + const bucketFeature: BucketFeature = { + id: feature.id, + properties: feature.properties, + type: feature.type, + sourceLayerIndex, + index, + geometry, + patterns: {}, + sortKey + }; + + bucketFeatures.push(bucketFeature); } } + + if (circleSortKey) { + bucketFeatures.sort((a, b) => { + // a.sortKey is always a number when in use + return ((a.sortKey: any): number) - ((b.sortKey: any): number); + }); + } + + for (const bucketFeature of bucketFeatures) { + const {geometry, index, sourceLayerIndex} = bucketFeature; + const feature = features[index].feature; + + this.addFeature(bucketFeature, geometry, index); + options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); + } } update(states: FeatureStates, vtLayer: VectorTileLayer, imagePositions: {[string]: ImagePosition}) { @@ -117,7 +155,7 @@ class CircleBucket implements Bucke this.segments.destroy(); } - addFeature(feature: VectorTileFeature, geometry: Array>, index: number) { + addFeature(feature: BucketFeature, geometry: Array>, index: number) { for (const ring of geometry) { for (const point of ring) { const x = point.x; @@ -135,7 +173,7 @@ class CircleBucket implements Bucke // │ 0 1 │ // └─────────┘ - const segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray); + const segment = this.segments.prepareSegment(4, this.layoutVertexArray, this.indexArray, feature.sortKey); const index = segment.vertexLength; addCircleVertex(this.layoutVertexArray, x, y, -1, -1); diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 417e382105c..35a6486ff8b 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -38,6 +38,7 @@ class FillBucket implements Bucket { layerIds: Array; stateDependentLayers: Array; stateDependentLayerIds: Array; + patternFeatures: Array; layoutVertexArray: FillLayoutArray; layoutVertexBuffer: VertexBuffer; @@ -53,7 +54,6 @@ class FillBucket implements Bucket { segments: SegmentVector; segments2: SegmentVector; uploaded: boolean; - features: Array; constructor(options: BucketParameters) { this.zoom = options.zoom; @@ -62,6 +62,7 @@ class FillBucket implements Bucket { this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; this.hasPattern = false; + this.patternFeatures = []; this.layoutVertexArray = new FillLayoutArray(); this.indexArray = new TriangleIndexArray(); @@ -70,37 +71,55 @@ class FillBucket implements Bucket { this.segments = new SegmentVector(); this.segments2 = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); - } populate(features: Array, options: PopulateParameters) { - this.features = []; this.hasPattern = hasPattern('fill', this.layers, options); + const fillSortKey = this.layers[0].layout.get('fill-sort-key'); + const bucketFeatures = []; for (const {feature, index, sourceLayerIndex} of features) { if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; const geometry = loadGeometry(feature); + const sortKey = fillSortKey ? + fillSortKey.evaluate(feature, {}) : + undefined; - const patternFeature: BucketFeature = { + const bucketFeature: BucketFeature = { + id: feature.id, + properties: feature.properties, + type: feature.type, sourceLayerIndex, index, geometry, - properties: feature.properties, - type: feature.type, - patterns: {} + patterns: {}, + sortKey }; - if (typeof feature.id !== 'undefined') { - patternFeature.id = feature.id; - } + bucketFeatures.push(bucketFeature); + } + + if (fillSortKey) { + bucketFeatures.sort((a, b) => { + // a.sortKey is always a number when in use + return ((a.sortKey: any): number) - ((b.sortKey: any): number); + }); + } + + for (const bucketFeature of bucketFeatures) { + const {geometry, index, sourceLayerIndex} = bucketFeature; if (this.hasPattern) { - this.features.push(addPatternDependencies('fill', this.layers, patternFeature, this.zoom, options)); + const patternFeature = addPatternDependencies('fill', this.layers, bucketFeature, this.zoom, options); + // pattern features are added only once the pattern is loaded into the image atlas + // so are stored during populate until later updated with positions by tile worker in addFeatures + this.patternFeatures.push(patternFeature); } else { - this.addFeature(patternFeature, geometry, index, {}); + this.addFeature(bucketFeature, geometry, index, {}); } + const feature = features[index].feature; options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } @@ -111,9 +130,8 @@ class FillBucket implements Bucket { } addFeatures(options: PopulateParameters, imagePositions: {[string]: ImagePosition}) { - for (const feature of this.features) { - const {geometry} = feature; - this.addFeature(feature, geometry, feature.index, imagePositions); + for (const feature of this.patternFeatures) { + this.addFeature(feature, feature.geometry, feature.index, imagePositions); } } @@ -202,6 +220,6 @@ class FillBucket implements Bucket { } } -register('FillBucket', FillBucket, {omit: ['layers', 'features']}); +register('FillBucket', FillBucket, {omit: ['layers', 'patternFeatures']}); export default FillBucket; diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index fc5bede7782..22835a31d3b 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -100,7 +100,7 @@ class LineBucket implements Bucket { layerIds: Array; stateDependentLayers: Array; stateDependentLayerIds: Array; - features: Array; + patternFeatures: Array; layoutVertexArray: LineLayoutArray; layoutVertexBuffer: VertexBuffer; @@ -119,8 +119,8 @@ class LineBucket implements Bucket { this.layers = options.layers; this.layerIds = this.layers.map(layer => layer.id); this.index = options.index; - this.features = []; this.hasPattern = false; + this.patternFeatures = []; this.layoutVertexArray = new LineLayoutArray(); this.indexArray = new TriangleIndexArray(); @@ -131,33 +131,52 @@ class LineBucket implements Bucket { } populate(features: Array, options: PopulateParameters) { - this.features = []; this.hasPattern = hasPattern('line', this.layers, options); + const lineSortKey = this.layers[0].layout.get('line-sort-key'); + const bucketFeatures = []; for (const {feature, index, sourceLayerIndex} of features) { if (!this.layers[0]._featureFilter(new EvaluationParameters(this.zoom), feature)) continue; const geometry = loadGeometry(feature); + const sortKey = lineSortKey ? + lineSortKey.evaluate(feature, {}) : + undefined; - const patternFeature: BucketFeature = { + const bucketFeature: BucketFeature = { + id: feature.id, + properties: feature.properties, + type: feature.type, sourceLayerIndex, index, geometry, - properties: feature.properties, - type: feature.type, - patterns: {} + patterns: {}, + sortKey }; - if (typeof feature.id !== 'undefined') { - patternFeature.id = feature.id; - } + bucketFeatures.push(bucketFeature); + } + + if (lineSortKey) { + bucketFeatures.sort((a, b) => { + // a.sortKey is always a number when in use + return ((a.sortKey: any): number) - ((b.sortKey: any): number); + }); + } + + for (const bucketFeature of bucketFeatures) { + const {geometry, index, sourceLayerIndex} = bucketFeature; if (this.hasPattern) { - this.features.push(addPatternDependencies('line', this.layers, patternFeature, this.zoom, options)); + const patternBucketFeature = addPatternDependencies('line', this.layers, bucketFeature, this.zoom, options); + // pattern features are added only once the pattern is loaded into the image atlas + // so are stored during populate until later updated with positions by tile worker in addFeatures + this.patternFeatures.push(patternBucketFeature); } else { - this.addFeature(patternFeature, geometry, index, {}); + this.addFeature(bucketFeature, geometry, index, {}); } + const feature = features[index].feature; options.featureIndex.insert(feature, geometry, index, sourceLayerIndex, this.index); } } @@ -168,9 +187,8 @@ class LineBucket implements Bucket { } addFeatures(options: PopulateParameters, imagePositions: {[string]: ImagePosition}) { - for (const feature of this.features) { - const {geometry} = feature; - this.addFeature(feature, geometry, feature.index, imagePositions); + for (const feature of this.patternFeatures) { + this.addFeature(feature, feature.geometry, feature.index, imagePositions); } } @@ -626,6 +644,6 @@ function calculateFullDistance(vertices: Array, first: number, len: numbe return total; } -register('LineBucket', LineBucket, {omit: ['layers', 'features']}); +register('LineBucket', LineBucket, {omit: ['layers', 'patternFeatures']}); export default LineBucket; diff --git a/src/render/draw_circle.js b/src/render/draw_circle.js index fee50eaa7de..8231959514f 100644 --- a/src/render/draw_circle.js +++ b/src/render/draw_circle.js @@ -3,22 +3,44 @@ import StencilMode from '../gl/stencil_mode'; import DepthMode from '../gl/depth_mode'; import CullFaceMode from '../gl/cull_face_mode'; +import Program from './program'; import { circleUniformValues } from './program/circle_program'; +import SegmentVector from '../data/segment'; +import { OverscaledTileID } from '../source/tile_id'; import type Painter from './painter'; import type SourceCache from '../source/source_cache'; import type CircleStyleLayer from '../style/style_layer/circle_style_layer'; import type CircleBucket from '../data/bucket/circle_bucket'; -import type {OverscaledTileID} from '../source/tile_id'; +import type ProgramConfiguration from '../data/program_configuration'; +import type VertexBuffer from '../gl/vertex_buffer'; +import type IndexBuffer from '../gl/index_buffer'; +import type {UniformValues} from './uniform_binding'; +import type {CircleUniformsType} from './program/circle_program'; export default drawCircles; +type TileRenderState = { + programConfiguration: ProgramConfiguration, + program: Program<*>, + layoutVertexBuffer: VertexBuffer, + indexBuffer: IndexBuffer, + uniformValues: UniformValues +}; + +type SegmentsTileRenderState = { + segments: SegmentVector, + sortKey: number, + state: TileRenderState +}; + function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleStyleLayer, coords: Array) { if (painter.renderPass !== 'translucent') return; const opacity = layer.paint.get('circle-opacity'); const strokeWidth = layer.paint.get('circle-stroke-width'); const strokeOpacity = layer.paint.get('circle-stroke-opacity'); + const sortFeaturesByKey = layer.layout.get('circle-sort-key').constantOr(1) !== undefined; if (opacity.constantOr(1) === 0 && (strokeWidth.constantOr(1) === 0 || strokeOpacity.constantOr(1) === 0)) { return; @@ -33,6 +55,8 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt const stencilMode = StencilMode.disabled; const colorMode = painter.colorModeForRenderPass(); + const segmentsRenderStates: Array = []; + for (let i = 0; i < coords.length; i++) { const coord = coords[i]; @@ -42,10 +66,48 @@ function drawCircles(painter: Painter, sourceCache: SourceCache, layer: CircleSt const programConfiguration = bucket.programConfigurations.get(layer.id); const program = painter.useProgram('circle', programConfiguration); + const layoutVertexBuffer = bucket.layoutVertexBuffer; + const indexBuffer = bucket.indexBuffer; + const uniformValues = circleUniformValues(painter, coord, tile, layer); + + const state: TileRenderState = { + programConfiguration, + program, + layoutVertexBuffer, + indexBuffer, + uniformValues, + }; + + if (sortFeaturesByKey) { + const oldSegments = bucket.segments.get(); + for (const segment of oldSegments) { + segmentsRenderStates.push({ + segments: new SegmentVector([segment]), + sortKey: ((segment.sortKey: any): number), + state + }); + } + } else { + segmentsRenderStates.push({ + segments: bucket.segments, + sortKey: 0, + state + }); + } + + } + + if (sortFeaturesByKey) { + segmentsRenderStates.sort((a, b) => a.sortKey - b.sortKey); + } + + for (const segmentsState of segmentsRenderStates) { + const {programConfiguration, program, layoutVertexBuffer, indexBuffer, uniformValues} = segmentsState.state; + const segments = segmentsState.segments; program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, - circleUniformValues(painter, coord, tile, layer), layer.id, - bucket.layoutVertexBuffer, bucket.indexBuffer, bucket.segments, + uniformValues, layer.id, + layoutVertexBuffer, indexBuffer, segments, layer.paint, painter.transform.zoom, programConfiguration); } } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index ed613340c27..67d26a68a48 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -632,6 +632,21 @@ } }, "layout_fill": { + "fill-sort-key": { + "type": "number", + "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", + "sdk-support": { + "js": "1.2.0" + }, + "expression": { + "interpolated": false, + "parameters": [ + "zoom", + "feature" + ] + }, + "property-type": "data-driven" + }, "visibility": { "type": "enum", "values": { @@ -656,6 +671,21 @@ } }, "layout_circle": { + "circle-sort-key": { + "type": "number", + "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", + "sdk-support": { + "js": "1.2.0" + }, + "expression": { + "interpolated": false, + "parameters": [ + "zoom", + "feature" + ] + }, + "property-type": "data-driven" + }, "visibility": { "type": "enum", "values": { @@ -850,6 +880,21 @@ }, "property-type": "data-constant" }, + "line-sort-key": { + "type": "number", + "doc": "Sorts features in ascending order based on this value. Features with a higher sort key will appear above features with a lower sort key.", + "sdk-support": { + "js": "1.2.0" + }, + "expression": { + "interpolated": false, + "parameters": [ + "zoom", + "feature" + ] + }, + "property-type": "data-driven" + }, "visibility": { "type": "enum", "values": { diff --git a/src/style-spec/types.js b/src/style-spec/types.js index 64ce4049ee4..8dc4ce57e29 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -156,6 +156,7 @@ export type FillLayerSpecification = {| "maxzoom"?: number, "filter"?: FilterSpecification, "layout"?: {| + "fill-sort-key"?: DataDrivenPropertyValueSpecification, "visibility"?: "visible" | "none" |}, "paint"?: {| @@ -183,6 +184,7 @@ export type LineLayerSpecification = {| "line-join"?: DataDrivenPropertyValueSpecification<"bevel" | "round" | "miter">, "line-miter-limit"?: PropertyValueSpecification, "line-round-limit"?: PropertyValueSpecification, + "line-sort-key"?: DataDrivenPropertyValueSpecification, "visibility"?: "visible" | "none" |}, "paint"?: {| @@ -280,6 +282,7 @@ export type CircleLayerSpecification = {| "maxzoom"?: number, "filter"?: FilterSpecification, "layout"?: {| + "circle-sort-key"?: DataDrivenPropertyValueSpecification, "visibility"?: "visible" | "none" |}, "paint"?: {| diff --git a/src/style/style_layer/circle_style_layer.js b/src/style/style_layer/circle_style_layer.js index 439c9a77804..eafbc7d2eb8 100644 --- a/src/style/style_layer/circle_style_layer.js +++ b/src/style/style_layer/circle_style_layer.js @@ -6,17 +6,20 @@ import CircleBucket from '../../data/bucket/circle_bucket'; import { polygonIntersectsBufferedPoint } from '../../util/intersection_tests'; import { getMaximumPaintValue, translateDistance, translate } from '../query_utils'; import properties from './circle_style_layer_properties'; -import { Transitionable, Transitioning, PossiblyEvaluated } from '../properties'; +import { Transitionable, Transitioning, Layout, PossiblyEvaluated } from '../properties'; import { vec4 } from 'gl-matrix'; import Point from '@mapbox/point-geometry'; import type { FeatureState } from '../../style-spec/expression'; import type Transform from '../../geo/transform'; import type {Bucket, BucketParameters} from '../../data/bucket'; -import type {PaintProps} from './circle_style_layer_properties'; +import type {LayoutProps, PaintProps} from './circle_style_layer_properties'; import type {LayerSpecification} from '../../style-spec/types'; class CircleStyleLayer extends StyleLayer { + _unevaluatedLayout: Layout; + layout: PossiblyEvaluated; + _transitionablePaint: Transitionable; _transitioningPaint: Transitioning; paint: PossiblyEvaluated; diff --git a/src/style/style_layer/circle_style_layer_properties.js b/src/style/style_layer/circle_style_layer_properties.js index 65ea36aeb18..e5574c4f005 100644 --- a/src/style/style_layer/circle_style_layer_properties.js +++ b/src/style/style_layer/circle_style_layer_properties.js @@ -17,6 +17,13 @@ import type Color from '../../style-spec/util/color'; import type Formatted from '../../style-spec/expression/types/formatted'; +export type LayoutProps = {| + "circle-sort-key": DataDrivenProperty, +|}; + +const layout: Properties = new Properties({ + "circle-sort-key": new DataDrivenProperty(styleSpec["layout_circle"]["circle-sort-key"]), +}); export type PaintProps = {| "circle-radius": DataDrivenProperty, @@ -49,6 +56,6 @@ const paint: Properties = new Properties({ // Note: without adding the explicit type annotation, Flow infers weaker types // for these objects from their use in the constructor to StyleLayer, as // {layout?: Properties<...>, paint: Properties<...>} -export default ({ paint }: $Exact<{ - paint: Properties +export default ({ paint, layout }: $Exact<{ + paint: Properties, layout: Properties }>); diff --git a/src/style/style_layer/fill_style_layer.js b/src/style/style_layer/fill_style_layer.js index c7a555676d5..57b7884b16f 100644 --- a/src/style/style_layer/fill_style_layer.js +++ b/src/style/style_layer/fill_style_layer.js @@ -6,17 +6,20 @@ import FillBucket from '../../data/bucket/fill_bucket'; import { polygonIntersectsMultiPolygon } from '../../util/intersection_tests'; import { translateDistance, translate } from '../query_utils'; import properties from './fill_style_layer_properties'; -import { Transitionable, Transitioning, PossiblyEvaluated } from '../properties'; +import { Transitionable, Transitioning, Layout, PossiblyEvaluated } from '../properties'; import type { FeatureState } from '../../style-spec/expression'; import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; -import type {PaintProps} from './fill_style_layer_properties'; +import type {LayoutProps, PaintProps} from './fill_style_layer_properties'; import type EvaluationParameters from '../evaluation_parameters'; import type Transform from '../../geo/transform'; import type {LayerSpecification} from '../../style-spec/types'; class FillStyleLayer extends StyleLayer { + _unevaluatedLayout: Layout; + layout: PossiblyEvaluated; + _transitionablePaint: Transitionable; _transitioningPaint: Transitioning; paint: PossiblyEvaluated; diff --git a/src/style/style_layer/fill_style_layer_properties.js b/src/style/style_layer/fill_style_layer_properties.js index d0173c989b3..48ed8d3572a 100644 --- a/src/style/style_layer/fill_style_layer_properties.js +++ b/src/style/style_layer/fill_style_layer_properties.js @@ -17,6 +17,13 @@ import type Color from '../../style-spec/util/color'; import type Formatted from '../../style-spec/expression/types/formatted'; +export type LayoutProps = {| + "fill-sort-key": DataDrivenProperty, +|}; + +const layout: Properties = new Properties({ + "fill-sort-key": new DataDrivenProperty(styleSpec["layout_fill"]["fill-sort-key"]), +}); export type PaintProps = {| "fill-antialias": DataConstantProperty, @@ -41,6 +48,6 @@ const paint: Properties = new Properties({ // Note: without adding the explicit type annotation, Flow infers weaker types // for these objects from their use in the constructor to StyleLayer, as // {layout?: Properties<...>, paint: Properties<...>} -export default ({ paint }: $Exact<{ - paint: Properties +export default ({ paint, layout }: $Exact<{ + paint: Properties, layout: Properties }>); diff --git a/src/style/style_layer/line_style_layer_properties.js b/src/style/style_layer/line_style_layer_properties.js index a9fb955ef94..4f8fcf8c61c 100644 --- a/src/style/style_layer/line_style_layer_properties.js +++ b/src/style/style_layer/line_style_layer_properties.js @@ -22,6 +22,7 @@ export type LayoutProps = {| "line-join": DataDrivenProperty<"bevel" | "round" | "miter">, "line-miter-limit": DataConstantProperty, "line-round-limit": DataConstantProperty, + "line-sort-key": DataDrivenProperty, |}; const layout: Properties = new Properties({ @@ -29,6 +30,7 @@ const layout: Properties = new Properties({ "line-join": new DataDrivenProperty(styleSpec["layout_line"]["line-join"]), "line-miter-limit": new DataConstantProperty(styleSpec["layout_line"]["line-miter-limit"]), "line-round-limit": new DataConstantProperty(styleSpec["layout_line"]["line-round-limit"]), + "line-sort-key": new DataDrivenProperty(styleSpec["layout_line"]["line-sort-key"]), }); export type PaintProps = {| diff --git a/test/integration/render-tests/circle-sort-key/literal/expected.png b/test/integration/render-tests/circle-sort-key/literal/expected.png new file mode 100644 index 00000000000..fa3c256c366 Binary files /dev/null and b/test/integration/render-tests/circle-sort-key/literal/expected.png differ diff --git a/test/integration/render-tests/circle-sort-key/literal/style.json b/test/integration/render-tests/circle-sort-key/literal/style.json new file mode 100644 index 00000000000..c26e5870698 --- /dev/null +++ b/test/integration/render-tests/circle-sort-key/literal/style.json @@ -0,0 +1,81 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 64, + "width": 64, + "debug": true + } + }, + "center": [0, 30], + "zoom": 1, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "sort-key": 1, + "color": "red", + "radius": 16 + }, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + 34 + ] + } + }, + { + "type": "Feature", + "properties": { + "sort-key": 0, + "color": "blue", + "radius": 16 + }, + "geometry": { + "type": "Point", + "coordinates": [ + 2, + 26 + ] + } + }, + { + "type": "Feature", + "properties": { + "sort-key": 2, + "color": "green", + "radius": 16 + }, + "geometry": { + "type": "Point", + "coordinates": [ + -2, + 30 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "circle", + "type": "circle", + "source": "geojson", + "paint": { + "circle-radius": ["get", "radius"], + "circle-color": ["get", "color"] + }, + "layout": { + "circle-sort-key": ["get", "sort-key"] + } + } + ] +} diff --git a/test/integration/render-tests/fill-sort-key/literal/expected.png b/test/integration/render-tests/fill-sort-key/literal/expected.png new file mode 100644 index 00000000000..bf77c2d934e Binary files /dev/null and b/test/integration/render-tests/fill-sort-key/literal/expected.png differ diff --git a/test/integration/render-tests/fill-sort-key/literal/style.json b/test/integration/render-tests/fill-sort-key/literal/style.json new file mode 100644 index 00000000000..fe81a37039c --- /dev/null +++ b/test/integration/render-tests/fill-sort-key/literal/style.json @@ -0,0 +1,93 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 32, + "width": 32 + } + }, + "center": [0, 0], + "zoom": 1, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "color": "red", + "sort-key": 1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ -2, -2 ], + [ -2, 2 ], + [ 2, 2 ], + [ 2, -2 ], + [ -2, -2 ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "color": "green", + "sort-key": 0 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ -3, -3 ], + [ -3, 1 ], + [ 1, 1 ], + [ 1, -3 ], + [ -3, -3 ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "color": "blue", + "sort-key": 2 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ -1, -1 ], + [ -1, 3 ], + [ 3, 3 ], + [ 3, -1 ], + [ -1, -1 ] + ] + ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "fill", + "type": "fill", + "source": "geojson", + "layout": { + "fill-sort-key": ["get", "sort-key"] + }, + "paint": { + "fill-color": ["get", "color"], + "fill-antialias": false + } + } + ] +} diff --git a/test/integration/render-tests/line-sort-key/literal/expected.png b/test/integration/render-tests/line-sort-key/literal/expected.png new file mode 100644 index 00000000000..f6ee234fb75 Binary files /dev/null and b/test/integration/render-tests/line-sort-key/literal/expected.png differ diff --git a/test/integration/render-tests/line-sort-key/literal/style.json b/test/integration/render-tests/line-sort-key/literal/style.json new file mode 100644 index 00000000000..9683dcc203f --- /dev/null +++ b/test/integration/render-tests/line-sort-key/literal/style.json @@ -0,0 +1,69 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 32, + "width": 32 + } + }, + "center": [0, 0], + "zoom": 1, + "sources": { + "geojson": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "color": "red", + "sort-key": 0 + }, + "geometry": { + "type": "LineString", + "coordinates": [ [-5, -5], [5, 5] ] + } + }, + { + "type": "Feature", + "properties": { + "color": "green", + "sort-key": 2 + }, + "geometry": { + "type": "LineString", + "coordinates": [ [-5, 5], [5, -5] ] + } + }, + { + "type": "Feature", + "properties": { + "color": "blue", + "sort-key": 1 + }, + "geometry": { + "type": "LineString", + "coordinates": [ [-5, 2.5], [5, 2.5] ] + } + } + ] + } + } + }, + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "line", + "type": "line", + "source": "geojson", + "layout": { + "line-sort-key": ["get", "sort-key"] + }, + "paint": { + "line-color": ["get", "color"], + "line-width": 4 + } + } + ] +}