UNPKG

mapbox-gl

Version:
1,318 lines (1,099 loc) 3.02 MB
(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