mapbox-gl
Version:
A WebGL interactive maps library
213 lines (171 loc) • 7.11 kB
JavaScript
'use strict';
const FeatureIndex = require('../data/feature_index');
const CollisionTile = require('../symbol/collision_tile');
const CollisionBoxArray = require('../symbol/collision_box');
const DictionaryCoder = require('../util/dictionary_coder');
const util = require('../util/util');
const assert = require('assert');
class WorkerTile {
constructor(params) {
this.coord = params.coord;
this.uid = params.uid;
this.zoom = params.zoom;
this.tileSize = params.tileSize;
this.source = params.source;
this.overscaling = params.overscaling;
this.angle = params.angle;
this.pitch = params.pitch;
this.showCollisionBoxes = params.showCollisionBoxes;
}
parse(data, layerIndex, actor, callback) {
// Normalize GeoJSON data.
if (!data.layers) {
data = { layers: { '_geojsonTileLayer': data } };
}
this.status = 'parsing';
this.data = data;
this.collisionBoxArray = new CollisionBoxArray();
const sourceLayerCoder = new DictionaryCoder(Object.keys(data.layers).sort());
const featureIndex = new FeatureIndex(this.coord, this.overscaling);
featureIndex.bucketLayerIDs = {};
const buckets = {};
let bucketIndex = 0;
const options = {
featureIndex: featureIndex,
iconDependencies: {},
glyphDependencies: {}
};
const layerFamilies = layerIndex.familiesBySource[this.source];
for (const sourceLayerId in layerFamilies) {
const sourceLayer = data.layers[sourceLayerId];
if (!sourceLayer) {
continue;
}
if (sourceLayer.version === 1) {
util.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 i = 0; i < sourceLayer.length; i++) {
const feature = sourceLayer.feature(i);
feature.index = i;
feature.sourceLayerIndex = sourceLayerIndex;
features.push(feature);
}
for (const family of layerFamilies[sourceLayerId]) {
const layer = family[0];
assert(layer.source === this.source);
if (layer.minzoom && this.zoom < layer.minzoom) continue;
if (layer.maxzoom && this.zoom >= layer.maxzoom) continue;
if (layer.layout && layer.layout.visibility === 'none') continue;
for (const layer of family) {
layer.recalculate(this.zoom);
}
const bucket = buckets[layer.id] = layer.createBucket({
index: bucketIndex,
layers: family,
zoom: this.zoom,
overscaling: this.overscaling,
collisionBoxArray: this.collisionBoxArray
});
bucket.populate(features, options);
featureIndex.bucketLayerIDs[bucketIndex] = family.map((l) => l.id);
bucketIndex++;
}
}
const done = (collisionTile) => {
this.status = 'done';
// collect data-driven paint property statistics from each bucket
featureIndex.paintPropertyStatistics = {};
for (const id in buckets) {
util.extend(featureIndex.paintPropertyStatistics, buckets[id].getPaintPropertyStatistics());
}
const transferables = [];
callback(null, {
buckets: serializeBuckets(util.values(buckets), transferables),
featureIndex: featureIndex.serialize(transferables),
collisionTile: collisionTile.serialize(transferables),
collisionBoxArray: this.collisionBoxArray.serialize()
}, transferables);
};
// Symbol buckets must be placed in reverse order.
this.symbolBuckets = [];
for (let i = layerIndex.symbolOrder.length - 1; i >= 0; i--) {
const bucket = buckets[layerIndex.symbolOrder[i]];
if (bucket) {
this.symbolBuckets.push(bucket);
}
}
if (this.symbolBuckets.length === 0) {
return done(new CollisionTile(this.angle, this.pitch, this.collisionBoxArray));
}
let deps = 0;
let icons = Object.keys(options.iconDependencies);
let stacks = util.mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(Number));
const gotDependency = (err) => {
if (err) return callback(err);
deps++;
if (deps === 2) {
const collisionTile = new CollisionTile(this.angle, this.pitch, this.collisionBoxArray);
for (const bucket of this.symbolBuckets) {
recalculateLayers(bucket, this.zoom);
bucket.prepare(stacks, icons);
bucket.place(collisionTile, this.showCollisionBoxes);
}
done(collisionTile);
}
};
if (Object.keys(stacks).length) {
actor.send('getGlyphs', {uid: this.uid, stacks: stacks}, (err, newStacks) => {
stacks = newStacks;
gotDependency(err);
});
} else {
gotDependency();
}
if (icons.length) {
actor.send('getIcons', {icons: icons}, (err, newIcons) => {
icons = newIcons;
gotDependency(err);
});
} else {
gotDependency();
}
}
redoPlacement(angle, pitch, showCollisionBoxes) {
this.angle = angle;
this.pitch = pitch;
if (this.status !== 'done') {
return {};
}
const collisionTile = new CollisionTile(this.angle, this.pitch, this.collisionBoxArray);
for (const bucket of this.symbolBuckets) {
recalculateLayers(bucket, this.zoom);
bucket.place(collisionTile, showCollisionBoxes);
}
const transferables = [];
return {
result: {
buckets: serializeBuckets(this.symbolBuckets, transferables),
collisionTile: collisionTile.serialize(transferables)
},
transferables: transferables
};
}
}
function recalculateLayers(bucket, zoom) {
// Layers are shared and may have been used by a WorkerTile with a different zoom.
for (const layer of bucket.layers) {
layer.recalculate(zoom);
}
}
function serializeBuckets(buckets, transferables) {
return buckets
.filter((b) => !b.isEmpty())
.map((b) => b.serialize(transferables));
}
module.exports = WorkerTile;