-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Property refactor #5682
Property refactor #5682
Changes from all commits
86dbf1b
3893bcf
a6f3ba0
d941a2f
69485f6
b7cda28
fb7139d
b344e2b
8270732
e491e69
80d0e48
7315489
7c03ef8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
'use strict'; | ||
|
||
require('flow-remove-types/register'); | ||
|
||
const fs = require('fs'); | ||
const ejs = require('ejs'); | ||
const spec = require('../src/style-spec/reference/v8'); | ||
const Color = require('../src/style-spec/util/color'); | ||
|
||
global.camelize = function (str) { | ||
return str.replace(/(?:^|-)(.)/g, function (_, x) { | ||
return x.toUpperCase(); | ||
}); | ||
}; | ||
|
||
global.isDataDriven = function (property) { | ||
return property['property-function'] === true; | ||
}; | ||
|
||
global.flowType = function (property) { | ||
switch (property.type) { | ||
case 'boolean': | ||
return 'boolean'; | ||
case 'number': | ||
return 'number'; | ||
case 'string': | ||
return 'string'; | ||
case 'enum': | ||
return Object.keys(property.values).map(JSON.stringify).join(' | '); | ||
case 'color': | ||
return `Color`; | ||
case 'array': | ||
if (property.length) { | ||
return `[${new Array(property.length).fill(flowType({type: property.value})).join(', ')}]`; | ||
} else { | ||
return `Array<${flowType({type: property.value})}>`; | ||
} | ||
default: throw new Error(`unknown type for ${property.name}`) | ||
} | ||
}; | ||
|
||
global.propertyType = function (property) { | ||
if (isDataDriven(property)) { | ||
return `DataDrivenProperty<${flowType(property)}>`; | ||
} else if (/-pattern$/.test(property.name) || property.name === 'line-dasharray') { | ||
return `CrossFadedProperty<${flowType(property)}>`; | ||
} else if (property.name === 'heatmap-color') { | ||
return `HeatmapColorProperty`; | ||
} else { | ||
return `DataConstantProperty<${flowType(property)}>`; | ||
} | ||
}; | ||
|
||
global.runtimeType = function (property) { | ||
switch (property.type) { | ||
case 'boolean': | ||
return 'BooleanType'; | ||
case 'number': | ||
return 'NumberType'; | ||
case 'string': | ||
case 'enum': | ||
return 'StringType'; | ||
case 'color': | ||
return `ColorType`; | ||
case 'array': | ||
if (property.length) { | ||
return `array(${runtimeType({type: property.value})}, ${property.length})`; | ||
} else { | ||
return `array(${runtimeType({type: property.value})})`; | ||
} | ||
default: throw new Error(`unknown type for ${property.name}`) | ||
} | ||
}; | ||
|
||
global.defaultValue = function (property) { | ||
switch (property.type) { | ||
case 'boolean': | ||
case 'number': | ||
case 'string': | ||
case 'array': | ||
case 'enum': | ||
return JSON.stringify(property.default); | ||
case 'color': | ||
if (typeof property.default !== 'string') { | ||
return JSON.stringify(property.default); | ||
} else { | ||
const {r, g, b, a} = Color.parse(property.default); | ||
return `new Color(${r}, ${g}, ${b}, ${a})`; | ||
} | ||
default: throw new Error(`unknown type for ${property.name}`) | ||
} | ||
}; | ||
|
||
global.propertyValue = function (property, type) { | ||
if (isDataDriven(property)) { | ||
return `new DataDrivenProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`; | ||
} else if (/-pattern$/.test(property.name) || property.name === 'line-dasharray') { | ||
return `new CrossFadedProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`; | ||
} else if (property.name === 'heatmap-color') { | ||
return `new HeatmapColorProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`; | ||
} else { | ||
return `new DataConstantProperty(styleSpec["${type}_${property.layerType}"]["${property.name}"])`; | ||
} | ||
}; | ||
|
||
const propertiesJs = ejs.compile(fs.readFileSync('src/style/style_layer/layer_properties.js.ejs', 'utf8'), {strict: true}); | ||
|
||
const layers = Object.keys(spec.layer.type.values).map((type) => { | ||
const layoutProperties = Object.keys(spec[`layout_${type}`]).reduce((memo, name) => { | ||
if (name !== 'visibility') { | ||
spec[`layout_${type}`][name].name = name; | ||
spec[`layout_${type}`][name].layerType = type; | ||
memo.push(spec[`layout_${type}`][name]); | ||
} | ||
return memo; | ||
}, []); | ||
|
||
const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => { | ||
spec[`paint_${type}`][name].name = name; | ||
spec[`paint_${type}`][name].layerType = type; | ||
memo.push(spec[`paint_${type}`][name]); | ||
return memo; | ||
}, []); | ||
|
||
return { type, layoutProperties, paintProperties }; | ||
}); | ||
|
||
for (const layer of layers) { | ||
fs.writeFileSync(`src/style/style_layer/${layer.type.replace('-', '_')}_style_layer_properties.js`, propertiesJs(layer)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
// @flow | ||
|
||
const Point = require('@mapbox/point-geometry'); | ||
const {SegmentVector} = require('../segment'); | ||
const VertexBuffer = require('../../gl/vertex_buffer'); | ||
|
@@ -27,6 +28,8 @@ import type { | |
} from '../../util/struct_array'; | ||
import type SymbolStyleLayer from '../../style/style_layer/symbol_style_layer'; | ||
import type {SymbolQuad} from '../../symbol/quads'; | ||
import type {SizeData} from '../../symbol/symbol_size'; | ||
import type {PossiblyEvaluatedPropertyValue} from '../../style/properties'; | ||
|
||
export type SingleCollisionBox = { | ||
x1: number; | ||
|
@@ -365,8 +368,29 @@ class SymbolBucket implements Bucket { | |
index: number; | ||
sdfIcons: boolean; | ||
iconsNeedLinear: boolean; | ||
textSizeData: any; | ||
iconSizeData: any; | ||
|
||
// The symbol layout process needs `text-size` evaluated at up to five different zoom levels, and | ||
// `icon-size` at up to three: | ||
// | ||
// 1. `text-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `text-size` | ||
// expressions, and to calculate the box dimensions for icon-text-fit. | ||
// 2. `icon-size` at the zoom level of the bucket. Used to calculate a per-feature size for source `icon-size` | ||
// expressions. | ||
// 3. `text-size` and `icon-size` at the zoom level of the bucket, plus one. Used to calculate collision boxes. | ||
// 4. `text-size` at zoom level 18. Used for something line-symbol-placement-related. | ||
// 5. For composite `*-size` expressions: two zoom levels of curve stops that "cover" the zoom level of the | ||
// bucket. These go into a vertex buffer and are used by the shader to interpolate the size at render time. | ||
// | ||
// (1) and (2) are stored in `this.layers[0].layout`. The remainder are below. | ||
// | ||
textSizeData: SizeData; | ||
iconSizeData: SizeData; | ||
layoutTextSize: PossiblyEvaluatedPropertyValue<number>; // (3) | ||
layoutIconSize: PossiblyEvaluatedPropertyValue<number>; // (3) | ||
textMaxSize: PossiblyEvaluatedPropertyValue<number>; // (4) | ||
compositeTextSizes: [PossiblyEvaluatedPropertyValue<number>, PossiblyEvaluatedPropertyValue<number>]; // (5) | ||
compositeIconSizes: [PossiblyEvaluatedPropertyValue<number>, PossiblyEvaluatedPropertyValue<number>]; // (5) | ||
|
||
placedGlyphArray: StructArray; | ||
placedIconArray: StructArray; | ||
glyphOffsetArray: StructArray; | ||
|
@@ -414,13 +438,34 @@ class SymbolBucket implements Bucket { | |
this.symbolInstances = options.symbolInstances; | ||
|
||
const layout = options.layers[0].layout; | ||
this.sortFeaturesByY = layout['text-allow-overlap'] || layout['icon-allow-overlap'] || | ||
layout['text-ignore-placement'] || layout['icon-ignore-placement']; | ||
this.sortFeaturesByY = layout.get('text-allow-overlap') || layout.get('icon-allow-overlap') || | ||
layout.get('text-ignore-placement') || layout.get('icon-ignore-placement'); | ||
|
||
} else { | ||
const layer = this.layers[0]; | ||
this.textSizeData = getSizeData(this.zoom, layer, 'text-size'); | ||
this.iconSizeData = getSizeData(this.zoom, layer, 'icon-size'); | ||
const layer: SymbolStyleLayer = this.layers[0]; | ||
const unevaluatedLayoutValues = layer._unevaluatedLayout._values; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use const textSize = layer.getLayoutProperty('text-size');
const iconSize = layer.getLayoutProperty('icon-size'); instead of accessing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, is this because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. I guess we could add |
||
|
||
this.textSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['text-size']); | ||
if (this.textSizeData.functionType === 'composite') { | ||
const {min, max} = this.textSizeData.zoomRange; | ||
this.compositeTextSizes = [ | ||
unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: min}), | ||
unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: max}) | ||
]; | ||
} | ||
|
||
this.iconSizeData = getSizeData(this.zoom, unevaluatedLayoutValues['icon-size']); | ||
if (this.iconSizeData.functionType === 'composite') { | ||
const {min, max} = this.iconSizeData.zoomRange; | ||
this.compositeIconSizes = [ | ||
unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: min}), | ||
unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: max}) | ||
]; | ||
} | ||
|
||
this.layoutTextSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: this.zoom + 1}); | ||
this.layoutIconSize = unevaluatedLayoutValues['icon-size'].possiblyEvaluate({zoom: this.zoom + 1}); | ||
this.textMaxSize = unevaluatedLayoutValues['text-size'].possiblyEvaluate({zoom: 18}); | ||
} | ||
} | ||
|
||
|
@@ -437,12 +482,14 @@ class SymbolBucket implements Bucket { | |
} | ||
|
||
populate(features: Array<IndexedFeature>, options: PopulateParameters) { | ||
const layer: SymbolStyleLayer = this.layers[0]; | ||
const layer = this.layers[0]; | ||
const layout = layer.layout; | ||
const textFont = layout['text-font']; | ||
|
||
const hasText = (!layer.isLayoutValueFeatureConstant('text-field') || layout['text-field']) && textFont; | ||
const hasIcon = (!layer.isLayoutValueFeatureConstant('icon-image') || layout['icon-image']); | ||
const textFont = layout.get('text-font').join(','); | ||
const textField = layout.get('text-field'); | ||
const iconImage = layout.get('icon-image'); | ||
const hasText = textField.value.kind !== 'constant' || textField.value.value.length > 0 && textFont.length > 0; | ||
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.length > 0; | ||
|
||
this.features = []; | ||
|
||
|
@@ -462,13 +509,13 @@ class SymbolBucket implements Bucket { | |
|
||
let text; | ||
if (hasText) { | ||
text = layer.getValueAndResolveTokens('text-field', globalProperties, feature); | ||
text = transformText(text, layer, globalProperties, feature); | ||
text = layer.getValueAndResolveTokens('text-field', feature); | ||
text = transformText(text, layer, feature); | ||
} | ||
|
||
let icon; | ||
if (hasIcon) { | ||
icon = layer.getValueAndResolveTokens('icon-image', globalProperties, feature); | ||
icon = layer.getValueAndResolveTokens('icon-image', feature); | ||
} | ||
|
||
if (!text && !icon) { | ||
|
@@ -494,7 +541,7 @@ class SymbolBucket implements Bucket { | |
} | ||
|
||
if (text) { | ||
const textAlongLine = layout['text-rotation-alignment'] === 'map' && layout['symbol-placement'] === 'line'; | ||
const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') === 'line'; | ||
const allowsVerticalWritingMode = scriptDetection.allowsVerticalWritingMode(text); | ||
for (let i = 0; i < text.length; i++) { | ||
stack[text.charCodeAt(i)] = true; | ||
|
@@ -508,7 +555,7 @@ class SymbolBucket implements Bucket { | |
} | ||
} | ||
|
||
if (layout['symbol-placement'] === 'line') { | ||
if (layout.get('symbol-placement') === 'line') { | ||
// Merge adjacent lines with the same text to improve labelling. | ||
// It's better to place labels on one long line than on many short segments. | ||
this.features = mergeLines(this.features); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add more detail to why we need the max-size for line-symbol placement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should, but nobody seems remember the details! Tracking this in #5683.