Skip to content

Commit

Permalink
reparse overscaled tiles
Browse files Browse the repository at this point in the history
When the current zoom is past the vectortile source's maxzoom, load
overscaled tiles and parse them specifically for the current integer
zoom level. This is necessary to:
- make layout property functions work
- let us place labels only 1 zoom level deep (for performance)

Overscaled tile id's have the same x/y/w as their corresponding tiles
from the source's maxzoom. For example:

currentzoom: 18
maxzoom: 16

The overscaled tile { z: 18, x: 123, y: 456 }
loads the real tile { x: 16, x: 123, y: 456 }
  • Loading branch information
ansis committed Feb 18, 2015
1 parent d420376 commit 47c1fcb
Show file tree
Hide file tree
Showing 10 changed files with 43 additions and 25 deletions.
4 changes: 2 additions & 2 deletions js/data/create_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var LayoutProperties = require('../style/layout_properties');
var featureFilter = require('feature-filter');
var StyleDeclarationSet = require('../style/style_declaration_set');

function createBucket(layer, buffers, collision, z) {
function createBucket(layer, buffers, collision, z, overscaling) {
var values = new StyleDeclarationSet('layout', layer.type, layer.layout, {}).values(),
fakeZoomHistory = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 },
layout = {};
Expand All @@ -23,7 +23,7 @@ function createBucket(layer, buffers, collision, z) {
layer.type === 'fill' ? FillBucket :
layer.type === 'symbol' ? SymbolBucket : null;

var bucket = new BucketClass(buffers, new LayoutProperties[layer.type](layout), collision);
var bucket = new BucketClass(buffers, new LayoutProperties[layer.type](layout), collision, overscaling);

bucket.id = layer.id;
bucket.type = layer.type;
Expand Down
5 changes: 3 additions & 2 deletions js/data/line_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ var ElementGroups = require('./element_groups');

module.exports = LineBucket;

function LineBucket(buffers, layoutProperties) {
function LineBucket(buffers, layoutProperties, _, overscaling) {
this.buffers = buffers;
this.elementGroups = new ElementGroups(buffers.lineVertex, buffers.lineElement);
this.layoutProperties = layoutProperties;
this.overscaling = overscaling;
}

LineBucket.prototype.addFeatures = function() {
Expand Down Expand Up @@ -90,7 +91,7 @@ LineBucket.prototype.addLine = function(vertices, join, cap, miterLimit, roundLi
currentVertex = vertices[i];

// Calculate how far along the line the currentVertex is
if (prevVertex) distance += currentVertex.dist(prevVertex);
if (prevVertex) distance += currentVertex.dist(prevVertex) * this.overscaling;

// Calculate the normal towards the next vertex in this line. In case
// there is no next vertex, pretend that the line is continuing straight,
Expand Down
4 changes: 4 additions & 0 deletions js/source/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ exports._renderTiles = function(layers, painter) {
y = pos.y,
w = pos.w;

// if z > maxzoom then the tile is actually a overscaled maxzoom tile,
// so calculate the matrix the maxzoom tile would use.
z = Math.min(z, this.maxzoom);

x += w * (1 << z);
tile.calculateMatrices(z, x, y, painter.transform, painter);

Expand Down
24 changes: 18 additions & 6 deletions js/source/tile_coord.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,28 @@ TileCoord.zoom = function(id) {
};

// Given an id and a list of urls, choose a url template and return a tile URL
TileCoord.url = function(id, urls) {
TileCoord.url = function(id, urls, sourceMaxZoom) {
var pos = TileCoord.fromID(id);

return urls[(pos.x + pos.y) % urls.length]
.replace('{prefix}', (pos.x % 16).toString(16) + (pos.y % 16).toString(16))
.replace('{z}', pos.z)
.replace('{z}', Math.min(pos.z, sourceMaxZoom || pos.z))
.replace('{x}', pos.x)
.replace('{y}', pos.y);
};

/*
* Given a packed integer id, return the id of its parent tile
*/
TileCoord.parent = function(id) {
TileCoord.parent = function(id, sourceMaxZoom) {
var pos = TileCoord.fromID(id);
if (pos.z === 0) return null;

// the id represents an overscaled tile, return the same coordinates with a lower z
if (pos.z > sourceMaxZoom) {
return TileCoord.toID(pos.z - 1, pos.x, pos.y, pos.w);
}

return TileCoord.toID(pos.z - 1, Math.floor(pos.x / 2), Math.floor(pos.y / 2), pos.w);
};

Expand All @@ -74,8 +80,14 @@ TileCoord.parentWithZoom = function(id, zoom) {
* Given a packed integer id, return an array of integer ids representing
* its four children.
*/
TileCoord.children = function(id) {
TileCoord.children = function(id, sourceMaxZoom) {
var pos = TileCoord.fromID(id);

if (pos.z >= sourceMaxZoom) {
// return a single tile id representing a an overscaled tile
return [TileCoord.toID(pos.z + 1, pos.x, pos.y, pos.w)];
}

pos.z += 1;
pos.x *= 2;
pos.y *= 2;
Expand Down Expand Up @@ -149,7 +161,7 @@ function scanTriangle(a, b, c, ymin, ymax, scanLine) {
if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
}

TileCoord.cover = function(z, bounds) {
TileCoord.cover = function(z, bounds, actualZ) {
var tiles = 1 << z;
var t = {};

Expand All @@ -158,7 +170,7 @@ TileCoord.cover = function(z, bounds) {
if (y >= 0 && y <= tiles) {
for (x = x0; x < x1; x++) {
wx = (x + tiles) % tiles;
t[TileCoord.toID(z, wx, y, Math.floor(x / tiles))] = {x: wx, y: y};
t[TileCoord.toID(actualZ, wx, y, Math.floor(x / tiles))] = {x: wx, y: y};
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions js/source/tile_pyramid.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ TilePyramid.prototype = {

coveringTiles: function(transform) {
var z = this.coveringZoomLevel(transform);
var actualZ = z;

if (z < this.minzoom) return [];
if (z > this.maxzoom) z = this.maxzoom;
Expand All @@ -79,7 +80,7 @@ TilePyramid.prototype = {
TileCoord.zoomTo(tr.pointCoordinate(tileCenter, {x: tr.width, y: 0}), z),
TileCoord.zoomTo(tr.pointCoordinate(tileCenter, {x: tr.width, y: tr.height}), z),
TileCoord.zoomTo(tr.pointCoordinate(tileCenter, {x: 0, y: tr.height}), z)
]).sort(function(a, b) {
], actualZ).sort(function(a, b) {
return centerPoint.dist(TileCoord.fromID(a)) -
centerPoint.dist(TileCoord.fromID(b));
});
Expand All @@ -90,7 +91,7 @@ TilePyramid.prototype = {
findLoadedChildren: function(id, maxCoveringZoom, retain) {
var complete = true;
var z = TileCoord.fromID(id).z;
var ids = TileCoord.children(id);
var ids = TileCoord.children(id, this.maxzoom);
for (var i = 0; i < ids.length; i++) {
if (this._tiles[ids[i]] && this._tiles[ids[i]].loaded) {
retain[ids[i]] = true;
Expand All @@ -109,7 +110,7 @@ TilePyramid.prototype = {
// adds the found tile to retain object and returns the tile if found
findLoadedParent: function(id, minCoveringZoom, retain) {
for (var z = TileCoord.fromID(id).z; z >= minCoveringZoom; z--) {
id = TileCoord.parent(id);
id = TileCoord.parent(id, this.maxzoom);
var tile = this._tiles[id];
if (tile && tile.loaded) {
retain[id] = true;
Expand Down
7 changes: 4 additions & 3 deletions js/source/vector_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ VectorTileSource.prototype = util.inherit(Evented, {
featuresAt: Source._vectorFeaturesAt,

_loadTile: function(tile) {
var overscaling = tile.zoom > this.maxzoom ? Math.pow(2, tile.zoom - this.maxzoom) : 1;
var params = {
url: TileCoord.url(tile.id, this.tiles),
url: TileCoord.url(tile.id, this.tiles, this.maxzoom),
id: tile.uid,
tileId: tile.id,
zoom: tile.zoom,
maxZoom: this.maxzoom,
tileSize: this.tileSize,
tileSize: this.tileSize * overscaling,
source: this.id,
depth: tile.zoom >= this.maxzoom ? this.map.options.maxZoom - tile.zoom : 1
overscaling: overscaling
};

if (tile.workerID) {
Expand Down
2 changes: 1 addition & 1 deletion js/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ util.extend(Worker.prototype, {

var tile = new WorkerTile(
params.id, params.zoom, params.maxZoom,
params.tileSize, params.source, params.depth);
params.tileSize, params.source, params.overscaling);

tile.data = new vt.VectorTile(new Protobuf(new Uint8Array(data)));
tile.parse(tile.data, this.layers, this.actor, callback);
Expand Down
8 changes: 4 additions & 4 deletions js/source/worker_tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ function getType(feature) {

module.exports = WorkerTile;

function WorkerTile(id, zoom, maxZoom, tileSize, source, depth) {
function WorkerTile(id, zoom, maxZoom, tileSize, source, overscaling) {
this.id = id;
this.zoom = zoom;
this.maxZoom = maxZoom;
this.tileSize = tileSize;
this.source = source;
this.depth = depth;
this.overscaling = overscaling;
}

WorkerTile.prototype.parse = function(data, layers, actor, callback) {
Expand All @@ -33,7 +33,7 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {
layer,
bucket,
buffers = new BufferSet(),
collision = new Collision(this.zoom, 4096, this.tileSize, this.depth),
collision = new Collision(this.zoom, 4096, this.tileSize),
buckets = {},
bucketsInOrder = [],
bucketsBySourceLayer = {};
Expand All @@ -60,7 +60,7 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {
if (visibility === 'none')
continue;

bucket = createBucket(layer, buffers, collision, this.zoom);
bucket = createBucket(layer, buffers, collision, this.zoom, this.overscaling);
bucket.layers = [layer.id];

buckets[bucket.id] = bucket;
Expand Down
5 changes: 2 additions & 3 deletions js/symbol/collision.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ var rbush = require('rbush'),

module.exports = Collision;

function Collision(zoom, tileExtent, tileSize, placementDepth) {
function Collision(zoom, tileExtent, tileSize) {
this.hTree = rbush(); // tree for horizontal labels
this.cTree = rbush(); // tree for glyphs from curved labels

Expand All @@ -23,8 +23,7 @@ function Collision(zoom, tileExtent, tileSize, placementDepth) {
// We don't want to place labels all the way to 25.5. This lets too many
// glyphs be placed, slowing down collision checking. Only place labels if
// they will show up within the intended zoom range of the tile.
placementDepth = Math.min(3, placementDepth || 1, 25.5 - this.zoom);
this.maxPlacementScale = Math.exp(Math.LN2 * placementDepth);
this.maxPlacementScale = 2;

var m = 4096;
var edge = m * this.tilePixelRatio * 2;
Expand Down
2 changes: 1 addition & 1 deletion test/js/source/tile_pyramid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test('TilePyramid#coveringTiles', function(t) {
t.deepEqual(pyramid.coveringTiles(transform), ['16760810', '16760842', '16793578', '16793610']);

transform.zoom = 11;
t.deepEqual(pyramid.coveringTiles(transform), ['16760810', '16760842', '16793578', '16793610']);
t.deepEqual(pyramid.coveringTiles(transform), ['33505259', '33505291', '33570795', '33570827']);

t.end();
});
Expand Down

0 comments on commit 47c1fcb

Please sign in to comment.