mapbox-gl
Version:
A WebGL interactive maps library
1,318 lines (1,099 loc) • 3.02 MB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.mapboxgl = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
var util = require('../util/util');
module.exports = ArrayGroup;
/**
* A class that manages vertex and element arrays for a range of features. It handles initialization,
* serialization for transfer to the main thread, and certain intervening mutations.
*
* Array elements are broken into array groups based on inherent limits of WebGL. Within a group is:
*
* * A "layout" vertex array, with fixed layout, containing values calculated from layout properties.
* * Zero, one, or two element arrays, with fixed layout, typically for eventual use in
* `gl.drawElements(gl.TRIANGLES, ...)`.
* * Zero or more "paint" vertex arrays keyed by layer ID, each with a dynamic layout which depends
* on which paint properties of that layer use data-driven-functions (property functions or
* property-and-zoom functions). Values are calculated by evaluating those functions.
*
* @private
*/
function ArrayGroup(arrayTypes) {
var LayoutVertexArrayType = arrayTypes.layoutVertexArrayType;
this.layoutVertexArray = new LayoutVertexArrayType();
var ElementArrayType = arrayTypes.elementArrayType;
if (ElementArrayType) this.elementArray = new ElementArrayType();
var ElementArrayType2 = arrayTypes.elementArrayType2;
if (ElementArrayType2) this.elementArray2 = new ElementArrayType2();
this.paintVertexArrays = util.mapObject(arrayTypes.paintVertexArrayTypes, function (PaintVertexArrayType) {
return new PaintVertexArrayType();
});
}
/**
* The maximum size of a vertex array. This limit is imposed by WebGL's 16 bit
* addressing of vertex buffers.
* @private
* @readonly
*/
ArrayGroup.MAX_VERTEX_ARRAY_LENGTH = Math.pow(2, 16) - 1;
ArrayGroup.prototype.hasCapacityFor = function(numVertices) {
return this.layoutVertexArray.length + numVertices <= ArrayGroup.MAX_VERTEX_ARRAY_LENGTH;
};
ArrayGroup.prototype.isEmpty = function() {
return this.layoutVertexArray.length === 0;
};
ArrayGroup.prototype.trim = function() {
this.layoutVertexArray.trim();
if (this.elementArray) {
this.elementArray.trim();
}
if (this.elementArray2) {
this.elementArray2.trim();
}
for (var layerName in this.paintVertexArrays) {
this.paintVertexArrays[layerName].trim();
}
};
ArrayGroup.prototype.serialize = function() {
return {
layoutVertexArray: this.layoutVertexArray.serialize(),
elementArray: this.elementArray && this.elementArray.serialize(),
elementArray2: this.elementArray2 && this.elementArray2.serialize(),
paintVertexArrays: util.mapObject(this.paintVertexArrays, function(array) {
return array.serialize();
})
};
};
ArrayGroup.prototype.getTransferables = function(transferables) {
transferables.push(this.layoutVertexArray.arrayBuffer);
if (this.elementArray) {
transferables.push(this.elementArray.arrayBuffer);
}
if (this.elementArray2) {
transferables.push(this.elementArray2.arrayBuffer);
}
for (var layerName in this.paintVertexArrays) {
transferables.push(this.paintVertexArrays[layerName].arrayBuffer);
}
};
},{"../util/util":122}],2:[function(require,module,exports){
'use strict';
var ArrayGroup = require('./array_group');
var BufferGroup = require('./buffer_group');
var util = require('../util/util');
var StructArrayType = require('../util/struct_array');
var assert = require('assert');
module.exports = Bucket;
/**
* Instantiate the appropriate subclass of `Bucket` for `options`.
* @private
* @param options See `Bucket` constructor options
* @returns {Bucket}
*/
Bucket.create = function(options) {
var Classes = {
fill: require('./bucket/fill_bucket'),
fillextrusion: require('./bucket/fill_extrusion_bucket'),
line: require('./bucket/line_bucket'),
circle: require('./bucket/circle_bucket'),
symbol: require('./bucket/symbol_bucket')
};
var type = options.layer.type;
if (type === 'fill' && (!options.layer.isPaintValueFeatureConstant('fill-extrude-height') ||
!options.layer.isPaintValueZoomConstant('fill-extrude-height') ||
options.layer.getPaintValue('fill-extrude-height') !== 0)) {
type = 'fillextrusion';
}
return new Classes[type](options);
};
/**
* The maximum extent of a feature that can be safely stored in the buffer.
* In practice, all features are converted to this extent before being added.
*
* Positions are stored as signed 16bit integers.
* One bit is lost for signedness to support featuers extending past the left edge of the tile.
* One bit is lost because the line vertex buffer packs 1 bit of other data into the int.
* One bit is lost to support features extending past the extent on the right edge of the tile.
* This leaves us with 2^13 = 8192
*
* @private
* @readonly
*/
Bucket.EXTENT = 8192;
/**
* The `Bucket` class is the single point of knowledge about turning vector
* tiles into WebGL buffers.
*
* `Bucket` is an abstract class. A subclass exists for each Mapbox GL
* style spec layer type. Because `Bucket` is an abstract class,
* instances should be created via the `Bucket.create` method.
*
* @class Bucket
* @private
* @param options
* @param {number} options.zoom Zoom level of the buffers being built. May be
* a fractional zoom level.
* @param options.layer A Mapbox style layer object
* @param {Object.<string, Buffer>} options.buffers The set of `Buffer`s being
* built for this tile. This object facilitates sharing of `Buffer`s be
between `Bucket`s.
*/
function Bucket(options) {
this.zoom = options.zoom;
this.overscaling = options.overscaling;
this.layer = options.layer;
this.childLayers = options.childLayers;
this.type = this.layer.type;
this.features = [];
this.id = this.layer.id;
this.index = options.index;
this.sourceLayer = this.layer.sourceLayer;
this.sourceLayerIndex = options.sourceLayerIndex;
this.minZoom = this.layer.minzoom;
this.maxZoom = this.layer.maxzoom;
this.paintAttributes = createPaintAttributes(this);
if (options.arrays) {
var programInterfaces = this.programInterfaces;
this.bufferGroups = util.mapObject(options.arrays, function(programArrayGroups, programName) {
var programInterface = programInterfaces[programName];
var paintVertexArrayTypes = options.paintVertexArrayTypes[programName];
return programArrayGroups.map(function(arrayGroup) {
return new BufferGroup(arrayGroup, {
layoutVertexArrayType: programInterface.layoutVertexArrayType.serialize(),
elementArrayType: programInterface.elementArrayType && programInterface.elementArrayType.serialize(),
elementArrayType2: programInterface.elementArrayType2 && programInterface.elementArrayType2.serialize(),
paintVertexArrayTypes: paintVertexArrayTypes
});
});
});
}
}
/**
* Build the arrays! Features are set directly to the `features` property.
* @private
*/
Bucket.prototype.populateArrays = function() {
this.createArrays();
this.recalculateStyleLayers();
for (var i = 0; i < this.features.length; i++) {
this.addFeature(this.features[i]);
}
this.trimArrays();
};
/**
* Check if there is enough space available in the current array group for
* `vertexLength` vertices. If not, append a new array group. Should be called
* by `populateArrays` and its callees.
*
* Array groups are added to this.arrayGroups[programName].
*
* @private
* @param {string} programName the name of the program associated with the buffer that will receive the vertices
* @param {number} vertexLength The number of vertices that will be inserted to the buffer.
* @returns The current array group
*/
Bucket.prototype.prepareArrayGroup = function(programName, numVertices) {
var groups = this.arrayGroups[programName];
var currentGroup = groups.length && groups[groups.length - 1];
if (!currentGroup || !currentGroup.hasCapacityFor(numVertices)) {
currentGroup = new ArrayGroup({
layoutVertexArrayType: this.programInterfaces[programName].layoutVertexArrayType,
elementArrayType: this.programInterfaces[programName].elementArrayType,
elementArrayType2: this.programInterfaces[programName].elementArrayType2,
paintVertexArrayTypes: this.paintVertexArrayTypes[programName]
});
currentGroup.index = groups.length;
groups.push(currentGroup);
}
return currentGroup;
};
/**
* Sets up `this.paintVertexArrayTypes` as { [programName]: { [layerName]: PaintArrayType, ... }, ... }
*
* And `this.arrayGroups` as { [programName]: [], ... }; these get populated
* with array group structure over in `prepareArrayGroup`.
*
* @private
*/
Bucket.prototype.createArrays = function() {
this.arrayGroups = {};
this.paintVertexArrayTypes = {};
for (var programName in this.programInterfaces) {
this.arrayGroups[programName] = [];
var paintVertexArrayTypes = this.paintVertexArrayTypes[programName] = {};
var layerPaintAttributes = this.paintAttributes[programName];
for (var layerName in layerPaintAttributes) {
paintVertexArrayTypes[layerName] = new Bucket.VertexArrayType(layerPaintAttributes[layerName].attributes);
}
}
};
Bucket.prototype.destroy = function() {
for (var programName in this.bufferGroups) {
var programBufferGroups = this.bufferGroups[programName];
for (var i = 0; i < programBufferGroups.length; i++) {
programBufferGroups[i].destroy();
}
}
};
Bucket.prototype.trimArrays = function() {
for (var programName in this.arrayGroups) {
var arrayGroups = this.arrayGroups[programName];
for (var i = 0; i < arrayGroups.length; i++) {
arrayGroups[i].trim();
}
}
};
Bucket.prototype.isEmpty = function() {
for (var programName in this.arrayGroups) {
var arrayGroups = this.arrayGroups[programName];
for (var i = 0; i < arrayGroups.length; i++) {
if (!arrayGroups[i].isEmpty()) {
return false;
}
}
}
return true;
};
Bucket.prototype.getTransferables = function(transferables) {
for (var programName in this.arrayGroups) {
var arrayGroups = this.arrayGroups[programName];
for (var i = 0; i < arrayGroups.length; i++) {
arrayGroups[i].getTransferables(transferables);
}
}
};
Bucket.prototype.setUniforms = function(gl, programName, program, layer, globalProperties) {
var uniforms = this.paintAttributes[programName][layer.id].uniforms;
for (var i = 0; i < uniforms.length; i++) {
var uniform = uniforms[i];
var uniformLocation = program[uniform.name];
gl['uniform' + uniform.components + 'fv'](uniformLocation, uniform.getValue(layer, globalProperties));
}
};
Bucket.prototype.serialize = function() {
return {
layerId: this.layer.id,
zoom: this.zoom,
arrays: util.mapObject(this.arrayGroups, function(programArrayGroups) {
return programArrayGroups.map(function(arrayGroup) {
return arrayGroup.serialize();
});
}),
paintVertexArrayTypes: util.mapObject(this.paintVertexArrayTypes, function(arrayTypes) {
return util.mapObject(arrayTypes, function(arrayType) {
return arrayType.serialize();
});
}),
childLayerIds: this.childLayers.map(function(layer) {
return layer.id;
})
};
};
var FAKE_ZOOM_HISTORY = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 };
Bucket.prototype.recalculateStyleLayers = function() {
for (var i = 0; i < this.childLayers.length; i++) {
this.childLayers[i].recalculate(this.zoom, FAKE_ZOOM_HISTORY);
}
};
Bucket.prototype.populatePaintArrays = function(interfaceName, globalProperties, featureProperties, startGroup, startIndex) {
for (var l = 0; l < this.childLayers.length; l++) {
var layer = this.childLayers[l];
var groups = this.arrayGroups[interfaceName];
for (var g = startGroup.index; g < groups.length; g++) {
var group = groups[g];
var length = group.layoutVertexArray.length;
var paintArray = group.paintVertexArrays[layer.id];
paintArray.resize(length);
var attributes = this.paintAttributes[interfaceName][layer.id].attributes;
for (var m = 0; m < attributes.length; m++) {
var attribute = attributes[m];
var value = attribute.getValue(layer, globalProperties, featureProperties);
var multiplier = attribute.multiplier || 1;
var components = attribute.components || 1;
var start = g === startGroup.index ? startIndex : 0;
for (var i = start; i < length; i++) {
var vertex = paintArray.get(i);
for (var c = 0; c < components; c++) {
var memberName = components > 1 ? (attribute.name + c) : attribute.name;
vertex[memberName] = value[c] * multiplier;
}
}
}
}
}
};
/**
* A vertex array stores data for each vertex in a geometry. Elements are aligned to 4 byte
* boundaries for best performance in WebGL.
* @private
*/
Bucket.VertexArrayType = function (members) {
return new StructArrayType({
members: members,
alignment: 4
});
};
/**
* An element array stores Uint16 indicies of vertexes in a corresponding vertex array. With no
* arguments, it defaults to three components per element, forming triangles.
* @private
*/
Bucket.ElementArrayType = function (components) {
return new StructArrayType({
members: [{
type: 'Uint16',
name: 'vertices',
components: components || 3
}]
});
};
function createPaintAttributes(bucket) {
var attributes = {};
for (var interfaceName in bucket.programInterfaces) {
var layerPaintAttributes = attributes[interfaceName] = {};
for (var c = 0; c < bucket.childLayers.length; c++) {
var childLayer = bucket.childLayers[c];
layerPaintAttributes[childLayer.id] = {
attributes: [],
uniforms: [],
defines: [],
vertexPragmas: { define: {}, initialize: {} },
fragmentPragmas: { define: {}, initialize: {} }
};
}
var interface_ = bucket.programInterfaces[interfaceName];
if (!interface_.paintAttributes) continue;
// These tokens are replaced by arguments to the pragma
// https://github.com/mapbox/mapbox-gl-shaders#pragmas
var attributePrecision = '{precision}';
var attributeType = '{type}';
for (var i = 0; i < interface_.paintAttributes.length; i++) {
var attribute = interface_.paintAttributes[i];
attribute.multiplier = attribute.multiplier || 1;
for (var j = 0; j < bucket.childLayers.length; j++) {
var layer = bucket.childLayers[j];
var paintAttributes = layerPaintAttributes[layer.id];
var attributeInputName = attribute.name;
assert(attribute.name.slice(0, 2) === 'a_');
var attributeInnerName = attribute.name.slice(2);
var attributeVaryingDefinition;
paintAttributes.fragmentPragmas.initialize[attributeInnerName] = '';
if (layer.isPaintValueFeatureConstant(attribute.paintProperty)) {
paintAttributes.uniforms.push(attribute);
paintAttributes.fragmentPragmas.define[attributeInnerName] = paintAttributes.vertexPragmas.define[attributeInnerName] = [
'uniform',
attributePrecision,
attributeType,
attributeInputName
].join(' ') + ';';
paintAttributes.fragmentPragmas.initialize[attributeInnerName] = paintAttributes.vertexPragmas.initialize[attributeInnerName] = [
attributePrecision,
attributeType,
attributeInnerName,
'=',
attributeInputName
].join(' ') + ';\n';
} else if (layer.isPaintValueZoomConstant(attribute.paintProperty)) {
paintAttributes.attributes.push(util.extend({}, attribute, {
name: attributeInputName
}));
attributeVaryingDefinition = [
'varying',
attributePrecision,
attributeType,
attributeInnerName
].join(' ') + ';\n';
var attributeAttributeDefinition = [
paintAttributes.fragmentPragmas.define[attributeInnerName],
'attribute',
attributePrecision,
attributeType,
attributeInputName
].join(' ') + ';\n';
paintAttributes.fragmentPragmas.define[attributeInnerName] = attributeVaryingDefinition;
paintAttributes.vertexPragmas.define[attributeInnerName] = attributeVaryingDefinition + attributeAttributeDefinition;
paintAttributes.vertexPragmas.initialize[attributeInnerName] = [
attributeInnerName,
'=',
attributeInputName,
'/',
attribute.multiplier.toFixed(1)
].join(' ') + ';\n';
} else {
var tName = 'u_' + attributeInputName.slice(2) + '_t';
var zoomLevels = layer.getPaintValueStopZoomLevels(attribute.paintProperty);
// Pick the index of the first offset to add to the buffers.
// Find the four closest stops, ideally with two on each side of the zoom level.
var numStops = 0;
while (numStops < zoomLevels.length && zoomLevels[numStops] < bucket.zoom) numStops++;
var stopOffset = Math.max(0, Math.min(zoomLevels.length - 4, numStops - 2));
var fourZoomLevels = [];
for (var s = 0; s < 4; s++) {
fourZoomLevels.push(zoomLevels[Math.min(stopOffset + s, zoomLevels.length - 1)]);
}
attributeVaryingDefinition = [
'varying',
attributePrecision,
attributeType,
attributeInnerName
].join(' ') + ';\n';
paintAttributes.vertexPragmas.define[attributeInnerName] = attributeVaryingDefinition + [
'uniform',
'lowp',
'float',
tName
].join(' ') + ';\n';
paintAttributes.fragmentPragmas.define[attributeInnerName] = attributeVaryingDefinition;
paintAttributes.uniforms.push(util.extend({}, attribute, {
name: tName,
getValue: createGetUniform(attribute, stopOffset),
components: 1
}));
var components = attribute.components;
if (components === 1) {
paintAttributes.attributes.push(util.extend({}, attribute, {
getValue: createFunctionGetValue(attribute, fourZoomLevels),
isFunction: true,
components: components * 4
}));
paintAttributes.vertexPragmas.define[attributeInnerName] += [
'attribute',
attributePrecision,
'vec4',
attributeInputName
].join(' ') + ';\n';
paintAttributes.vertexPragmas.initialize[attributeInnerName] = [
attributeInnerName,
'=',
'evaluate_zoom_function_1(' + attributeInputName + ', ' + tName + ')',
'/',
attribute.multiplier.toFixed(1)
].join(' ') + ';\n';
} else {
var attributeInputNames = [];
for (var k = 0; k < 4; k++) {
attributeInputNames.push(attributeInputName + k);
paintAttributes.attributes.push(util.extend({}, attribute, {
getValue: createFunctionGetValue(attribute, [fourZoomLevels[k]]),
isFunction: true,
name: attributeInputName + k
}));
paintAttributes.vertexPragmas.define[attributeInnerName] += [
'attribute',
attributePrecision,
attributeType,
attributeInputName + k
].join(' ') + ';\n';
}
paintAttributes.vertexPragmas.initialize[attributeInnerName] = [
attributeInnerName,
' = ',
'evaluate_zoom_function_4(' + attributeInputNames.join(', ') + ', ' + tName + ')',
'/',
attribute.multiplier.toFixed(1)
].join(' ') + ';\n';
}
}
}
}
}
return attributes;
}
function createFunctionGetValue(attribute, stopZoomLevels) {
return function(layer, globalProperties, featureProperties) {
if (stopZoomLevels.length === 1) {
// return one multi-component value like color0
return attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevels[0] }), featureProperties);
} else {
// pack multiple single-component values into a four component attribute
var values = [];
for (var z = 0; z < stopZoomLevels.length; z++) {
var stopZoomLevel = stopZoomLevels[z];
values.push(attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevel }), featureProperties)[0]);
}
return values;
}
};
}
function createGetUniform(attribute, stopOffset) {
return function(layer, globalProperties) {
// stopInterp indicates which stops need to be interpolated.
// If stopInterp is 3.5 then interpolate half way between stops 3 and 4.
var stopInterp = layer.getPaintInterpolationT(attribute.paintProperty, globalProperties.zoom);
// We can only store four stop values in the buffers. stopOffset is the number of stops that come
// before the stops that were added to the buffers.
return [Math.max(0, Math.min(4, stopInterp - stopOffset))];
};
}
},{"../util/struct_array":120,"../util/util":122,"./array_group":1,"./bucket/circle_bucket":3,"./bucket/fill_bucket":4,"./bucket/fill_extrusion_bucket":5,"./bucket/line_bucket":6,"./bucket/symbol_bucket":7,"./buffer_group":9,"assert":125}],3:[function(require,module,exports){
'use strict';
var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var EXTENT = Bucket.EXTENT;
module.exports = CircleBucket;
/**
* Circles are represented by two triangles.
*
* Each corner has a pos that is the center of the circle and an extrusion
* vector that is where it points.
* @private
*/
function CircleBucket() {
Bucket.apply(this, arguments);
}
CircleBucket.prototype = util.inherit(Bucket, {});
CircleBucket.prototype.addCircleVertex = function(layoutVertexArray, x, y, extrudeX, extrudeY) {
return layoutVertexArray.emplaceBack(
(x * 2) + ((extrudeX + 1) / 2),
(y * 2) + ((extrudeY + 1) / 2));
};
CircleBucket.prototype.programInterfaces = {
circle: {
layoutVertexArrayType: new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}]),
elementArrayType: new Bucket.ElementArrayType(),
paintAttributes: [{
name: 'a_color',
components: 4,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
return layer.getPaintValue("circle-color", globalProperties, featureProperties);
},
multiplier: 255,
paintProperty: 'circle-color'
}, {
name: 'a_radius',
components: 1,
type: 'Uint16',
isLayerConstant: false,
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("circle-radius", globalProperties, featureProperties)];
},
multiplier: 10,
paintProperty: 'circle-radius'
}, {
name: 'a_blur',
components: 1,
type: 'Uint16',
isLayerConstant: false,
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("circle-blur", globalProperties, featureProperties)];
},
multiplier: 10,
paintProperty: 'circle-blur'
}, {
name: 'a_opacity',
components: 1,
type: 'Uint16',
isLayerConstant: false,
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("circle-opacity", globalProperties, featureProperties)];
},
multiplier: 255,
paintProperty: 'circle-opacity'
}]
}
};
CircleBucket.prototype.addFeature = function(feature) {
var globalProperties = {zoom: this.zoom};
var geometries = loadGeometry(feature);
var startGroup = this.prepareArrayGroup('circle', 0);
var startIndex = startGroup.layoutVertexArray.length;
for (var j = 0; j < geometries.length; j++) {
for (var k = 0; k < geometries[j].length; k++) {
var x = geometries[j][k].x;
var y = geometries[j][k].y;
// Do not include points that are outside the tile boundaries.
if (x < 0 || x >= EXTENT || y < 0 || y >= EXTENT) continue;
// this geometry will be of the Point type, and we'll derive
// two triangles from it.
//
// ┌─────────┐
// │ 3 2 │
// │ │
// │ 0 1 │
// └─────────┘
var group = this.prepareArrayGroup('circle', 4);
var layoutVertexArray = group.layoutVertexArray;
var index = this.addCircleVertex(layoutVertexArray, x, y, -1, -1);
this.addCircleVertex(layoutVertexArray, x, y, 1, -1);
this.addCircleVertex(layoutVertexArray, x, y, 1, 1);
this.addCircleVertex(layoutVertexArray, x, y, -1, 1);
group.elementArray.emplaceBack(index, index + 1, index + 2);
group.elementArray.emplaceBack(index, index + 3, index + 2);
}
}
this.populatePaintArrays('circle', globalProperties, feature.properties, startGroup, startIndex);
};
},{"../../util/util":122,"../bucket":2,"../load_geometry":11}],4:[function(require,module,exports){
'use strict';
var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var earcut = require('earcut');
var classifyRings = require('../../util/classify_rings');
var Point = require('point-geometry');
var EARCUT_MAX_RINGS = 500;
module.exports = FillBucket;
function FillBucket() {
Bucket.apply(this, arguments);
}
FillBucket.prototype = util.inherit(Bucket, {});
FillBucket.prototype.programInterfaces = {
fill: {
layoutVertexArrayType: new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}]),
elementArrayType: new Bucket.ElementArrayType(1),
elementArrayType2: new Bucket.ElementArrayType(2),
paintAttributes: [{
name: 'a_color',
components: 4,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
return layer.getPaintValue("fill-color", globalProperties, featureProperties);
},
multiplier: 255,
paintProperty: 'fill-color'
}, {
name: 'a_outline_color',
components: 4,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
return layer.getPaintValue("fill-outline-color", globalProperties, featureProperties);
},
multiplier: 255,
paintProperty: 'fill-outline-color'
}, {
name: 'a_opacity',
components: 1,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("fill-opacity", globalProperties, featureProperties)];
},
multiplier: 255,
paintProperty: 'fill-opacity'
}]
}
};
FillBucket.prototype.addVertex = function(vertexArray, x, y) {
return vertexArray.emplaceBack(x, y);
};
FillBucket.prototype.addFeature = function(feature) {
var lines = loadGeometry(feature);
var polygons = convertCoords(classifyRings(lines, EARCUT_MAX_RINGS));
this.factor = Math.pow(2, 13);
var startGroup = this.prepareArrayGroup('fill', 0);
var startIndex = startGroup.layoutVertexArray.length;
for (var i = 0; i < polygons.length; i++) {
this.addPolygon(polygons[i]);
}
this.populatePaintArrays('fill', {zoom: this.zoom}, feature.properties, startGroup, startIndex);
};
FillBucket.prototype.addPolygon = function(polygon) {
var numVertices = 0;
for (var k = 0; k < polygon.length; k++) {
numVertices += polygon[k].length;
}
var group = this.prepareArrayGroup('fill', numVertices);
var flattened = [];
var holeIndices = [];
var startIndex = group.layoutVertexArray.length;
var indices = [];
for (var r = 0; r < polygon.length; r++) {
var ring = polygon[r];
if (r > 0) holeIndices.push(flattened.length / 2);
for (var v = 0; v < ring.length; v++) {
var v1 = ring[v];
var index = this.addVertex(group.layoutVertexArray, v1[0], v1[1], 0, 0, 1, 1, 0);
indices.push(index);
if (v >= 1) {
group.elementArray2.emplaceBack(index - 1, index);
}
// convert to format used by earcut
flattened.push(v1[0]);
flattened.push(v1[1]);
}
}
var triangleIndices = earcut(flattened, holeIndices);
for (var i = 0; i < triangleIndices.length; i++) {
group.elementArray.emplaceBack(triangleIndices[i] + startIndex);
}
};
function convertCoords(rings) {
if (rings instanceof Point) return [rings.x, rings.y];
return rings.map(convertCoords);
}
},{"../../util/classify_rings":108,"../../util/util":122,"../bucket":2,"../load_geometry":11,"earcut":138,"point-geometry":193}],5:[function(require,module,exports){
'use strict';
var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var earcut = require('earcut');
var classifyRings = require('../../util/classify_rings');
var Point = require('point-geometry');
var EARCUT_MAX_RINGS = 500;
module.exports = FillExtrusionBucket;
function FillExtrusionBucket() {
Bucket.apply(this, arguments);
}
FillExtrusionBucket.prototype = util.inherit(Bucket, {});
FillExtrusionBucket.prototype.programInterfaces = {
fillextrusion: {
layoutVertexArrayType: new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}, {
name: 'a_normal',
components: 3,
type: 'Int16'
}, {
name: 'a_edgedistance',
components: 1,
type: 'Int16'
}]),
elementArrayType: new Bucket.ElementArrayType(3),
paintAttributes: [{
name: 'a_minH',
components: 1,
type: 'Uint16',
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("fill-extrude-base", globalProperties, featureProperties)];
},
multiplier: 1,
paintProperty: 'fill-extrude-base'
}, {
name: 'a_maxH',
components: 1,
type: 'Uint16',
getValue: function(layer, globalProperties, featureProperties) {
return [layer.getPaintValue("fill-extrude-height", globalProperties, featureProperties)];
},
multiplier: 1,
paintProperty: 'fill-extrude-height'
}, {
name: 'a_color',
components: 4,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
var color = layer.getPaintValue("fill-color", globalProperties, featureProperties);
color[3] = 1.0;
return color;
},
multiplier: 255,
paintProperty: 'fill-color'
}]
}
};
FillExtrusionBucket.prototype.addVertex = function(vertexArray, x, y, nx, ny, nz, t, e) {
return vertexArray.emplaceBack(
// a_pos
x,
y,
// a_normal
Math.floor(nx * this.factor) * 2 + t,
ny * this.factor * 2,
nz * this.factor * 2,
// a_edgedistance
Math.round(e)
);
};
FillExtrusionBucket.prototype.addFeature = function(feature) {
var lines = loadGeometry(feature);
var polygons = convertCoords(classifyRings(lines, EARCUT_MAX_RINGS));
this.factor = Math.pow(2, 13);
var startGroup = this.prepareArrayGroup('fillextrusion', 0);
var startIndex = startGroup.layoutVertexArray.length;
for (var i = 0; i < polygons.length; i++) {
this.addPolygon(polygons[i]);
}
this.populatePaintArrays('fillextrusion', {zoom: this.zoom}, feature.properties, startGroup, startIndex);
};
FillExtrusionBucket.prototype.addPolygon = function(polygon) {
var numVertices = 0;
for (var k = 0; k < polygon.length; k++) {
numVertices += polygon[k].length;
}
numVertices *= 5;
var group = this.prepareArrayGroup('fillextrusion', numVertices);
var flattened = [];
var holeIndices = [];
var indices = [];
for (var r = 0; r < polygon.length; r++) {
var ring = polygon[r];
if (r > 0) holeIndices.push(flattened.length / 2);
var edgeDistance = 0;
for (var v = 0; v < ring.length; v++) {
var v1 = ring[v];
var index = this.addVertex(group.layoutVertexArray, v1[0], v1[1], 0, 0, 1, 1, 0);
indices.push(index);
if (v >= 1) {
var v2 = ring[v - 1];
if (!isBoundaryEdge(v1, v2)) {
var perp = Point.convert(v1)._sub(Point.convert(v2))._perp()._unit();
var bottomRight = this.addVertex(group.layoutVertexArray, v1[0], v1[1], perp.x, perp.y, 0, 0, edgeDistance);
this.addVertex(group.layoutVertexArray, v1[0], v1[1], perp.x, perp.y, 0, 1, edgeDistance);
edgeDistance += Point.convert(v2).dist(Point.convert(v1));
this.addVertex(group.layoutVertexArray, v2[0], v2[1], perp.x, perp.y, 0, 0, edgeDistance);
this.addVertex(group.layoutVertexArray, v2[0], v2[1], perp.x, perp.y, 0, 1, edgeDistance);
group.elementArray.emplaceBack(bottomRight, bottomRight + 1, bottomRight + 2);
group.elementArray.emplaceBack(bottomRight + 1, bottomRight + 2, bottomRight + 3);
}
}
// convert to format used by earcut
flattened.push(v1[0]);
flattened.push(v1[1]);
}
}
var triangleIndices = earcut(flattened, holeIndices);
for (var j = 0; j < triangleIndices.length - 2; j += 3) {
group.elementArray.emplaceBack(indices[triangleIndices[j]],
indices[triangleIndices[j + 1]],
indices[triangleIndices[j + 2]]);
}
};
function convertCoords(rings) {
if (rings instanceof Point) return [rings.x, rings.y];
return rings.map(convertCoords);
}
function isBoundaryEdge(v1, v2) {
return v1.some(function(a, i) {
return isOutside(v2[i]) && v2[i] === a;
});
}
function isOutside(coord) {
return coord < 0 || coord > Bucket.EXTENT;
}
},{"../../util/classify_rings":108,"../../util/util":122,"../bucket":2,"../load_geometry":11,"earcut":138,"point-geometry":193}],6:[function(require,module,exports){
'use strict';
var Bucket = require('../bucket');
var util = require('../../util/util');
var loadGeometry = require('../load_geometry');
var EXTENT = Bucket.EXTENT;
// NOTE ON EXTRUDE SCALE:
// scale the extrusion vector so that the normal length is this value.
// contains the "texture" normals (-1..1). this is distinct from the extrude
// normals for line joins, because the x-value remains 0 for the texture
// normal array, while the extrude normal actually moves the vertex to create
// the acute/bevelled line join.
var EXTRUDE_SCALE = 63;
/*
* Sharp corners cause dashed lines to tilt because the distance along the line
* is the same at both the inner and outer corners. To improve the appearance of
* dashed lines we add extra points near sharp corners so that a smaller part
* of the line is tilted.
*
* COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an
* extra vertex. The default is 75 degrees.
*
* The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner.
*/
var COS_HALF_SHARP_CORNER = Math.cos(75 / 2 * (Math.PI / 180));
var SHARP_CORNER_OFFSET = 15;
// The number of bits that is used to store the line distance in the buffer.
var LINE_DISTANCE_BUFFER_BITS = 15;
// We don't have enough bits for the line distance as we'd like to have, so
// use this value to scale the line distance (in tile units) down to a smaller
// value. This lets us store longer distances while sacrificing precision.
var LINE_DISTANCE_SCALE = 1 / 2;
// The maximum line distance, in tile units, that fits in the buffer.
var MAX_LINE_DISTANCE = Math.pow(2, LINE_DISTANCE_BUFFER_BITS - 1) / LINE_DISTANCE_SCALE;
module.exports = LineBucket;
/**
* @private
*/
function LineBucket() {
Bucket.apply(this, arguments);
}
LineBucket.prototype = util.inherit(Bucket, {});
LineBucket.prototype.addLineVertex = function(layoutVertexBuffer, point, extrude, tx, ty, dir, linesofar) {
return layoutVertexBuffer.emplaceBack(
// a_pos
(point.x << 1) | tx,
(point.y << 1) | ty,
// a_data
// add 128 to store an byte in an unsigned byte
Math.round(EXTRUDE_SCALE * extrude.x) + 128,
Math.round(EXTRUDE_SCALE * extrude.y) + 128,
// Encode the -1/0/1 direction value into the first two bits of .z of a_data.
// Combine it with the lower 6 bits of `linesofar` (shifted by 2 bites to make
// room for the direction value). The upper 8 bits of `linesofar` are placed in
// the `w` component. `linesofar` is scaled down by `LINE_DISTANCE_SCALE` so that
// we can store longer distances while sacrificing precision.
((dir === 0 ? 0 : (dir < 0 ? -1 : 1)) + 1) | (((linesofar * LINE_DISTANCE_SCALE) & 0x3F) << 2),
(linesofar * LINE_DISTANCE_SCALE) >> 6);
};
LineBucket.prototype.programInterfaces = {
line: {
layoutVertexArrayType: new Bucket.VertexArrayType([{
name: 'a_pos',
components: 2,
type: 'Int16'
}, {
name: 'a_data',
components: 4,
type: 'Uint8'
}]),
paintAttributes: [{
name: 'a_color',
components: 4,
type: 'Uint8',
getValue: function(layer, globalProperties, featureProperties) {
return layer.getPaintValue("line-color", globalProperties, featureProperties);
},
multiplier: 255,
paintProperty: 'line-color'
}],
elementArrayType: new Bucket.ElementArrayType()
}
};
LineBucket.prototype.addFeature = function(feature) {
var lines = loadGeometry(feature, LINE_DISTANCE_BUFFER_BITS);
for (var i = 0; i < lines.length; i++) {
this.addLine(
lines[i],
feature.properties,
this.layer.layout['line-join'],
this.layer.layout['line-cap'],
this.layer.layout['line-miter-limit'],
this.layer.layout['line-round-limit']
);
}
};
LineBucket.prototype.addLine = function(vertices, featureProperties, join, cap, miterLimit, roundLimit) {
var len = vertices.length;
// If the line has duplicate vertices at the end, adjust length to remove them.
while (len > 2 && vertices[len - 1].equals(vertices[len - 2])) {
len--;
}
// a line must have at least two vertices
if (vertices.length < 2) return;
if (join === 'bevel') miterLimit = 1.05;
var sharpCornerOffset = SHARP_CORNER_OFFSET * (EXTENT / (512 * this.overscaling));
var firstVertex = vertices[0],
lastVertex = vertices[len - 1],
closed = firstVertex.equals(lastVertex);
// we could be more precise, but it would only save a negligible amount of space
var group = this.prepareArrayGroup('line', len * 10);
var startIndex = group.layoutVertexArray.length;
// a line may not have coincident points
if (len === 2 && closed) return;
this.distance = 0;
var beginCap = cap,
endCap = closed ? 'butt' : cap,
startOfLine = true,
currentVertex, prevVertex, nextVertex, prevNormal, nextNormal, offsetA, offsetB;
// the last three vertices added
this.e1 = this.e2 = this.e3 = -1;
if (closed) {
currentVertex = vertices[len - 2];
nextNormal = firstVertex.sub(currentVertex)._unit()._perp();
}
for (var i = 0; i < len; i++) {
nextVertex = closed && i === len - 1 ?
vertices[1] : // if the line is closed, we treat the last vertex like the first
vertices[i + 1]; // just the next vertex
// if two consecutive vertices exist, skip the current one
if (nextVertex && vertices[i].equals(nextVertex)) continue;
if (nextNormal) prevNormal = nextNormal;
if (currentVertex) prevVertex = currentVertex;
currentVertex = vertices[i];
// Calculate the normal towards the next vertex in this line. In case
// there is no next vertex, pretend that the line is continuing straight,
// meaning that we are just using the previous normal.
nextNormal = nextVertex ? nextVertex.sub(currentVertex)._unit()._perp() : prevNormal;
// If we still don't have a previous normal, this is the beginning of a
// non-closed line, so we're doing a straight "join".
prevNormal = prevNormal || nextNormal;
// Determine the normal of the join extrusion. It is the angle bisector
// of the segments between the previous line and the next line.
var joinNormal = prevNormal.add(nextNormal)._unit();
/* joinNormal prevNormal
* ↖ ↑
* .________. prevVertex
* |
* nextNormal ← | currentVertex
* |
* nextVertex !
*
*/
// Calculate the length of the miter (the ratio of the miter to the width).
// Find the cosine of the angle between the next and join normals
// using dot product. The inverse of that is the miter length.
var cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y;
var miterLength = 1 / cosHalfAngle;
var isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevVertex && nextVertex;
if (isSharpCorner && i > 0) {
var prevSegmentLength = currentVertex.dist(prevVertex);
if (prevSegmentLength > 2 * sharpCornerOffset) {
var newPrevVertex = currentVertex.sub(currentVertex.sub(prevVertex)._mult(sharpCornerOffset / prevSegmentLength)._round());
this.distance += newPrevVertex.dist(prevVertex);
this.addCurrentVertex(newPrevVertex, this.distance, prevNormal.mult(1), 0, 0, false);
prevVertex = newPrevVertex;
}
}
// The join if a middle vertex, otherwise the cap.
var middleVertex = prevVertex && nextVertex;
var currentJoin = middleVertex ? join : nextVertex ? beginCap : endCap;
if (middleVertex && currentJoin === 'round') {
if (miterLength < roundLimit) {
currentJoin = 'miter';
} else if (miterLength <= 2) {
currentJoin = 'fakeround';
}
}
if (currentJoin === 'miter' && miterLength > miterLimit) {
currentJoin = 'bevel';
}
if (currentJoin === 'bevel') {
// The maximum extrude length is 128 / 63 = 2 times the width of the line
// so if miterLength >= 2 we need to draw a different type of bevel where.
if (miterLength > 2) currentJoin = 'flipbevel';
// If the miterLength is really small and the line bevel wouldn't be visible,
// just draw a miter join to save a triangle.
if (miterLength < miterLimit) currentJoin = 'miter';
}
// Calculate how far along the line the currentVertex is
if (prevVertex) this.distance += currentVertex.dist(prevVertex);
if (currentJoin === 'miter') {
joinNormal._mult(miterLength);
this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false);
} else if (currentJoin === 'flipbevel') {
// miter is too big, flip the direction to make a beveled join
if (miterLength > 100) {
// Almost parallel lines
joinNormal = nextNormal.clone();
} else {
var direction = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0 ? -1 : 1;
var bevelLength = miterLength * prevNormal.add(nextNormal).mag() / prevNormal.sub(nextNormal).mag();
joinNormal._perp()._mult(bevelLength * direction);
}
this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false);
this.addCurrentVertex(currentVertex, this.distance, joinNormal.mult(-1), 0, 0, false);
} else if (currentJoin === 'bevel' || currentJoin === 'fakeround') {
var lineTurnsLeft = (prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x) > 0;
var offset = -Math.sqrt(miterLength * miterLength - 1);
if (lineTurnsLeft) {
offsetB = 0;
offsetA = offset;
} else {
offsetA = 0;
offsetB = offset;
}
// Close previous segment with a bevel
if (!startOfLine) {
this.addCurrentVertex(currentVertex, this.distance, prevNormal, offsetA, offsetB, false);
}
if (currentJoin === 'fakeround') {
// The join angle is sharp enough that a round join would be visible.
// Bevel joins fill the gap between segments with a single pie slice triangle.
// Create a round join by adding multiple pie slices. The join isn't actually round, but
// it looks like it is at the sizes we render lines at.
// Add more triangles for sharper angles.
// This math is just a go