UNPKG

mapbox-gl

Version:
1,436 lines (1,194 loc) 2.24 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 featureFilter = require('feature-filter'); var ElementGroups = require('./element_groups'); var Buffer = require('./buffer'); var StyleLayer = require('../style/style_layer'); 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('./fill_bucket'), line: require('./line_bucket'), circle: require('./circle_bucket'), symbol: require('./symbol_bucket') }; return new Classes[options.layer.type](options); }; Bucket.AttributeType = Buffer.AttributeType; /** * The `Bucket` class builds a set of `Buffer`s for a set of vector tile * features. * * `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. * * For performance reasons, `Bucket` creates its "add"s methods at * runtime using `new Function(...)`. * * @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 GL 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 = StyleLayer.create(options.layer); this.layer.recalculate(this.zoom, { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 }); this.layers = [this.layer.id]; this.type = this.layer.type; this.features = []; this.id = this.layer.id; this['source-layer'] = this.layer['source-layer']; this.interactive = this.layer.interactive; this.minZoom = this.layer.minzoom; this.maxZoom = this.layer.maxzoom; this.filter = featureFilter(this.layer.filter); this.resetBuffers(options.buffers); for (var shaderName in this.shaders) { var shader = this.shaders[shaderName]; this[this.getAddMethodName(shaderName, 'vertex')] = createVertexAddMethod( shaderName, shader, this.getBufferName(shaderName, 'vertex') ); } } /** * Build the buffers! Features are set directly to the `features` property. * @private */ Bucket.prototype.addFeatures = function() { for (var i = 0; i < this.features.length; i++) { this.addFeature(this.features[i]); } }; /** * Check if there is enough space available in the current element group for * `vertexLength` vertices. If not, append a new elementGroup. Should be called * by `addFeatures` and its callees. * @private * @param {string} shaderName the name of the shader associated with the buffer that will receive the vertices * @param {number} vertexLength The number of vertices that will be inserted to the buffer. */ Bucket.prototype.makeRoomFor = function(shaderName, vertexLength) { return this.elementGroups[shaderName].makeRoomFor(vertexLength); }; /** * Start using a new shared `buffers` object and recreate instances of `Buffer` * as necessary. * @private * @param {Object.<string, Buffer>} buffers */ Bucket.prototype.resetBuffers = function(buffers) { this.buffers = buffers; this.elementGroups = {}; for (var shaderName in this.shaders) { var shader = this.shaders[shaderName]; var vertexBufferName = this.getBufferName(shaderName, 'vertex'); if (shader.vertexBuffer && !buffers[vertexBufferName]) { buffers[vertexBufferName] = new Buffer({ type: Buffer.BufferType.VERTEX, attributes: shader.attributes }); } if (shader.elementBuffer) { var elementBufferName = this.getBufferName(shaderName, 'element'); if (!buffers[elementBufferName]) { buffers[elementBufferName] = createElementBuffer(shader.elementBufferComponents); } this[this.getAddMethodName(shaderName, 'element')] = createElementAddMethod(this.buffers[elementBufferName]); } if (shader.secondElementBuffer) { var secondElementBufferName = this.getBufferName(shaderName, 'secondElement'); if (!buffers[secondElementBufferName]) { buffers[secondElementBufferName] = createElementBuffer(shader.secondElementBufferComponents); } this[this.getAddMethodName(shaderName, 'secondElement')] = createElementAddMethod(this.buffers[secondElementBufferName]); } this.elementGroups[shaderName] = new ElementGroups( buffers[this.getBufferName(shaderName, 'vertex')], buffers[this.getBufferName(shaderName, 'element')], buffers[this.getBufferName(shaderName, 'secondElement')] ); } }; /** * Get the name of the method used to add an item to a buffer. * @param {string} shaderName The name of the shader that will use the buffer * @param {string} type One of "vertex", "element", or "secondElement" * @returns {string} */ Bucket.prototype.getAddMethodName = function(shaderName, type) { return 'add' + capitalize(shaderName) + capitalize(type); }; /** * Get the name of a buffer. * @param {string} shaderName The name of the shader that will use the buffer * @param {string} type One of "vertex", "element", or "secondElement" * @returns {string} */ Bucket.prototype.getBufferName = function(shaderName, type) { return shaderName + capitalize(type); }; var createVertexAddMethodCache = {}; function createVertexAddMethod(shaderName, shader, bufferName) { var pushArgs = []; for (var i = 0; i < shader.attributes.length; i++) { pushArgs = pushArgs.concat(shader.attributes[i].value); } var body = 'return this.buffers.' + bufferName + '.push(' + pushArgs.join(', ') + ');'; if (!createVertexAddMethodCache[body]) { createVertexAddMethodCache[body] = new Function(shader.attributeArgs, body); } return createVertexAddMethodCache[body]; } function createElementAddMethod(buffer) { return function(one, two, three) { return buffer.push(one, two, three); }; } function createElementBuffer(components) { return new Buffer({ type: Buffer.BufferType.ELEMENT, attributes: [{ name: 'vertices', components: components || 3, type: Buffer.ELEMENT_ATTRIBUTE_TYPE }] }); } function capitalize(string) { return string.charAt(0).toUpperCase() + string.slice(1); } },{"../style/style_layer":47,"./buffer":2,"./circle_bucket":3,"./element_groups":4,"./fill_bucket":6,"./line_bucket":7,"./symbol_bucket":9,"feature-filter":107}],2:[function(require,module,exports){ 'use strict'; // Note: all "sizes" are measured in bytes var assert = require('assert'); /** * The `Buffer` class is responsible for managing one instance of `ArrayBuffer`. `ArrayBuffer`s * provide low-level read/write access to a chunk of memory. `ArrayBuffer`s are populated with * per-vertex data, uploaded to the GPU, and used in rendering. * * `Buffer` provides an abstraction over `ArrayBuffer`, making it behave like an array of * statically typed structs. A buffer is comprised of items. An item is comprised of a set of * attributes. Attributes are defined when the class is constructed. * * @class Buffer * @private * @param options * @param {BufferType} options.type * @param {Array.<BufferAttribute>} options.attributes */ function Buffer(options) { this.type = options.type; // Clone an existing Buffer if (options.arrayBuffer) { this.capacity = options.capacity; this.arrayBuffer = options.arrayBuffer; this.attributes = options.attributes; this.itemSize = options.itemSize; this.length = options.length; // Create a new Buffer } else { this.capacity = align(Buffer.CAPACITY_DEFAULT, Buffer.CAPACITY_ALIGNMENT); this.arrayBuffer = new ArrayBuffer(this.capacity); this.attributes = []; this.itemSize = 0; this.length = 0; // Vertex buffer attributes must be aligned to word boundaries but // element buffer attributes do not need to be aligned. var attributeAlignment = this.type === Buffer.BufferType.VERTEX ? Buffer.VERTEX_ATTRIBUTE_ALIGNMENT : 1; this.attributes = options.attributes.map(function(attributeOptions) { var attribute = {}; attribute.name = attributeOptions.name; attribute.components = attributeOptions.components || 1; attribute.type = attributeOptions.type || Buffer.AttributeType.UNSIGNED_BYTE; attribute.size = attribute.type.size * attribute.components; attribute.offset = this.itemSize; this.itemSize = align(attribute.offset + attribute.size, attributeAlignment); assert(!isNaN(this.itemSize)); assert(!isNaN(attribute.size)); assert(attribute.type.name in Buffer.AttributeType); return attribute; }, this); // These are expensive calls. Because we only push things to buffers in // the worker thread, we can skip in the "clone an existing buffer" case. this._createPushMethod(); this._refreshViews(); } } /** * Bind this buffer to a WebGL context. * @private * @param gl The WebGL context */ Buffer.prototype.bind = function(gl) { var type = gl[this.type]; if (!this.buffer) { this.buffer = gl.createBuffer(); gl.bindBuffer(type, this.buffer); gl.bufferData(type, this.arrayBuffer.slice(0, this.length * this.itemSize), gl.STATIC_DRAW); // dump array buffer once it's bound to gl this.arrayBuffer = null; } else { gl.bindBuffer(type, this.buffer); } }; /** * Destroy the GL buffer bound to the given WebGL context * @private * @param gl The WebGL context */ Buffer.prototype.destroy = function(gl) { if (this.buffer) { gl.deleteBuffer(this.buffer); } }; /** * Set the attribute pointers in a WebGL context according to the buffer's attribute layout * @private * @param gl The WebGL context * @param shader The active WebGL shader * @param {number} offset The offset of the attribute data in the currently bound GL buffer. */ Buffer.prototype.setAttribPointers = function(gl, shader, offset) { for (var i = 0; i < this.attributes.length; i++) { var attrib = this.attributes[i]; gl.vertexAttribPointer( shader['a_' + attrib.name], attrib.components, gl[attrib.type.name], false, this.itemSize, offset + attrib.offset); } }; /** * Get an item from the `ArrayBuffer`. Only used for debugging. * @private * @param {number} index The index of the item to get * @returns {Object.<string, Array.<number>>} */ Buffer.prototype.get = function(index) { this._refreshViews(); var item = {}; var offset = index * this.itemSize; for (var i = 0; i < this.attributes.length; i++) { var attribute = this.attributes[i]; var values = item[attribute.name] = []; for (var j = 0; j < attribute.components; j++) { var componentOffset = ((offset + attribute.offset) / attribute.type.size) + j; values.push(this.views[attribute.type.name][componentOffset]); } } return item; }; /** * Check that a buffer item is well formed and throw an error if not. Only * used for debugging. * @private * @param {number} args The "arguments" object from Buffer::push */ Buffer.prototype.validate = function(args) { var argIndex = 0; for (var i = 0; i < this.attributes.length; i++) { for (var j = 0; j < this.attributes[i].components; j++) { assert(!isNaN(args[argIndex++])); } } assert(argIndex === args.length); }; Buffer.prototype._resize = function(capacity) { var old = this.views.UNSIGNED_BYTE; this.capacity = align(capacity, Buffer.CAPACITY_ALIGNMENT); this.arrayBuffer = new ArrayBuffer(this.capacity); this._refreshViews(); this.views.UNSIGNED_BYTE.set(old); }; Buffer.prototype._refreshViews = function() { this.views = { UNSIGNED_BYTE: new Uint8Array(this.arrayBuffer), BYTE: new Int8Array(this.arrayBuffer), UNSIGNED_SHORT: new Uint16Array(this.arrayBuffer), SHORT: new Int16Array(this.arrayBuffer) }; }; var createPushMethodCache = {}; Buffer.prototype._createPushMethod = function() { var body = ''; var argNames = []; body += 'var i = this.length++;\n'; body += 'var o = i * ' + this.itemSize + ';\n'; body += 'if (o + ' + this.itemSize + ' > this.capacity) { this._resize(this.capacity * 1.5); }\n'; for (var i = 0; i < this.attributes.length; i++) { var attribute = this.attributes[i]; var offsetId = 'o' + i; body += '\nvar ' + offsetId + ' = (o + ' + attribute.offset + ') / ' + attribute.type.size + ';\n'; for (var j = 0; j < attribute.components; j++) { var rvalue = 'v' + argNames.length; var lvalue = 'this.views.' + attribute.type.name + '[' + offsetId + ' + ' + j + ']'; body += lvalue + ' = ' + rvalue + ';\n'; argNames.push(rvalue); } } body += '\nreturn i;\n'; if (!createPushMethodCache[body]) { createPushMethodCache[body] = new Function(argNames, body); } this.push = createPushMethodCache[body]; }; /** * @typedef BufferAttribute * @private * @property {string} name * @property {number} components * @property {BufferAttributeType} type * @property {number} size * @property {number} offset */ /** * @enum {string} BufferType * @private * @readonly */ Buffer.BufferType = { VERTEX: 'ARRAY_BUFFER', ELEMENT: 'ELEMENT_ARRAY_BUFFER' }; /** * @enum {{size: number, name: string}} BufferAttributeType * @private * @readonly */ Buffer.AttributeType = { BYTE: { size: 1, name: 'BYTE' }, UNSIGNED_BYTE: { size: 1, name: 'UNSIGNED_BYTE' }, SHORT: { size: 2, name: 'SHORT' }, UNSIGNED_SHORT: { size: 2, name: 'UNSIGNED_SHORT' } }; /** * An `BufferType.ELEMENT` buffer holds indicies of a corresponding `BufferType.VERTEX` buffer. * These indicies are stored in the `BufferType.ELEMENT` buffer as `UNSIGNED_SHORT`s. * * @property {BufferAttributeType} * @private * @readonly */ Buffer.ELEMENT_ATTRIBUTE_TYPE = Buffer.AttributeType.UNSIGNED_SHORT; /** * 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 * * @property {number} * @private * @readonly */ Buffer.EXTENT = 8192; /** * @property {number} * @private * @readonly */ Buffer.CAPACITY_DEFAULT = 8192; /** * WebGL performs best if buffer sizes are aligned to 2 byte boundaries. * @property {number} * @private * @readonly */ Buffer.CAPACITY_ALIGNMENT = 2; /** * WebGL performs best if vertex attribute offsets are aligned to 4 byte boundaries. * @property {number} * @private * @readonly */ Buffer.VERTEX_ATTRIBUTE_ALIGNMENT = 4; function align(value, alignment) { alignment = alignment || 1; var remainder = value % alignment; if (alignment !== 1 && remainder !== 0) { value += (alignment - remainder); } return value; } module.exports = Buffer; },{"assert":100}],3:[function(require,module,exports){ 'use strict'; var Bucket = require('./bucket'); var util = require('../util/util'); var loadGeometry = require('./load_geometry'); var EXTENT = require('./buffer').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.shaders = { circle: { vertexBuffer: true, elementBuffer: true, attributeArgs: ['x', 'y', 'extrudeX', 'extrudeY'], attributes: [{ name: 'pos', components: 2, type: Bucket.AttributeType.SHORT, value: [ '(x * 2) + ((extrudeX + 1) / 2)', '(y * 2) + ((extrudeY + 1) / 2)' ] }] } }; CircleBucket.prototype.addFeature = function(feature) { var geometries = loadGeometry(feature); for (var j = 0; j < geometries.length; j++) { var geometry = geometries[j]; for (var k = 0; k < geometry.length; k++) { var group = this.makeRoomFor('circle', 4); var x = geometry[k].x; var y = geometry[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 index = this.addCircleVertex(x, y, -1, -1) - group.vertexStartIndex; this.addCircleVertex(x, y, 1, -1); this.addCircleVertex(x, y, 1, 1); this.addCircleVertex(x, y, -1, 1); group.vertexLength += 4; this.addCircleElement(index, index + 1, index + 2); this.addCircleElement(index, index + 3, index + 2); group.elementLength += 2; } } }; },{"../util/util":99,"./bucket":1,"./buffer":2,"./load_geometry":8}],4:[function(require,module,exports){ 'use strict'; module.exports = ElementGroups; function ElementGroups(vertexBuffer, elementBuffer, secondElementBuffer) { this.vertexBuffer = vertexBuffer; this.elementBuffer = elementBuffer; this.secondElementBuffer = secondElementBuffer; this.groups = []; } ElementGroups.prototype.makeRoomFor = function(numVertices) { if (!this.current || this.current.vertexLength + numVertices > 65535) { this.current = new ElementGroup(this.vertexBuffer.length, this.elementBuffer && this.elementBuffer.length, this.secondElementBuffer && this.secondElementBuffer.length); this.groups.push(this.current); } return this.current; }; function ElementGroup(vertexStartIndex, elementStartIndex, secondElementStartIndex) { // the offset into the vertex buffer of the first vertex in this group this.vertexStartIndex = vertexStartIndex; this.elementStartIndex = elementStartIndex; this.secondElementStartIndex = secondElementStartIndex; this.elementLength = 0; this.vertexLength = 0; this.secondElementLength = 0; } },{}],5:[function(require,module,exports){ 'use strict'; var rbush = require('rbush'); var Point = require('point-geometry'); var vt = require('vector-tile'); var util = require('../util/util'); var loadGeometry = require('./load_geometry'); var EXTENT = require('./buffer').EXTENT; module.exports = FeatureTree; function FeatureTree(coord, overscaling) { this.x = coord.x; this.y = coord.y; this.z = coord.z - Math.log(overscaling) / Math.LN2; this.rtree = rbush(9); this.toBeInserted = []; } FeatureTree.prototype.insert = function(bbox, layers, feature) { var scale = EXTENT / feature.extent; bbox[0] *= scale; bbox[1] *= scale; bbox[2] *= scale; bbox[3] *= scale; bbox.layers = layers; bbox.feature = feature; this.toBeInserted.push(bbox); }; // bulk insert into tree FeatureTree.prototype._load = function() { this.rtree.load(this.toBeInserted); this.toBeInserted = []; }; // Finds features in this tile at a particular position. FeatureTree.prototype.query = function(args, callback) { if (this.toBeInserted.length) this._load(); var params = args.params || {}, x = args.x, y = args.y, result = []; var radius, bounds; if (typeof x !== 'undefined' && typeof y !== 'undefined') { // a point (or point+radius) query radius = (params.radius || 0) * EXTENT / args.tileSize / args.scale; bounds = [x - radius, y - radius, x + radius, y + radius]; } else { // a rectangle query bounds = [ args.minX, args.minY, args.maxX, args.maxY ]; } var matching = this.rtree.search(bounds); for (var i = 0; i < matching.length; i++) { var feature = matching[i].feature, layers = matching[i].layers, type = vt.VectorTileFeature.types[feature.type]; if (params.$type && type !== params.$type) continue; if (radius && !geometryContainsPoint(loadGeometry(feature), type, new Point(x, y), radius)) continue; else if (!geometryIntersectsBox(loadGeometry(feature), type, bounds)) continue; var geoJSON = feature.toGeoJSON(this.x, this.y, this.z); if (!params.includeGeometry) { geoJSON.geometry = null; } for (var l = 0; l < layers.length; l++) { var layer = layers[l]; if (params.layerIds && params.layerIds.indexOf(layer) < 0) continue; result.push(util.extend({layer: layer}, geoJSON)); } } callback(null, result); }; function geometryIntersectsBox(rings, type, bounds) { return type === 'Point' ? pointIntersectsBox(rings, bounds) : type === 'LineString' ? lineIntersectsBox(rings, bounds) : type === 'Polygon' ? polyIntersectsBox(rings, bounds) || lineIntersectsBox(rings, bounds) : false; } // Tests whether any of the four corners of the bbox are contained in the // interior of the polygon. Otherwise, defers to lineIntersectsBox. function polyIntersectsBox(rings, bounds) { if (polyContainsPoint(rings, new Point(bounds[0], bounds[1])) || polyContainsPoint(rings, new Point(bounds[0], bounds[3])) || polyContainsPoint(rings, new Point(bounds[2], bounds[1])) || polyContainsPoint(rings, new Point(bounds[2], bounds[3]))) return true; return lineIntersectsBox(rings, bounds); } // Only needs to cover the case where the line crosses the bbox boundary. // Otherwise, pointIntersectsBox should have us covered. function lineIntersectsBox(rings, bounds) { for (var k = 0; k < rings.length; k++) { var ring = rings[k]; for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) { var p0 = ring[i]; var p1 = ring[j]; // invert the segment so as to reuse segmentCrossesHorizontal for // checking whether it crosses the vertical sides of the bbox. var i0 = new Point(p0.y, p0.x); var i1 = new Point(p1.y, p1.x); if (segmentCrossesHorizontal(p0, p1, bounds[0], bounds[2], bounds[1]) || segmentCrossesHorizontal(p0, p1, bounds[0], bounds[2], bounds[3]) || segmentCrossesHorizontal(i0, i1, bounds[1], bounds[3], bounds[0]) || segmentCrossesHorizontal(i0, i1, bounds[1], bounds[3], bounds[2])) return true; } } return pointIntersectsBox(rings, bounds); } /* * Answer whether segment p1-p2 intersects with (x1, y)-(x2, y) * Assumes x2 >= x1 */ function segmentCrossesHorizontal(p0, p1, x1, x2, y) { if (p1.y === p0.y) return p1.y === y && Math.min(p0.x, p1.x) <= x2 && Math.max(p0.x, p1.x) >= x1; var r = (y - p0.y) / (p1.y - p0.y); var x = p0.x + r * (p1.x - p0.x); return (x >= x1 && x <= x2 && r <= 1 && r >= 0); } function pointIntersectsBox(rings, bounds) { for (var i = 0; i < rings.length; i++) { var ring = rings[i]; for (var j = 0; j < ring.length; j++) { if (ring[j].x >= bounds[0] && ring[j].y >= bounds[1] && ring[j].x <= bounds[2] && ring[j].y <= bounds[3]) return true; } } return false; } function geometryContainsPoint(rings, type, p, radius) { return type === 'Point' ? pointContainsPoint(rings, p, radius) : type === 'LineString' ? lineContainsPoint(rings, p, radius) : type === 'Polygon' ? polyContainsPoint(rings, p) || lineContainsPoint(rings, p, radius) : false; } // Code from http://stackoverflow.com/a/1501725/331379. function distToSegmentSquared(p, v, w) { var l2 = v.distSqr(w); if (l2 === 0) return p.distSqr(v); var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2; if (t < 0) return p.distSqr(v); if (t > 1) return p.distSqr(w); return p.distSqr(w.sub(v)._mult(t)._add(v)); } function lineContainsPoint(rings, p, radius) { var r = radius * radius; for (var i = 0; i < rings.length; i++) { var ring = rings[i]; for (var j = 1; j < ring.length; j++) { // Find line segments that have a distance <= radius^2 to p // In that case, we treat the line as "containing point p". var v = ring[j - 1], w = ring[j]; if (distToSegmentSquared(p, v, w) < r) return true; } } return false; } // point in polygon ray casting algorithm function polyContainsPoint(rings, p) { var c = false, ring, p1, p2; for (var k = 0; k < rings.length; k++) { ring = rings[k]; for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) { p1 = ring[i]; p2 = ring[j]; if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) { c = !c; } } } return c; } function pointContainsPoint(rings, p, radius) { var r = radius * radius; for (var i = 0; i < rings.length; i++) { var ring = rings[i]; for (var j = 0; j < ring.length; j++) { if (ring[j].distSqr(p) <= r) return true; } } return false; } },{"../util/util":99,"./buffer":2,"./load_geometry":8,"point-geometry":153,"rbush":154,"vector-tile":159}],6:[function(require,module,exports){ 'use strict'; var Bucket = require('./bucket'); var util = require('../util/util'); var loadGeometry = require('./load_geometry'); module.exports = FillBucket; function FillBucket() { Bucket.apply(this, arguments); } FillBucket.prototype = util.inherit(Bucket, {}); FillBucket.prototype.shaders = { fill: { vertexBuffer: true, elementBuffer: true, secondElementBuffer: true, secondElementBufferComponents: 2, attributeArgs: ['x', 'y'], attributes: [{ name: 'pos', components: 2, type: Bucket.AttributeType.SHORT, value: ['x', 'y'] }] } }; FillBucket.prototype.addFeature = function(feature) { var lines = loadGeometry(feature); for (var i = 0; i < lines.length; i++) { this.addFill(lines[i]); } }; FillBucket.prototype.addFill = function(vertices) { if (vertices.length < 3) { //console.warn('a fill must have at least three vertices'); return; } // Calculate the total number of vertices we're going to produce so that we // can resize the buffer beforehand, or detect whether the current line // won't fit into the buffer anymore. // In order to be able to use the vertex buffer for drawing the antialiased // outlines, we separate all polygon vertices with a degenerate (out-of- // viewplane) vertex. var len = vertices.length; // Expand this geometry buffer to hold all the required vertices. var group = this.makeRoomFor('fill', len + 1); // We're generating triangle fans, so we always start with the first coordinate in this polygon. var firstIndex, prevIndex; for (var i = 0; i < vertices.length; i++) { var currentVertex = vertices[i]; var currentIndex = this.addFillVertex(currentVertex.x, currentVertex.y) - group.vertexStartIndex; group.vertexLength++; if (i === 0) firstIndex = currentIndex; // Only add triangles that have distinct vertices. if (i >= 2 && (currentVertex.x !== vertices[0].x || currentVertex.y !== vertices[0].y)) { this.addFillElement(firstIndex, prevIndex, currentIndex); group.elementLength++; } if (i >= 1) { this.addFillSecondElement(prevIndex, currentIndex); group.secondElementLength++; } prevIndex = currentIndex; } }; },{"../util/util":99,"./bucket":1,"./load_geometry":8}],7:[function(require,module,exports){ 'use strict'; var Bucket = require('./bucket'); var util = require('../util/util'); var loadGeometry = require('./load_geometry'); var EXTENT = require('./buffer').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; module.exports = LineBucket; function LineBucket() { Bucket.apply(this, arguments); } LineBucket.prototype = util.inherit(Bucket, {}); LineBucket.prototype.shaders = { line: { vertexBuffer: true, elementBuffer: true, attributeArgs: ['point', 'extrude', 'tx', 'ty', 'dir', 'linesofar'], attributes: [{ name: 'pos', components: 2, type: Bucket.AttributeType.SHORT, value: [ '(point.x << 1) | tx', '(point.y << 1) | ty' ] }, { name: 'data', components: 4, type: Bucket.AttributeType.BYTE, value: [ 'Math.round(' + EXTRUDE_SCALE + ' * extrude.x)', 'Math.round(' + EXTRUDE_SCALE + ' * extrude.y)', // Encode the -1/0/1 direction value into .zw coordinates of a_data, which is normally covered // by linesofar, so we need to merge them. // The z component's first bit, as well as the sign bit is reserved for the direction, // so we need to shift the linesofar. '((dir < 0) ? -1 : 1) * ((dir ? 1 : 0) | ((linesofar << 1) & 0x7F))', '(linesofar >> 6) & 0x7F' ] }] } }; LineBucket.prototype.addFeature = function(feature) { var lines = loadGeometry(feature); for (var i = 0; i < lines.length; i++) { this.addLine( lines[i], 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, 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--; } if (vertices.length < 2) { //console.warn('a line must have at least two vertices'); 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 this.makeRoomFor('line', len * 10); if (len === 2 && closed) { // console.warn('a line may not have coincident points'); return; } var beginCap = cap, endCap = closed ? 'butt' : cap, flip = 1, distance = 0, 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()); distance += newPrevVertex.dist(prevVertex); this.addCurrentVertex(newPrevVertex, flip, 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) distance += currentVertex.dist(prevVertex); if (currentJoin === 'miter') { joinNormal._mult(miterLength); this.addCurrentVertex(currentVertex, flip, 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, flip, distance, joinNormal, 0, 0, false); this.addCurrentVertex(currentVertex, -flip, distance, joinNormal, 0, 0, false); } else if (currentJoin === 'bevel' || currentJoin === 'fakeround') { var lineTurnsLeft = flip * (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, flip, 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 good enough approximation. It isn't "correct". var n = Math.floor((0.5 - (cosHalfAngle - 0.5)) * 8); var approxFractionalJoinNormal; for (var m = 0; m < n; m++) { approxFractionalJoinNormal = nextNormal.mult((m + 1) / (n + 1))._add(prevNormal)._unit(); this.addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft); } this.addPieSliceVertex(currentVertex, flip, distance, joinNormal, lineTurnsLeft); for (var k = n - 1; k >= 0; k--) { approxFractionalJoinNormal = prevNormal.mult((k + 1) / (n + 1))._add(nextNormal)._unit(); this.addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft); } } // Start next segment if (nextVertex) { this.addCurrentVertex(currentVertex, flip, distance, nextNormal, -offsetA, -offsetB, false); } } else if (currentJoin === 'butt') { if (!startOfLine) { // Close previous segment with a butt this.addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false); } // Start next segment with a butt if (nextVertex) { this.addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false); } } else if (currentJoin === 'square') { if (!startOfLine) { // Close previous segment with a square cap this.addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, false); // The segment is done. Unset vertices to disconnect segments. this.e1 = this.e2 = -1; flip = 1; } // Start next segment if (nextVertex) { this.addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, false); } } else if (currentJoin === 'round') { if (!startOfLine) { // Close previous segment with butt this.addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false); // Add round cap or linejoin at end of segment this.addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, true); // The segment is done. Unset vertices to disconnect segments. this.e1 = this.e2 = -1; flip = 1; } // Start next segment with a butt if (nextVertex) { // Add round cap before first segment this.addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true); this.addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false); } } if (isSharpCorner && i < len - 1) { var nextSegmentLength = currentVertex.dist(nextVertex); if (nextSegmentLength > 2 * sharpCornerOffset) { var newCurrentVertex = currentVertex.add(nextVertex.sub(currentVertex)._mult(sharpCornerOffset / nextSegmentLength)._round()); distance += newCurrentVertex.dist(currentVertex); this.addCurrentVertex(newCurrentVertex, flip, distance, nextNormal.mult(1), 0, 0, false); currentVertex = newCurrentVertex; } } startOfLine = false; } }; /** * Add two vertices to the buffers. * * @param {Object} currentVertex the line vertex to add buffer vertices for * @param {number} flip -1 if the vertices should be flipped, 1 otherwise * @param {number} distance the distance from the beginning of the line to the vertex * @param {number} endLeft extrude to shift the left vertex along the line * @param {number} endRight extrude to shift the left vertex along the line * @param {boolean} round whether this is a round cap * @private */ LineBucket.prototype.addCurrentVertex = function(currentVertex, flip, distance, normal, endLeft, endRight, round) { var tx = round ? 1 : 0; var extrude; var group = this.elementGroups.line.current; group.vertexLength += 2; extrude = normal.mult(flip); if (endLeft) extrude._sub(normal.perp()._mult(endLeft)); this.e3 = this.addLineVertex(currentVertex, extrude, tx, 0, endLeft, distance) - group.vertexStartIndex; if (this.e1 >= 0 && this.e2 >= 0) { this.addLineElement(this.e1, this.e2, this.e3); group.elementLength++; } this.e1 = this.e2; this.e2 = this.e3; extrude = normal.mult(-flip); if (endRight) extrude._sub(normal.perp()._mult(endRight)); this.e3 = this.addLineVertex(currentVertex, extrude, tx, 1, -endRight, distance) - group.vertexStartIndex; if (this.e1 >= 0 && this.e2 >= 0) { this.addLineElement(this.e1, this.e2, this.e3); group.elementLength++; } this.e1 = this.e2; this.e2 = this.e3; }; /** * Add a single new vertex and a triangle using two previous vertices. * This adds a pie slice triangle near a join to simulate round joins * * @param {Object} currentVertex the line vertex to add buffer vertices for * @param {number} flip -1 if the vertices should be flipped, 1 otherwise * @param {number} distance the distance from the beggining of the line to the vertex * @param {Object} extrude the offset of the new vertex from the currentVertex * @param {boolean} whether the line is turning left or right at this angle * @private */ LineBucket.prototype.addPieSliceVertex = function(currentVertex, flip, distance, extrude, lineTurnsLeft) { var ty = lineTurnsLeft ? 1 : 0; extrude = extrude.mult(flip * (lineTurnsLeft ? -1 : 1)); var group = this.elementGroups.line.current; this.e3 = this.addLineVertex(currentVertex, extrude, 0, ty, 0, distance) - group.vertexStartIndex; group.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { this.addLineElement(this.e1, this.e2, this.e3); group.elementLength++; } if (lineTurnsLeft) { this.e2 = this.e3; } else { this.e1 = this.e3; } }; },{"../util/util":99,"./bucket":1,"./buffer":2,"./load_geometry":8}],8:[function(require,module,exports){ 'use strict'; var EXTENT = require('./buffer').EXTENT; /** * Loads a geometry from a VectorTileFeature and scales it to the common extent * used internally. * @private */ module.exports = function loadGeometry(feature) { var scale = EXTENT / feature.extent; var geometry = feature.loadGeometry(); for (var r = 0; r < geometry.length; r++) { var ring = geometry[r]; for (var p = 0; p < ring.length; p++) { var point = ring[p]; // round here because mapbox-gl-native uses integers to represent // points and we need to do the same to avoid renering differences. point.x = Math.round(point.x * scale); point.y = Math.round(point.y * scale); } } return geometry; }; },{"./buffer":2}],9:[function(require,module,exports){ 'use strict'; var Point = require('point-geometry'); var Bucket = require('./bucket'); var ElementGroups = require('./element_groups'); var Anchor = require('../symbol/anchor'); var getAnchors = require('../symbol/get_anchors'); var resolveTokens = require('../util/token'); var Quads = require('../symbol/quads'); var Shaping = require('../symbol/shaping'); var resolveText = require('../symbol/resolve_text'); var mergeLines = require('../symbol/mergelines'); var shapeText = Shaping.shapeText; var shapeIcon = Shaping.shapeIcon; var getGlyphQuads = Quads.getGlyphQuads; var getIconQuads = Quads.getIconQuads; var clipLine = require('../symbol/clip_line'); var util = require('../util/util'); var loadGeometry = require('./load_geometry'); var EXTENT = require('./buffer').EXTENT; var CollisionFeature = require('../symbol/collision_feature'); module.exports = SymbolBucket; function SymbolBucket(options) { Bucket.apply(this, arguments); this.collisionDebug = options.collisionDebug; this.overscaling = options.overscaling; // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. // This calculates text-size at a high zoom level so that all tiles can // use the same value when calculating anchor positions. var zoomHistory = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 }; this.adjustedTextMaxSize = this.layer.getLayoutValue('text-size', 18, zoomHistory); this.adjustedTextSize = this.layer.getLayoutValue('text-size', this.zoom + 1, zoomHistory); this.adjustedIconMaxSize = this.layer.getLayoutValue('icon-size', 18, zoomHistory); this.adjustedIconSize = this.layer.getLayoutValue('icon-size', this.zoom + 1, zoomHistory); } SymbolBucket.prototype = util.inherit(Bucket, {}); var shaderAttributeArgs = ['x', 'y', 'ox', 'oy', 'tx', 'ty', 'minzoom', 'maxzoom', 'labelminzoom']; var shaderAttributes = [{ name: 'pos', components: 2, type: Bucket.AttributeType.SHORT, value: ['x', 'y'] }, { name: 'offset', components: 2, type: Bucket.AttributeType.SHORT, value: [ 'Math.round(ox * 64)', // use 1/64 pixels for placement 'Math.round(oy * 64)' ] }, { name: 'data1', components: 4, type: Bucket.AttributeType.UNSIGNED_BYTE, value: [ 'tx / 4', // tex 'ty / 4', // tex '(labelminzoom || 0) * 10', // labelminzoom '0' ] }, { name: 'data2', components: 2, type: Bucket.AttributeType.UNSIGNED_BYTE, value: [