package.src.source.worker_tile.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mapbox-gl Show documentation
Show all versions of mapbox-gl Show documentation
A WebGL interactive maps library
The newest version!
// @flow
import FeatureIndex from '../data/feature_index';
import {performSymbolLayout} from '../symbol/symbol_layout';
import {CollisionBoxArray} from '../data/array_types';
import DictionaryCoder from '../util/dictionary_coder';
import SymbolBucket from '../data/bucket/symbol_bucket';
import LineBucket from '../data/bucket/line_bucket';
import FillBucket from '../data/bucket/fill_bucket';
import FillExtrusionBucket from '../data/bucket/fill_extrusion_bucket';
import {warnOnce, mapObject, values} from '../util/util';
import assert from 'assert';
import ImageAtlas from '../render/image_atlas';
import GlyphAtlas from '../render/glyph_atlas';
import EvaluationParameters from '../style/evaluation_parameters';
import {OverscaledTileID} from './tile_id';
import type {Bucket} from '../data/bucket';
import type Actor from '../util/actor';
import type StyleLayer from '../style/style_layer';
import type StyleLayerIndex from '../style/style_layer_index';
import type {StyleImage} from '../style/style_image';
import type {StyleGlyph} from '../style/style_glyph';
import type {
WorkerTileParameters,
WorkerTileCallback,
} from '../source/worker_source';
import type {PromoteIdSpecification} from '../style-spec/types';
class WorkerTile {
tileID: OverscaledTileID;
uid: string;
zoom: number;
pixelRatio: number;
tileSize: number;
source: string;
promoteId: ?PromoteIdSpecification;
overscaling: number;
showCollisionBoxes: boolean;
collectResourceTiming: boolean;
returnDependencies: boolean;
status: 'parsing' | 'done';
data: VectorTile;
collisionBoxArray: CollisionBoxArray;
abort: ?() => void;
reloadCallback: WorkerTileCallback;
vectorTile: VectorTile;
constructor(params: WorkerTileParameters) {
this.tileID = new OverscaledTileID(params.tileID.overscaledZ, params.tileID.wrap, params.tileID.canonical.z, params.tileID.canonical.x, params.tileID.canonical.y);
this.uid = params.uid;
this.zoom = params.zoom;
this.pixelRatio = params.pixelRatio;
this.tileSize = params.tileSize;
this.source = params.source;
this.overscaling = this.tileID.overscaleFactor();
this.showCollisionBoxes = params.showCollisionBoxes;
this.collectResourceTiming = !!params.collectResourceTiming;
this.returnDependencies = !!params.returnDependencies;
this.promoteId = params.promoteId;
}
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());
const featureIndex = new FeatureIndex(this.tileID, this.promoteId);
featureIndex.bucketLayerIDs = [];
const buckets: {[_: string]: Bucket} = {};
const options = {
featureIndex,
iconDependencies: {},
patternDependencies: {},
glyphDependencies: {},
availableImages
};
const layerFamilies = layerIndex.familiesBySource[this.source];
for (const sourceLayerId in layerFamilies) {
const sourceLayer = data.layers[sourceLayerId];
if (!sourceLayer) {
continue;
}
if (sourceLayer.version === 1) {
warnOnce(`Vector tile source "${this.source}" layer "${sourceLayerId}" ` +
`does not use vector tile spec v2 and therefore may have some rendering errors.`);
}
const sourceLayerIndex = sourceLayerCoder.encode(sourceLayerId);
const features = [];
for (let index = 0; index < sourceLayer.length; index++) {
const feature = sourceLayer.feature(index);
const id = featureIndex.getId(feature, sourceLayerId);
features.push({feature, id, index, sourceLayerIndex});
}
for (const family of layerFamilies[sourceLayerId]) {
const layer = family[0];
assert(layer.source === this.source);
if (layer.minzoom && this.zoom < Math.floor(layer.minzoom)) continue;
if (layer.maxzoom && this.zoom >= layer.maxzoom) continue;
if (layer.visibility === 'none') continue;
recalculateLayers(family, this.zoom, availableImages);
const bucket = buckets[layer.id] = layer.createBucket({
index: featureIndex.bucketLayerIDs.length,
layers: family,
zoom: this.zoom,
pixelRatio: this.pixelRatio,
overscaling: this.overscaling,
collisionBoxArray: this.collisionBoxArray,
sourceLayerIndex,
sourceID: this.source
});
bucket.populate(features, options, this.tileID.canonical);
featureIndex.bucketLayerIDs.push(family.map((l) => l.id));
}
}
let error: ?Error;
let glyphMap: ?{[_: string]: {[_: number]: ?StyleGlyph}};
let iconMap: ?{[_: string]: StyleImage};
let patternMap: ?{[_: string]: StyleImage};
const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(Number));
if (Object.keys(stacks).length) {
actor.send('getGlyphs', {uid: this.uid, stacks}, (err, result) => {
if (!error) {
error = err;
glyphMap = result;
maybePrepare.call(this);
}
});
} else {
glyphMap = {};
}
const icons = Object.keys(options.iconDependencies);
if (icons.length) {
actor.send('getImages', {icons, source: this.source, tileID: this.tileID, type: 'icons'}, (err, result) => {
if (!error) {
error = err;
iconMap = result;
maybePrepare.call(this);
}
});
} else {
iconMap = {};
}
const patterns = Object.keys(options.patternDependencies);
if (patterns.length) {
actor.send('getImages', {icons: patterns, source: this.source, tileID: this.tileID, type: 'patterns'}, (err, result) => {
if (!error) {
error = err;
patternMap = result;
maybePrepare.call(this);
}
});
} else {
patternMap = {};
}
maybePrepare.call(this);
function maybePrepare() {
if (error) {
return callback(error);
} else if (glyphMap && iconMap && patternMap) {
const glyphAtlas = new GlyphAtlas(glyphMap);
const imageAtlas = new ImageAtlas(iconMap, patternMap);
for (const key in buckets) {
const bucket = buckets[key];
if (bucket instanceof SymbolBucket) {
recalculateLayers(bucket.layers, this.zoom, availableImages);
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, this.tileID.canonical, imageAtlas.patternPositions);
}
}
this.status = 'done';
callback(null, {
buckets: values(buckets).filter(b => !b.isEmpty()),
featureIndex,
collisionBoxArray: this.collisionBoxArray,
glyphAtlasImage: glyphAtlas.image,
imageAtlas,
// Only used for benchmarking:
glyphMap: this.returnDependencies ? glyphMap : null,
iconMap: this.returnDependencies ? iconMap : null,
glyphPositions: this.returnDependencies ? glyphAtlas.positions : null
});
}
}
}
}
function recalculateLayers(layers: $ReadOnlyArray, zoom: number, availableImages: Array) {
// Layers are shared and may have been used by a WorkerTile with a different zoom.
const parameters = new EvaluationParameters(zoom);
for (const layer of layers) {
layer.recalculate(parameters, availableImages);
}
}
export default WorkerTile;