Skip to content
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

Image expression #8684

Merged
merged 23 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bench/lib/tile_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export default class TileParser {
const vectorTile = new VT.VectorTile(new Protobuf(tile.buffer));

return new Promise((resolve, reject) => {
workerTile.parse(vectorTile, this.layerIndex, (this.actor: any), (err, result) => {
workerTile.parse(vectorTile, this.layerIndex, [], (this.actor: any), (err, result) => {
if (err) {
reject(err);
} else {
Expand Down
2 changes: 2 additions & 0 deletions build/generate-flow-typed-style-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ export type ColorSpecification = string;

export type FormattedSpecification = string;

export type ImageSpecification = string;

export type FilterSpecification =
| ['has', string]
| ['!has', string]
Expand Down
4 changes: 4 additions & 0 deletions build/generate-style-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ global.flowType = function (property) {
return `Color`;
case 'formatted':
return `Formatted`;
case 'image':
return `Image`;
case 'array':
if (property.length) {
return `[${new Array(property.length).fill(flowType({type: property.value})).join(', ')}]`;
Expand Down Expand Up @@ -73,6 +75,8 @@ global.runtimeType = function (property) {
return `ColorType`;
case 'formatted':
return `FormattedType`;
case 'Image':
return `ImageType`;
case 'array':
if (property.length) {
return `array(${runtimeType({type: property.value})}, ${property.length})`;
Expand Down
3 changes: 2 additions & 1 deletion src/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export type PopulateParameters = {
featureIndex: FeatureIndex,
iconDependencies: {},
patternDependencies: {},
glyphDependencies: {}
glyphDependencies: {},
availableImages: Array<string>
}

export type IndexedFeature = {
Expand Down
2 changes: 1 addition & 1 deletion src/data/bucket/fill_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class FillBucket implements Bucket {

const geometry = loadGeometry(feature);
const sortKey = fillSortKey ?
fillSortKey.evaluate(feature, {}) :
fillSortKey.evaluate(feature, {}, options.availableImages) :
undefined;

const bucketFeature: BucketFeature = {
Expand Down
6 changes: 3 additions & 3 deletions src/data/bucket/pattern_bucket_features.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export function addPatternDependencies(type: string, layers: PatternStyleLayers,

const patternPropertyValue = patternProperty.value;
if (patternPropertyValue.kind !== "constant") {
const min = patternPropertyValue.evaluate({zoom: zoom - 1}, patternFeature, {});
const mid = patternPropertyValue.evaluate({zoom}, patternFeature, {});
const max = patternPropertyValue.evaluate({zoom: zoom + 1}, patternFeature, {});
const min = patternPropertyValue.evaluate({zoom: zoom - 1}, patternFeature, {}, options.availableImages);
const mid = patternPropertyValue.evaluate({zoom}, patternFeature, {}, options.availableImages);
const max = patternPropertyValue.evaluate({zoom: zoom + 1}, patternFeature, {}, options.availableImages);
// add to patternDependencies
patterns[min] = true;
patterns[mid] = true;
Expand Down
20 changes: 14 additions & 6 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {getSizeData} from '../../symbol/symbol_size';
import {register} from '../../util/web_worker_transfer';
import EvaluationParameters from '../../style/evaluation_parameters';
import Formatted from '../../style-spec/expression/types/formatted';
import ResolvedImage from '../../style-spec/expression/types/resolved_image';

import type {
Bucket,
Expand Down Expand Up @@ -76,7 +77,7 @@ export type CollisionArrays = {
export type SymbolFeature = {|
sortKey: number | void,
text: Formatted | void,
icon: string | void,
icon: ResolvedImage | void,
index: number,
sourceLayerIndex: number,
geometry: Array<Array<Point>>,
Expand Down Expand Up @@ -377,7 +378,7 @@ class SymbolBucket implements Bucket {
const hasText =
(textField.value.kind !== 'constant' || textField.value.value.toString().length > 0) &&
(textFont.value.kind !== 'constant' || textFont.value.value.length > 0);
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.length > 0;
const hasIcon = iconImage.value.kind !== 'constant' || iconImage.value.value && iconImage.value.value.toString().length > 0;
const symbolSortKey = layout.get('symbol-sort-key');

this.features = [];
Expand All @@ -388,6 +389,7 @@ class SymbolBucket implements Bucket {

const icons = options.iconDependencies;
const stacks = options.glyphDependencies;
const availableImages = options.availableImages;
const globalProperties = new EvaluationParameters(this.zoom);

for (const {feature, index, sourceLayerIndex} of features) {
Expand All @@ -400,16 +402,22 @@ 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', feature);
const resolvedTokens = layer.getValueAndResolveTokens('text-field', feature, availableImages);
text = transformText(resolvedTokens instanceof Formatted ?
resolvedTokens :
Formatted.fromString(resolvedTokens),
layer, feature);
}

let icon;
let icon: ResolvedImage | void;
if (hasIcon) {
icon = layer.getValueAndResolveTokens('icon-image', feature);
// 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);
icon = resolvedTokens instanceof ResolvedImage ?
resolvedTokens :
ResolvedImage.fromString(resolvedTokens);
}

if (!text && !icon) {
Expand All @@ -436,7 +444,7 @@ class SymbolBucket implements Bucket {
this.features.push(symbolFeature);

if (icon) {
icons[icon] = true;
icons[icon.name] = true;
}

if (text) {
Expand Down
6 changes: 3 additions & 3 deletions src/data/program_configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class SourceExpressionBinder<T> implements Binder<T> {
const start = paintArray.length;
paintArray.reserve(newLength);

const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, formattedSection);
const value = this.expression.evaluate(new EvaluationParameters(0), feature, {}, [], formattedSection);

if (this.type === 'color') {
const color = packColor(value);
Expand Down Expand Up @@ -326,8 +326,8 @@ class CompositeExpressionBinder<T> implements Binder<T> {
const start = paintArray.length;
paintArray.reserve(newLength);

const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, formattedSection);
const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, formattedSection);
const min = this.expression.evaluate(new EvaluationParameters(this.zoom), feature, {}, [], formattedSection);
const max = this.expression.evaluate(new EvaluationParameters(this.zoom + 1), feature, {}, [], formattedSection);

if (this.type === 'color') {
const minColor = packColor(min);
Expand Down
1 change: 0 additions & 1 deletion src/render/draw_background.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ function drawBackground(painter: Painter, sourceCache: SourceCache, layer: Backg
const crossfade = layer.getCrossfadeParameters();
for (const tileID of tileIDs) {
const matrix = painter.transform.calculatePosMatrix(tileID.toUnwrapped());

const uniformValues = image ?
backgroundPatternUniformValues(matrix, opacity, painter, image, {tileID, tileSize}, crossfade) :
backgroundUniformValues(matrix, opacity, color);
Expand Down
5 changes: 3 additions & 2 deletions src/render/draw_fill.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ function drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode

const constantPattern = patternProperty.constantOr(null);
if (constantPattern && tile.imageAtlas) {
const posTo = tile.imageAtlas.patternPositions[constantPattern.to];
const posFrom = tile.imageAtlas.patternPositions[constantPattern.from];
const atlas = tile.imageAtlas;
const posTo = atlas.patternPositions[constantPattern.to.toString()];
const posFrom = atlas.patternPositions[constantPattern.from.toString()];
if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom);
}

Expand Down
5 changes: 3 additions & 2 deletions src/render/draw_fill_extrusion.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ function drawExtrusionTiles(painter, source, layer, coords, depthMode, stencilMo

const constantPattern = patternProperty.constantOr(null);
if (constantPattern && tile.imageAtlas) {
const posTo = tile.imageAtlas.patternPositions[constantPattern.to];
const posFrom = tile.imageAtlas.patternPositions[constantPattern.from];
const atlas = tile.imageAtlas;
const posTo = atlas.patternPositions[constantPattern.to.toString()];
const posFrom = atlas.patternPositions[constantPattern.from.toString()];
if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom);
}

Expand Down
5 changes: 3 additions & 2 deletions src/render/draw_line.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ export default function drawLine(painter: Painter, sourceCache: SourceCache, lay

const constantPattern = patternProperty.constantOr(null);
if (constantPattern && tile.imageAtlas) {
const posTo = tile.imageAtlas.patternPositions[constantPattern.to];
const posFrom = tile.imageAtlas.patternPositions[constantPattern.from];
const atlas = tile.imageAtlas;
const posTo = atlas.patternPositions[constantPattern.to.toString()];
const posFrom = atlas.patternPositions[constantPattern.from.toString()];
if (posTo && posFrom) programConfiguration.setConstantPatternPositions(posTo, posFrom);
}

Expand Down
1 change: 0 additions & 1 deletion src/render/image_atlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,3 @@ export default class ImageAtlas {

register('ImagePosition', ImagePosition);
register('ImageAtlas', ImageAtlas);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no change needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was automatically added by my editor. It's pretty standard to end files with a new line isn't it? Most of our other files have a trailing new line.

2 changes: 1 addition & 1 deletion src/render/image_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class ImageManager extends Evented {

getImages(ids: Array<string>, callback: Callback<{[string]: StyleImage}>) {
// If the sprite has been loaded, or if all the icon dependencies are already present
// (i.e. if they've been addeded via runtime styling), then notify the requestor immediately.
// (i.e. if they've been added via runtime styling), then notify the requestor immediately.
// Otherwise, delay notification until the sprite is loaded. At that point, if any of the
// dependencies are still unavailable, we'll just assume they are permanently missing.
let hasAllDependencies = true;
Expand Down
7 changes: 4 additions & 3 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import type GlyphManager from './glyph_manager';
import type VertexBuffer from '../gl/vertex_buffer';
import type IndexBuffer from '../gl/index_buffer';
import type {DepthRangeType, DepthMaskType, DepthFuncType} from '../gl/types';
import type ResolvedImage from '../style-spec/expression/types/resolved_image';

export type RenderPass = 'offscreen' | 'opaque' | 'translucent';

Expand Down Expand Up @@ -508,10 +509,10 @@ class Painter {
*
* @returns true if a needed image is missing and rendering needs to be skipped.
*/
isPatternMissing(image: ?CrossFaded<string>): boolean {
isPatternMissing(image: ?CrossFaded<ResolvedImage>): boolean {
if (!image) return false;
const imagePosA = this.imageManager.getPattern(image.from);
const imagePosB = this.imageManager.getPattern(image.to);
const imagePosA = this.imageManager.getPattern(image.from.toString());
const imagePosB = this.imageManager.getPattern(image.to.toString());
return !imagePosA || !imagePosB;
}

Expand Down
3 changes: 2 additions & 1 deletion src/render/program/background_program.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type Color from '../../style-spec/util/color';
import type {CrossFaded} from '../../style/properties';
import type {CrossfadeParameters} from '../../style/evaluation_parameters';
import type {OverscaledTileID} from '../../source/tile_id';
import type ResolvedImage from '../../style-spec/expression/types/resolved_image';

export type BackgroundUniformsType = {|
'u_matrix': UniformMatrix4f,
Expand Down Expand Up @@ -83,7 +84,7 @@ const backgroundPatternUniformValues = (
matrix: Float32Array,
opacity: number,
painter: Painter,
image: CrossFaded<string>,
image: CrossFaded<ResolvedImage>,
tile: {tileID: OverscaledTileID, tileSize: number},
crossfade: CrossfadeParameters
): UniformValues<BackgroundPatternUniformsType> => extend(
Expand Down
7 changes: 4 additions & 3 deletions src/render/program/pattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {CrossFaded} from '../../style/properties';
import type {CrossfadeParameters} from '../../style/evaluation_parameters';
import type {UniformValues} from '../uniform_binding';
import type Tile from '../../source/tile';
import type ResolvedImage from '../../style-spec/expression/types/resolved_image';

type BackgroundPatternUniformsType = {|
'u_image': Uniform1i,
Expand Down Expand Up @@ -68,11 +69,11 @@ function patternUniformValues(crossfade: CrossfadeParameters, painter: Painter,
};
}

function bgPatternUniformValues(image: CrossFaded<string>, crossfade: CrossfadeParameters, painter: Painter,
function bgPatternUniformValues(image: CrossFaded<ResolvedImage>, crossfade: CrossfadeParameters, painter: Painter,
tile: {tileID: OverscaledTileID, tileSize: number}
): UniformValues<BackgroundPatternUniformsType> {
const imagePosA = painter.imageManager.getPattern(image.from);
const imagePosB = painter.imageManager.getPattern(image.to);
const imagePosA = painter.imageManager.getPattern(image.from.toString());
const imagePosB = painter.imageManager.getPattern(image.to.toString());
assert(imagePosA && imagePosB);
const {width, height} = painter.imageManager.getPixelSize();

Expand Down
4 changes: 2 additions & 2 deletions src/source/geojson_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
* GeoJSON based on parameters passed from the main-thread Source.
* See {@link GeoJSONWorkerSource#loadGeoJSON}.
*/
constructor(actor: Actor, layerIndex: StyleLayerIndex, loadGeoJSON: ?LoadGeoJSON) {
super(actor, layerIndex, loadGeoJSONTile);
constructor(actor: Actor, layerIndex: StyleLayerIndex, availableImages: Array<string>, loadGeoJSON: ?LoadGeoJSON) {
super(actor, layerIndex, availableImages, loadGeoJSONTile);
if (loadGeoJSON) {
this.loadGeoJSON = loadGeoJSON;
}
Expand Down
10 changes: 6 additions & 4 deletions src/source/vector_tile_worker_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function loadVectorTile(params: WorkerTileParameters, callback: LoadVectorDataCa
class VectorTileWorkerSource implements WorkerSource {
actor: Actor;
layerIndex: StyleLayerIndex;
availableImages: Array<string>;
loadVectorData: LoadVectorData;
loading: { [string]: WorkerTile };
loaded: { [string]: WorkerTile };
Expand All @@ -82,9 +83,10 @@ class VectorTileWorkerSource implements WorkerSource {
* {@link VectorTileWorkerSource#loadTile}. The default implementation simply
* loads the pbf at `params.url`.
*/
constructor(actor: Actor, layerIndex: StyleLayerIndex, loadVectorData: ?LoadVectorData) {
constructor(actor: Actor, layerIndex: StyleLayerIndex, availableImages: Array<string>, loadVectorData: ?LoadVectorData) {
this.actor = actor;
this.layerIndex = layerIndex;
this.availableImages = availableImages;
this.loadVectorData = loadVectorData || loadVectorTile;
this.loading = {};
this.loaded = {};
Expand Down Expand Up @@ -129,7 +131,7 @@ class VectorTileWorkerSource implements WorkerSource {
}

workerTile.vectorTile = response.vectorTile;
workerTile.parse(response.vectorTile, this.layerIndex, this.actor, (err, result) => {
workerTile.parse(response.vectorTile, this.layerIndex, this.availableImages, this.actor, (err, result) => {
if (err || !result) return callback(err);

// Transferring a copy of rawTileData because the worker needs to retain its copy.
Expand All @@ -156,7 +158,7 @@ class VectorTileWorkerSource implements WorkerSource {
const reloadCallback = workerTile.reloadCallback;
if (reloadCallback) {
delete workerTile.reloadCallback;
workerTile.parse(workerTile.vectorTile, vtSource.layerIndex, vtSource.actor, reloadCallback);
workerTile.parse(workerTile.vectorTile, vtSource.layerIndex, this.availableImages, vtSource.actor, reloadCallback);
}
callback(err, data);
};
Expand All @@ -166,7 +168,7 @@ class VectorTileWorkerSource implements WorkerSource {
} else if (workerTile.status === 'done') {
// if there was no vector tile data on the initial load, don't try and re-parse tile
if (workerTile.vectorTile) {
workerTile.parse(workerTile.vectorTile, this.layerIndex, this.actor, done);
workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, done);
} else {
done();
}
Expand Down
20 changes: 18 additions & 2 deletions src/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default class Worker {
self: WorkerGlobalScopeInterface;
actor: Actor;
layerIndexes: { [string]: StyleLayerIndex };
availableImages: { [string]: Array<string> };
workerSourceTypes: { [string]: Class<WorkerSource> };
workerSources: { [string]: { [string]: { [string]: WorkerSource } } };
demWorkerSources: { [string]: { [string]: RasterDEMTileWorkerSource } };
Expand All @@ -40,6 +41,7 @@ export default class Worker {
this.actor = new Actor(self, this);

this.layerIndexes = {};
this.availableImages = {};

this.workerSourceTypes = {
vector: VectorTileWorkerSource,
Expand Down Expand Up @@ -71,6 +73,11 @@ export default class Worker {
this.referrer = referrer;
}

setImages(mapId: string, images: Array<string>, callback: WorkerTileCallback) {
this.availableImages[mapId] = images;
callback();
}

setLayers(mapId: string, layers: Array<LayerSpecification>, callback: WorkerTileCallback) {
this.getLayerIndex(mapId).replace(layers);
callback();
Expand Down Expand Up @@ -157,6 +164,16 @@ export default class Worker {
}
}

getAvailableImages(mapId: string) {
let availableImages = this.availableImages[mapId];

if (!availableImages) {
availableImages = [];
}

return availableImages;
}

getLayerIndex(mapId: string) {
let layerIndexes = this.layerIndexes[mapId];
if (!layerIndexes) {
Expand All @@ -179,8 +196,7 @@ export default class Worker {
this.actor.send(type, data, callback, mapId);
}
};

this.workerSources[mapId][type][source] = new (this.workerSourceTypes[type]: any)((actor: any), this.getLayerIndex(mapId));
this.workerSources[mapId][type][source] = new (this.workerSourceTypes[type]: any)((actor: any), this.getLayerIndex(mapId), this.getAvailableImages(mapId));
}

return this.workerSources[mapId][type][source];
Expand Down
Loading