mapbox-gl
Version:
A WebGL interactive maps library
207 lines (173 loc) • 6.84 kB
JavaScript
'use strict';
var util = require('../util/util');
var Bucket = require('../data/bucket');
var FeatureIndex = require('../data/feature_index');
var vt = require('vector-tile');
var Protobuf = require('pbf');
var GeoJSONFeature = require('../util/vectortile_to_geojson');
var featureFilter = require('feature-filter');
var CollisionTile = require('../symbol/collision_tile');
var CollisionBoxArray = require('../symbol/collision_box');
var SymbolInstancesArray = require('../symbol/symbol_instances');
var SymbolQuadsArray = require('../symbol/symbol_quads');
module.exports = Tile;
/**
* A tile object is the combination of a Coordinate, which defines
* its place, as well as a unique ID and data tracking for its content
*
* @param {Coordinate} coord
* @param {number} size
* @private
*/
function Tile(coord, size, sourceMaxZoom) {
this.coord = coord;
this.uid = util.uniqueId();
this.uses = 0;
this.tileSize = size;
this.sourceMaxZoom = sourceMaxZoom;
this.buckets = {};
// `this.state` must be one of
//
// - `loading`: Tile data is in the process of loading.
// - `loaded`: Tile data has been loaded. Tile can be rendered.
// - `reloading`: Tile data has been loaded and is being updated. Tile can be rendered.
// - `unloaded`: Tile data has been deleted.
// - `errored`: Tile data was not loaded because of an error.
this.state = 'loading';
}
Tile.prototype = {
/**
* Given a data object with a 'buffers' property, load it into
* this tile's elementGroups and buffers properties and set loaded
* to true. If the data is null, like in the case of an empty
* GeoJSON tile, no-op but still set loaded to true.
* @param {Object} data
* @returns {undefined}
* @private
*/
loadVectorData: function(data, painter) {
if (this.hasData()) {
this.unloadVectorData(painter);
}
this.state = 'loaded';
// empty GeoJSON tile
if (!data) return;
// If we are redoing placement for the same tile, we will not recieve
// a new "rawTileData" object. If we are loading a new tile, we will
// recieve a new "rawTileData" object.
if (data.rawTileData) {
this.rawTileData = data.rawTileData;
}
this.collisionBoxArray = new CollisionBoxArray(data.collisionBoxArray);
this.collisionTile = new CollisionTile(data.collisionTile, this.collisionBoxArray);
this.symbolInstancesArray = new SymbolInstancesArray(data.symbolInstancesArray);
this.symbolQuadsArray = new SymbolQuadsArray(data.symbolQuadsArray);
this.featureIndex = new FeatureIndex(data.featureIndex, this.rawTileData, this.collisionTile);
this.buckets = unserializeBuckets(data.buckets, painter.style);
},
/**
* Replace this tile's symbol buckets with fresh data.
* @param {Object} data
* @param {Style} style
* @returns {undefined}
* @private
*/
reloadSymbolData: function(data, style) {
if (this.state === 'unloaded') return;
this.collisionTile = new CollisionTile(data.collisionTile, this.collisionBoxArray);
this.featureIndex.setCollisionTile(this.collisionTile);
for (var id in this.buckets) {
var bucket = this.buckets[id];
if (bucket.type === 'symbol') {
bucket.destroy();
delete this.buckets[id];
}
}
// Add new symbol buckets
util.extend(this.buckets, unserializeBuckets(data.buckets, style));
},
/**
* Release any data or WebGL resources referenced by this tile.
* @returns {undefined}
* @private
*/
unloadVectorData: function() {
for (var id in this.buckets) {
this.buckets[id].destroy();
}
this.collisionBoxArray = null;
this.symbolQuadsArray = null;
this.symbolInstancesArray = null;
this.collisionTile = null;
this.featureIndex = null;
this.buckets = null;
this.state = 'unloaded';
},
redoPlacement: function(source) {
if (this.state !== 'loaded' || this.state === 'reloading') {
this.redoWhenDone = true;
return;
}
this.state = 'reloading';
source.dispatcher.send('redo placement', {
uid: this.uid,
source: source.id,
angle: source.map.transform.angle,
pitch: source.map.transform.pitch,
showCollisionBoxes: source.map.showCollisionBoxes
}, done.bind(this), this.workerID);
function done(_, data) {
this.reloadSymbolData(data, source.map.style);
source.fire('data', {tile: this, dataType: 'tile'});
// HACK this is nescessary to fix https://github.com/mapbox/mapbox-gl-js/issues/2986
if (source.map) source.map.painter.tileExtentVAO.vao = null;
this.state = 'loaded';
if (this.redoWhenDone) {
this.redoPlacement(source);
this.redoWhenDone = false;
}
}
},
getBucket: function(layer) {
return this.buckets && this.buckets[layer.ref || layer.id];
},
querySourceFeatures: function(result, params) {
if (!this.rawTileData) return;
if (!this.vtLayers) {
this.vtLayers = new vt.VectorTile(new Protobuf(this.rawTileData)).layers;
}
var layer = this.vtLayers._geojsonTileLayer || this.vtLayers[params.sourceLayer];
if (!layer) return;
var filter = featureFilter(params.filter);
var coord = { z: this.coord.z, x: this.coord.x, y: this.coord.y };
for (var i = 0; i < layer.length; i++) {
var feature = layer.feature(i);
if (filter(feature)) {
var geojsonFeature = new GeoJSONFeature(feature, this.coord.z, this.coord.x, this.coord.y);
geojsonFeature.tile = coord;
result.push(geojsonFeature);
}
}
},
hasData: function() {
return this.state === 'loaded' || this.state === 'reloading';
}
};
function unserializeBuckets(input, style) {
// Guard against the case where the map's style has been set to null while
// this bucket has been parsing.
if (!style) return;
var output = {};
for (var i = 0; i < input.length; i++) {
var layer = style.getLayer(input[i].layerId);
if (!layer) continue;
var bucket = Bucket.create(util.extend({
layer: layer,
childLayers: input[i].childLayerIds
.map(style.getLayer.bind(style))
.filter(function(layer) { return layer; })
}, input[i]));
output[bucket.id] = bucket;
}
return output;
}