UNPKG

mapbox-gl

Version:
989 lines (847 loc) 37.1 kB
// @flow import {uniqueId, parseCacheControl} from '../util/util.js'; import {deserialize as deserializeBucket} from '../data/bucket.js'; import FeatureIndex from '../data/feature_index.js'; import GeoJSONFeature from '../util/vectortile_to_geojson.js'; import featureFilter from '../style-spec/feature_filter/index.js'; import SymbolBucket from '../data/bucket/symbol_bucket.js'; import FillBucket from '../data/bucket/fill_bucket.js'; import LineBucket from '../data/bucket/line_bucket.js'; import {CollisionBoxArray, TileBoundsArray, PosArray, TriangleIndexArray, LineStripIndexArray, PosGlobeExtArray} from '../data/array_types.js'; import Texture from '../render/texture.js'; import browser from '../util/browser.js'; import {Debug} from '../util/debug.js'; import toEvaluationFeature from '../data/evaluation_feature.js'; import EvaluationParameters from '../style/evaluation_parameters.js'; import SourceFeatureState from '../source/source_state.js'; import {lazyLoadRTLTextPlugin} from './rtl_text_plugin.js'; import {TileSpaceDebugBuffer} from '../data/debug_viz.js'; import Color from '../style-spec/util/color.js'; import loadGeometry from '../data/load_geometry.js'; import earcut from 'earcut'; import getTileMesh from './tile_mesh.js'; import tileTransform from '../geo/projection/tile_transform.js'; import {mercatorXfromLng, mercatorYfromLat} from '../geo/mercator_coordinate.js'; import boundsAttributes from '../data/bounds_attributes.js'; import posAttributes, {posAttributesGlobeExt} from '../data/pos_attributes.js'; import EXTENT from '../style-spec/data/extent.js'; import Point from '@mapbox/point-geometry'; import SegmentVector from '../data/segment.js'; import {transitionTileAABBinECEF, globeNormalizeECEF, tileCoordToECEF, globeToMercatorTransition, interpolateVec3} from '../geo/projection/globe_util.js'; import {vec3, mat4} from 'gl-matrix'; import type {Bucket} from '../data/bucket.js'; import type StyleLayer from '../style/style_layer.js'; import type {WorkerTileResult} from './worker_source.js'; import type Actor from '../util/actor.js'; import type DEMData from '../data/dem_data.js'; import type {AlphaImage, SpritePositions} from '../util/image.js'; import type ImageAtlas from '../render/image_atlas.js'; import type LineAtlas from '../render/line_atlas.js'; import type ImageManager from '../render/image_manager.js'; import type Context from '../gl/context.js'; import type {CanonicalTileID, OverscaledTileID} from './tile_id.js'; import type Framebuffer from '../gl/framebuffer.js'; import type Transform from '../geo/transform.js'; import type {LayerFeatureStates} from './source_state.js'; import type {Cancelable} from '../types/cancelable.js'; import type {FilterSpecification} from '../style-spec/types.js'; import type {TilespaceQueryGeometry} from '../style/query_geometry.js'; import type VertexBuffer from '../gl/vertex_buffer.js'; import type IndexBuffer from '../gl/index_buffer.js'; import type Projection from '../geo/projection/projection.js'; import type {TileTransform} from '../geo/projection/tile_transform.js'; import type {QueryResult} from '../data/feature_index.js'; import type Painter from '../render/painter.js'; import type {QueryFeature} from '../util/vectortile_to_geojson.js'; import type {Vec3} from 'gl-matrix'; import type {UserManagedTexture, TextureImage} from '../render/texture.js'; import type {VectorTileLayer} from '@mapbox/vector-tile'; const CLOCK_SKEW_RETRY_TIMEOUT = 30000; export type TileState = | 'loading' // Tile data is in the process of loading. | 'loaded' // Tile data has been loaded. Tile can be rendered. | 'reloading' // Tile data has been loaded and is being updated. Tile can be rendered. | 'unloaded' // Tile data has been deleted. | 'errored' // Tile data was not loaded because of an error. | 'expired'; /* Tile data was previously loaded, but has expired per its * HTTP headers and is in the process of refreshing. */ // a tile bounds outline used for getting reprojected tile geometry in non-mercator projections const BOUNDS_FEATURE = (() => { return { type: 2, extent: EXTENT, loadGeometry() { return [[ new Point(0, 0), new Point(EXTENT + 1, 0), new Point(EXTENT + 1, EXTENT + 1), new Point(0, EXTENT + 1), new Point(0, 0) ]]; } }; })(); /** * A tile object is the combination of a Coordinate, which defines * its place, as well as a unique ID and data tracking for its content * * @private */ class Tile { tileID: OverscaledTileID; uid: number; uses: number; tileSize: number; tileZoom: number; buckets: {[_: string]: Bucket}; latestFeatureIndex: ?FeatureIndex; latestRawTileData: ?ArrayBuffer; imageAtlas: ?ImageAtlas; imageAtlasTexture: ?Texture; lineAtlas: ?LineAtlas; lineAtlasTexture: ?Texture; glyphAtlasImage: ?AlphaImage; glyphAtlasTexture: ?Texture; expirationTime: any; expiredRequestCount: number; state: TileState; timeAdded: any; fadeEndTime: any; collisionBoxArray: ?CollisionBoxArray; redoWhenDone: boolean; showCollisionBoxes: boolean; placementSource: any; actor: ?Actor; vtLayers: {[_: string]: VectorTileLayer}; isSymbolTile: ?boolean; isExtraShadowCaster: ?boolean; isRaster: ?boolean; _tileTransform: TileTransform; neighboringTiles: ?Object; dem: ?DEMData; aborted: ?boolean; needsHillshadePrepare: ?boolean; needsDEMTextureUpload: ?boolean; request: ?Cancelable; texture: ?Texture | ?UserManagedTexture; hillshadeFBO: ?Framebuffer; demTexture: ?Texture; refreshedUponExpiration: boolean; reloadCallback: any; resourceTiming: ?Array<PerformanceResourceTiming>; queryPadding: number; symbolFadeHoldUntil: ?number; hasSymbolBuckets: boolean; hasRTLText: boolean; dependencies: Object; projection: Projection; queryGeometryDebugViz: ?TileSpaceDebugBuffer; queryBoundsDebugViz: ?TileSpaceDebugBuffer; _tileDebugBuffer: ?VertexBuffer; _tileBoundsBuffer: ?VertexBuffer; _tileDebugIndexBuffer: ?IndexBuffer; _tileBoundsIndexBuffer: IndexBuffer; _tileDebugSegments: SegmentVector; _tileBoundsSegments: SegmentVector; _globeTileDebugBorderBuffer: ?VertexBuffer; _tileDebugTextBuffer: ?VertexBuffer; _tileDebugTextSegments: SegmentVector; _tileDebugTextIndexBuffer: IndexBuffer; _globeTileDebugTextBuffer: ?VertexBuffer; _lastUpdatedBrightness: ?number; /** * @param {OverscaledTileID} tileID * @param size * @private */ constructor(tileID: OverscaledTileID, size: number, tileZoom: number, painter: any, isRaster?: boolean) { this.tileID = tileID; this.uid = uniqueId(); this.uses = 0; this.tileSize = size; this.tileZoom = tileZoom; this.buckets = {}; this.expirationTime = null; this.queryPadding = 0; this.hasSymbolBuckets = false; this.hasRTLText = false; this.dependencies = {}; this.isRaster = isRaster; if (painter && painter.style) { this._lastUpdatedBrightness = painter.style.getBrightness(); } // Counts the number of times a response was already expired when // received. We're using this to add a delay when making a new request // so we don't have to keep retrying immediately in case of a server // serving expired tiles. this.expiredRequestCount = 0; this.state = 'loading'; if (painter && painter.transform) { this.projection = painter.transform.projection; } } registerFadeDuration(duration: number) { const fadeEndTime = duration + this.timeAdded; if (fadeEndTime < browser.now()) return; if (this.fadeEndTime && fadeEndTime < this.fadeEndTime) return; this.fadeEndTime = fadeEndTime; } wasRequested(): boolean { return this.state === 'errored' || this.state === 'loaded' || this.state === 'reloading'; } get tileTransform(): TileTransform { if (!this._tileTransform) { this._tileTransform = tileTransform(this.tileID.canonical, this.projection); } return this._tileTransform; } /** * Given a data object with a 'buffers' property, load it into * this tile's elementGroups and buffers properties and set loaded * to true. If the data is null, like in the case of an empty * GeoJSON tile, no-op but still set loaded to true. * @param {Object} data * @param painter * @returns {undefined} * @private */ loadVectorData(data: ?WorkerTileResult, painter: Painter, justReloaded: ?boolean) { this.unloadVectorData(); this.state = 'loaded'; // empty GeoJSON tile if (!data) { this.collisionBoxArray = new CollisionBoxArray(); return; } if (data.featureIndex) { this.latestFeatureIndex = data.featureIndex; if (data.rawTileData) { // Only vector tiles have rawTileData, and they won't update it for // 'reloadTile' this.latestRawTileData = data.rawTileData; this.latestFeatureIndex.rawTileData = data.rawTileData; } else if (this.latestRawTileData) { // If rawTileData hasn't updated, hold onto a pointer to the last // one we received this.latestFeatureIndex.rawTileData = this.latestRawTileData; } } this.collisionBoxArray = data.collisionBoxArray; this.buckets = deserializeBucket(data.buckets, painter.style); this.hasSymbolBuckets = false; for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket instanceof SymbolBucket) { this.hasSymbolBuckets = true; if (justReloaded) { bucket.justReloaded = true; } else { break; } } } this.hasRTLText = false; if (this.hasSymbolBuckets) { for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket instanceof SymbolBucket) { if (bucket.hasRTLText) { this.hasRTLText = true; lazyLoadRTLTextPlugin(); break; } } } } this.queryPadding = 0; for (const id in this.buckets) { const bucket = this.buckets[id]; const layer = painter.style.getOwnLayer(id); if (!layer) continue; const queryRadius = layer.queryRadius(bucket); this.queryPadding = Math.max(this.queryPadding, queryRadius); } if (data.imageAtlas) { this.imageAtlas = data.imageAtlas; } if (data.glyphAtlasImage) { this.glyphAtlasImage = data.glyphAtlasImage; } if (data.lineAtlas) { this.lineAtlas = data.lineAtlas; } this._lastUpdatedBrightness = data.brightness; } /** * Release any data or WebGL resources referenced by this tile. * @returns {undefined} * @private */ unloadVectorData() { if (!this.hasData()) return; for (const id in this.buckets) { this.buckets[id].destroy(); } this.buckets = {}; if (this.imageAtlas) { this.imageAtlas = null; } if (this.lineAtlas) { this.lineAtlas = null; } if (this.imageAtlasTexture) { this.imageAtlasTexture.destroy(); } if (this.glyphAtlasTexture) { this.glyphAtlasTexture.destroy(); } if (this.lineAtlasTexture) { this.lineAtlasTexture.destroy(); } if (this._tileBoundsBuffer) { this._tileBoundsBuffer.destroy(); this._tileBoundsIndexBuffer.destroy(); this._tileBoundsSegments.destroy(); this._tileBoundsBuffer = null; } if (this._tileDebugBuffer) { this._tileDebugBuffer.destroy(); this._tileDebugSegments.destroy(); this._tileDebugBuffer = null; } if (this._tileDebugIndexBuffer) { this._tileDebugIndexBuffer.destroy(); this._tileDebugIndexBuffer = null; } if (this._globeTileDebugBorderBuffer) { this._globeTileDebugBorderBuffer.destroy(); this._globeTileDebugBorderBuffer = null; } if (this._tileDebugTextBuffer) { this._tileDebugTextBuffer.destroy(); this._tileDebugTextSegments.destroy(); this._tileDebugTextIndexBuffer.destroy(); this._tileDebugTextBuffer = null; } if (this._globeTileDebugTextBuffer) { this._globeTileDebugTextBuffer.destroy(); this._globeTileDebugTextBuffer = null; } Debug.run(() => { if (this.queryGeometryDebugViz) { this.queryGeometryDebugViz.unload(); delete this.queryGeometryDebugViz; } if (this.queryBoundsDebugViz) { this.queryBoundsDebugViz.unload(); delete this.queryBoundsDebugViz; } }); this.latestFeatureIndex = null; this.state = 'unloaded'; } getBucket(layer: StyleLayer): Bucket { return this.buckets[layer.fqid]; } upload(context: Context) { for (const id in this.buckets) { const bucket = this.buckets[id]; if (bucket.uploadPending()) { bucket.upload(context); } } const gl = context.gl; const atlas = this.imageAtlas; if (atlas && !atlas.uploaded) { const hasPattern = !!Object.keys(atlas.patternPositions).length; this.imageAtlasTexture = new Texture(context, atlas.image, gl.RGBA, {useMipmap: hasPattern}); ((this.imageAtlas: any): ImageAtlas).uploaded = true; } if (this.glyphAtlasImage) { this.glyphAtlasTexture = new Texture(context, this.glyphAtlasImage, gl.R8); this.glyphAtlasImage = null; } if (this.lineAtlas && !this.lineAtlas.uploaded) { this.lineAtlasTexture = new Texture(context, this.lineAtlas.image, gl.R8); ((this.lineAtlas: any): LineAtlas).uploaded = true; } } prepare(imageManager: ImageManager, painter: ?Painter, scope: string) { if (this.imageAtlas && this.imageAtlasTexture) { this.imageAtlas.patchUpdatedImages(imageManager, this.imageAtlasTexture, scope); } if (!painter || !this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) { return; } const brightness = painter.style.getBrightness(); if (!this._lastUpdatedBrightness && !brightness) { return; } if (this._lastUpdatedBrightness && brightness && Math.abs(this._lastUpdatedBrightness - brightness) < 0.001) { return; } this._lastUpdatedBrightness = brightness; this.updateBuckets(undefined, painter); } // Queries non-symbol features rendered for this tile. // Symbol features are queried globally queryRenderedFeatures(layers: {[_: string]: StyleLayer}, serializedLayers: {[string]: Object}, sourceFeatureState: SourceFeatureState, tileResult: TilespaceQueryGeometry, params: { filter: FilterSpecification, layers: Array<string>, availableImages: Array<string> }, transform: Transform, pixelPosMatrix: Float32Array, visualizeQueryGeometry: boolean): QueryResult { Debug.run(() => { if (visualizeQueryGeometry) { let geometryViz = this.queryGeometryDebugViz; let boundsViz = this.queryBoundsDebugViz; if (!geometryViz) { geometryViz = this.queryGeometryDebugViz = new TileSpaceDebugBuffer(this.tileSize); } if (!boundsViz) { boundsViz = this.queryBoundsDebugViz = new TileSpaceDebugBuffer(this.tileSize, Color.blue); } geometryViz.addPoints(tileResult.tilespaceGeometry); boundsViz.addPoints(tileResult.bufferedTilespaceGeometry); } }); if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData) return {}; return this.latestFeatureIndex.query({ tileResult, pixelPosMatrix, transform, params, tileTransform: this.tileTransform }, layers, serializedLayers, sourceFeatureState); } querySourceFeatures(result: Array<QueryFeature>, params: any) { const featureIndex = this.latestFeatureIndex; if (!featureIndex || !featureIndex.rawTileData) return; const vtLayers = featureIndex.loadVTLayers(); const sourceLayer = params ? params.sourceLayer : ''; const layer = vtLayers._geojsonTileLayer || vtLayers[sourceLayer]; if (!layer) return; const filter = featureFilter(params && params.filter); const {z, x, y} = this.tileID.canonical; const coord = {z, x, y}; for (let i = 0; i < layer.length; i++) { const feature = layer.feature(i); if (filter.needGeometry) { const evaluationFeature = toEvaluationFeature(feature, true); // $FlowFixMe[method-unbinding] if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), evaluationFeature, this.tileID.canonical)) continue; // $FlowFixMe[method-unbinding] } else if (!filter.filter(new EvaluationParameters(this.tileID.overscaledZ), feature)) { continue; } const id = featureIndex.getId(feature, sourceLayer); const geojsonFeature = new GeoJSONFeature(feature, z, x, y, id); geojsonFeature.tile = coord; result.push(geojsonFeature); } } hasData(): boolean { return this.state === 'loaded' || this.state === 'reloading' || this.state === 'expired'; } bucketsLoaded(): boolean { for (const id in this.buckets) { if (this.buckets[id].uploadPending()) return false; } return true; } patternsLoaded(): boolean { return !!this.imageAtlas && !!Object.keys(this.imageAtlas.patternPositions).length; } setExpiryData(data: any) { const prior = this.expirationTime; if (data.cacheControl) { const parsedCC = parseCacheControl(data.cacheControl); if (parsedCC['max-age']) this.expirationTime = Date.now() + parsedCC['max-age'] * 1000; } else if (data.expires) { this.expirationTime = new Date(data.expires).getTime(); } if (this.expirationTime) { const now = Date.now(); let isExpired = false; if (this.expirationTime > now) { isExpired = false; } else if (!prior) { isExpired = true; } else if (this.expirationTime < prior) { // Expiring date is going backwards: // fall back to exponential backoff isExpired = true; } else { const delta = this.expirationTime - prior; if (!delta) { // Server is serving the same expired resource over and over: fall // back to exponential backoff. isExpired = true; } else { // Assume that either the client or the server clock is wrong and // try to interpolate a valid expiration date (from the client POV) // observing a minimum timeout. this.expirationTime = now + Math.max(delta, CLOCK_SKEW_RETRY_TIMEOUT); } } if (isExpired) { this.expiredRequestCount++; this.state = 'expired'; } else { this.expiredRequestCount = 0; } } } getExpiryTimeout(): void | number { if (this.expirationTime) { if (this.expiredRequestCount) { return 1000 * (1 << Math.min(this.expiredRequestCount - 1, 31)); } else { // Max value for `setTimeout` implementations is a 32 bit integer; cap this accordingly return Math.min(this.expirationTime - new Date().getTime(), Math.pow(2, 31) - 1); } } } setFeatureState(states: LayerFeatureStates, painter: ?Painter) { if (!this.latestFeatureIndex || !this.latestFeatureIndex.rawTileData || Object.keys(states).length === 0 || !painter) { return; } this.updateBuckets(states, painter); } updateBuckets(states: ?LayerFeatureStates, painter: Painter) { if (!this.latestFeatureIndex) return; const vtLayers = this.latestFeatureIndex.loadVTLayers(); const availableImages = painter.style.listImages(); const brightness = painter.style.getBrightness(); for (const id in this.buckets) { if (!painter.style.hasLayer(id)) continue; const bucket = this.buckets[id]; // Buckets are grouped by common source-layer const sourceLayerId = bucket.layers[0]['sourceLayer'] || '_geojsonTileLayer'; const sourceLayer = vtLayers[sourceLayerId]; let sourceLayerStates = {}; if (states) { sourceLayerStates = states[sourceLayerId]; if (!sourceLayer || !sourceLayerStates || Object.keys(sourceLayerStates).length === 0) continue; } // $FlowFixMe[incompatible-type] Flow can't interpret ImagePosition as SpritePosition for some reason here const imagePositions: SpritePositions = (this.imageAtlas && this.imageAtlas.patternPositions) || {}; bucket.update(sourceLayerStates, sourceLayer, availableImages, imagePositions, brightness); if (bucket instanceof LineBucket || bucket instanceof FillBucket) { const sourceCache = painter.style.getOwnSourceCache(bucket.layers[0].source); if (painter._terrain && painter._terrain.enabled && sourceCache && bucket.programConfigurations.needsUpload) { painter._terrain._clearRenderCacheForTile(sourceCache.id, this.tileID); } } const layer = painter && painter.style && painter.style.getOwnLayer(id); if (layer) { this.queryPadding = Math.max(this.queryPadding, layer.queryRadius(bucket)); } } } holdingForFade(): boolean { return this.symbolFadeHoldUntil !== undefined; } symbolFadeFinished(): boolean { return !this.symbolFadeHoldUntil || this.symbolFadeHoldUntil < browser.now(); } clearFadeHold() { this.symbolFadeHoldUntil = undefined; } setHoldDuration(duration: number) { this.symbolFadeHoldUntil = browser.now() + duration; } setTexture(img: TextureImage, painter: Painter) { const context = painter.context; const gl = context.gl; this.texture = this.texture || painter.getTileTexture(img.width); if (this.texture && this.texture instanceof Texture) { this.texture.update(img, {useMipmap: true}); } else { this.texture = new Texture(context, img, gl.RGBA, {useMipmap: true}); this.texture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE); } } setDependencies(namespace: string, dependencies: Array<string>) { const index = {}; for (const dep of dependencies) { index[dep] = true; } this.dependencies[namespace] = index; } hasDependency(namespaces: Array<string>, keys: Array<string>): boolean { for (const namespace of namespaces) { const dependencies = this.dependencies[namespace]; if (dependencies) { for (const key of keys) { if (dependencies[key]) { return true; } } } } return false; } clearQueryDebugViz() { Debug.run(() => { if (this.queryGeometryDebugViz) { this.queryGeometryDebugViz.clearPoints(); } if (this.queryBoundsDebugViz) { this.queryBoundsDebugViz.clearPoints(); } }); } _makeDebugTileBoundsBuffers(context: Context, projection: Projection) { if (!projection || projection.name === 'mercator' || this._tileDebugBuffer) return; // reproject tile outline with adaptive resampling const boundsLine = loadGeometry(BOUNDS_FEATURE, this.tileID.canonical, this.tileTransform)[0]; // generate vertices for debugging tile boundaries const debugVertices = new PosArray(); const debugIndices = new LineStripIndexArray(); for (let i = 0; i < boundsLine.length; i++) { const {x, y} = boundsLine[i]; debugVertices.emplaceBack(x, y); debugIndices.emplaceBack(i); } debugIndices.emplaceBack(0); this._tileDebugIndexBuffer = context.createIndexBuffer(debugIndices); this._tileDebugBuffer = context.createVertexBuffer(debugVertices, posAttributes.members); this._tileDebugSegments = SegmentVector.simpleSegment(0, 0, debugVertices.length, debugIndices.length); } _makeTileBoundsBuffers(context: Context, projection: Projection) { if (this._tileBoundsBuffer || !projection || projection.name === 'mercator') return; // reproject tile outline with adaptive resampling const boundsLine = loadGeometry(BOUNDS_FEATURE, this.tileID.canonical, this.tileTransform)[0]; let boundsVertices, boundsIndices; if (this.isRaster) { // for raster tiles, generate an adaptive MARTINI mesh const mesh = getTileMesh(this.tileID.canonical, projection); boundsVertices = mesh.vertices; boundsIndices = mesh.indices; } else { // for vector tiles, generate an Earcut triangulation of the outline boundsVertices = new TileBoundsArray(); boundsIndices = new TriangleIndexArray(); for (const {x, y} of boundsLine) { boundsVertices.emplaceBack(x, y, 0, 0); } const indices = earcut(boundsVertices.int16, undefined, 4); for (let i = 0; i < indices.length; i += 3) { boundsIndices.emplaceBack(indices[i], indices[i + 1], indices[i + 2]); } } this._tileBoundsBuffer = context.createVertexBuffer(boundsVertices, boundsAttributes.members); this._tileBoundsIndexBuffer = context.createIndexBuffer(boundsIndices); this._tileBoundsSegments = SegmentVector.simpleSegment(0, 0, boundsVertices.length, boundsIndices.length); } _makeGlobeTileDebugBuffers(context: Context, transform: Transform) { const projection = transform.projection; if (!projection || projection.name !== 'globe' || transform.freezeTileCoverage) return; const id = this.tileID.canonical; const bounds = transitionTileAABBinECEF(id, transform); const normalizationMatrix = globeNormalizeECEF(bounds); const phase = globeToMercatorTransition(transform.zoom); let worldToECEFMatrix; if (phase > 0.0) { worldToECEFMatrix = mat4.invert(new Float64Array(16), transform.globeMatrix); } this._makeGlobeTileDebugBorderBuffer(context, id, transform, normalizationMatrix, worldToECEFMatrix, phase); this._makeGlobeTileDebugTextBuffer(context, id, transform, normalizationMatrix, worldToECEFMatrix, phase); } _globePoint(x: number, y: number, id: CanonicalTileID, tr: Transform, normalizationMatrix: Float64Array, worldToECEFMatrix?: Float64Array, phase: number): Vec3 { // The following is equivalent to doing globe.projectTilePoint. // This way we don't recompute the normalization matrix everytime since it remains the same for all points. let ecef = tileCoordToECEF(x, y, id); if (worldToECEFMatrix) { // When in globe-to-Mercator transition, interpolate between globe and Mercator positions in ECEF const tileCount = 1 << id.z; // Wrap tiles to ensure that that Mercator interpolation is in the right direction const camX = mercatorXfromLng(tr.center.lng); const camY = mercatorYfromLat(tr.center.lat); const tileCenterX = (id.x + .5) / tileCount; const dx = tileCenterX - camX; let wrap = 0; if (dx > .5) { wrap = -1; } else if (dx < -.5) { wrap = 1; } let mercatorX = (x / EXTENT + id.x) / tileCount + wrap; let mercatorY = (y / EXTENT + id.y) / tileCount; mercatorX = (mercatorX - camX) * tr._pixelsPerMercatorPixel + camX; mercatorY = (mercatorY - camY) * tr._pixelsPerMercatorPixel + camY; const mercatorPos = [mercatorX * tr.worldSize, mercatorY * tr.worldSize, 0]; vec3.transformMat4(mercatorPos, mercatorPos, worldToECEFMatrix); ecef = interpolateVec3(ecef, mercatorPos, phase); } const gp = vec3.transformMat4(ecef, ecef, normalizationMatrix); return gp; } _makeGlobeTileDebugBorderBuffer(context: Context, id: CanonicalTileID, tr: Transform, normalizationMatrix: Float64Array, worldToECEFMatrix?: Float64Array, phase: number) { const vertices = new PosArray(); const indices = new LineStripIndexArray(); const extraGlobe = new PosGlobeExtArray(); const addLine = (sx: number, sy: number, ex: number, ey: number, pointCount: number) => { const stepX = (ex - sx) / (pointCount - 1); const stepY = (ey - sy) / (pointCount - 1); const vOffset = vertices.length; for (let i = 0; i < pointCount; i++) { const x = sx + i * stepX; const y = sy + i * stepY; vertices.emplaceBack(x, y); const gp = this._globePoint(x, y, id, tr, normalizationMatrix, worldToECEFMatrix, phase); extraGlobe.emplaceBack(gp[0], gp[1], gp[2]); indices.emplaceBack(vOffset + i); } }; const e = EXTENT; addLine(0, 0, e, 0, 16); addLine(e, 0, e, e, 16); addLine(e, e, 0, e, 16); addLine(0, e, 0, 0, 16); this._tileDebugIndexBuffer = context.createIndexBuffer(indices); this._tileDebugBuffer = context.createVertexBuffer(vertices, posAttributes.members); this._globeTileDebugBorderBuffer = context.createVertexBuffer(extraGlobe, posAttributesGlobeExt.members); this._tileDebugSegments = SegmentVector.simpleSegment(0, 0, vertices.length, indices.length); } _makeGlobeTileDebugTextBuffer(context: Context, id: CanonicalTileID, tr: Transform, normalizationMatrix: Float64Array, worldToECEFMatrix?: Float64Array, phase: number) { const SEGMENTS = 4; const numVertices = SEGMENTS + 1; const step = EXTENT / SEGMENTS; const vertices = new PosArray(); const indices = new TriangleIndexArray(); const extraGlobe = new PosGlobeExtArray(); const totalVertices = numVertices * numVertices; const totalTriangles = SEGMENTS * SEGMENTS * 2; indices.reserve(totalTriangles); vertices.reserve(totalVertices); extraGlobe.reserve(totalVertices); const toIndex = (j: number, i: number): number => { return totalVertices * j + i; }; // add vertices. for (let j = 0; j < totalVertices; j++) { const y = j * step; for (let i = 0; i < totalVertices; i++) { const x = i * step; vertices.emplaceBack(x, y); const gp = this._globePoint(x, y, id, tr, normalizationMatrix, worldToECEFMatrix, phase); extraGlobe.emplaceBack(gp[0], gp[1], gp[2]); } } // add indices. for (let j = 0; j < SEGMENTS; j++) { for (let i = 0; i < SEGMENTS; i++) { const tl = toIndex(j, i); const tr = toIndex(j, i + 1); const bl = toIndex(j + 1, i); const br = toIndex(j + 1, i + 1); // first triangle of the sub-patch. indices.emplaceBack(tl, tr, bl); // second triangle of the sub-patch. indices.emplaceBack(bl, tr, br); } } this._tileDebugTextIndexBuffer = context.createIndexBuffer(indices); this._tileDebugTextBuffer = context.createVertexBuffer(vertices, posAttributes.members); this._globeTileDebugTextBuffer = context.createVertexBuffer(extraGlobe, posAttributesGlobeExt.members); this._tileDebugTextSegments = SegmentVector.simpleSegment(0, 0, totalVertices, totalTriangles); } /** * Release data and WebGL resources referenced by this tile. * @returns {undefined} * @private */ destroy(preserveTexture: boolean = false) { for (const id in this.buckets) { this.buckets[id].destroy(); } this.buckets = {}; if (this.imageAtlas) { this.imageAtlas = null; } if (this.lineAtlas) { this.lineAtlas = null; } if (this.imageAtlasTexture) { this.imageAtlasTexture.destroy(); delete this.imageAtlasTexture; } if (this.glyphAtlasTexture) { this.glyphAtlasTexture.destroy(); delete this.glyphAtlasTexture; } if (this.lineAtlasTexture) { this.lineAtlasTexture.destroy(); delete this.lineAtlasTexture; } if (this._tileBoundsBuffer) { this._tileBoundsBuffer.destroy(); this._tileBoundsIndexBuffer.destroy(); this._tileBoundsSegments.destroy(); this._tileBoundsBuffer = null; } if (this._tileDebugBuffer) { this._tileDebugBuffer.destroy(); this._tileDebugSegments.destroy(); this._tileDebugBuffer = null; } if (this._tileDebugIndexBuffer) { this._tileDebugIndexBuffer.destroy(); this._tileDebugIndexBuffer = null; } if (this._globeTileDebugBorderBuffer) { this._globeTileDebugBorderBuffer.destroy(); this._globeTileDebugBorderBuffer = null; } if (this._tileDebugTextBuffer) { this._tileDebugTextBuffer.destroy(); this._tileDebugTextSegments.destroy(); this._tileDebugTextIndexBuffer.destroy(); this._tileDebugTextBuffer = null; } if (this._globeTileDebugTextBuffer) { this._globeTileDebugTextBuffer.destroy(); this._globeTileDebugTextBuffer = null; } if (!preserveTexture && this.texture && this.texture instanceof Texture) { this.texture.destroy(); delete this.texture; } if (this.hillshadeFBO) { this.hillshadeFBO.destroy(); delete this.hillshadeFBO; } if (this.dem) { delete this.dem; } if (this.neighboringTiles) { delete this.neighboringTiles; } if (this.demTexture) { this.demTexture.destroy(); delete this.demTexture; } Debug.run(() => { if (this.queryGeometryDebugViz) { this.queryGeometryDebugViz.unload(); delete this.queryGeometryDebugViz; } if (this.queryBoundsDebugViz) { this.queryBoundsDebugViz.unload(); delete this.queryBoundsDebugViz; } }); this.latestFeatureIndex = null; this.state = 'unloaded'; } } export default Tile;