Skip to content

Commit

Permalink
return text and icons in featuresAt queries
Browse files Browse the repository at this point in the history
fix #303

This increases worker memory usage by around 10% (a couple of MB).
1/3 of the increase comes from retaining the CollisionTile.
2/3 of the increase comes from retaining VectorTileFeatures.
  • Loading branch information
ansis committed Feb 9, 2016
1 parent 576d0d5 commit 3a0b874
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 67 deletions.
2 changes: 1 addition & 1 deletion js/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function Bucket(options) {
this.layer = StyleLayer.create(options.layer);
this.layer.recalculate(this.zoom, { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 });

this.layers = [this.layer.id];
this.layerIDs = [this.layer.id];
this.type = this.layer.type;
this.features = [];
this.id = this.layer.id;
Expand Down
62 changes: 41 additions & 21 deletions js/data/feature_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@ var Point = require('point-geometry');
var vt = require('vector-tile');
var util = require('../util/util');
var loadGeometry = require('./load_geometry');
var CollisionBox = require('../symbol/collision_box');
var EXTENT = require('./buffer').EXTENT;

module.exports = FeatureTree;

function FeatureTree(coord, overscaling) {
function FeatureTree(coord, overscaling, collisionTile) {
this.x = coord.x;
this.y = coord.y;
this.z = coord.z - Math.log(overscaling) / Math.LN2;
this.rtree = rbush(9);
this.toBeInserted = [];
this.setCollisionTile(collisionTile);
}

FeatureTree.prototype.insert = function(bbox, layers, feature) {
FeatureTree.prototype.insert = function(bbox, layerIDs, feature) {
var scale = EXTENT / feature.extent;
bbox[0] *= scale;
bbox[1] *= scale;
bbox[2] *= scale;
bbox[3] *= scale;
bbox.layers = layers;
bbox.layerIDs = layerIDs;
bbox.feature = feature;
this.toBeInserted.push(bbox);
};
Expand All @@ -34,6 +36,10 @@ FeatureTree.prototype._load = function() {
this.toBeInserted = [];
};

FeatureTree.prototype.setCollisionTile = function(collisionTile) {
this.collisionTile = collisionTile;
};

// Finds features in this tile at a particular position.
FeatureTree.prototype.query = function(args, callback) {
if (this.toBeInserted.length) this._load();
Expand All @@ -43,45 +49,59 @@ FeatureTree.prototype.query = function(args, callback) {
y = args.y,
result = [];

var radius, bounds;
var radius, bounds, symbolQueryBox;
if (typeof x !== 'undefined' && typeof y !== 'undefined') {
// a point (or point+radius) query
radius = (params.radius || 0) * EXTENT / args.scale;
radius = (params.radius || 0) * EXTENT / args.tileSize / args.scale;
bounds = [x - radius, y - radius, x + radius, y + radius];
symbolQueryBox = new CollisionBox(new Point(x, y), -radius, -radius, radius, radius, args.scale, null);
} else {
// a rectangle query
bounds = [ args.minX, args.minY, args.maxX, args.maxY ];
symbolQueryBox = new CollisionBox(new Point(args.minX, args.minY), 0, 0, args.maxX - args.minX, args.maxY - args.minY, args.scale, null);
}

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];

function checkIntersection(feature) {
var 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;
return false;

return radius ?
geometryContainsPoint(loadGeometry(feature), type, new Point(x, y), radius) :
geometryIntersectsBox(loadGeometry(feature), type, bounds);
}

function checkSymbolIntersection() {
return true;
}

this.addFeatures(this.rtree.search(bounds), params, checkIntersection, result);
this.addFeatures(this.collisionTile.getFeaturesAt(symbolQueryBox, args.scale), params, checkSymbolIntersection, result);

callback(null, result);
};

FeatureTree.prototype.addFeatures = function(matching, params, checkIntersection, result) {
for (var i = 0; i < matching.length; i++) {
var feature = matching[i].feature,
layerIDs = matching[i].layerIDs;
var geoJSON = feature.toGeoJSON(this.x, this.y, this.z);

if (!checkIntersection(feature)) continue;

if (!params.includeGeometry) {
geoJSON.geometry = null;
}

for (var l = 0; l < layers.length; l++) {
var layer = layers[l];
for (var l = 0; l < layerIDs.length; l++) {
var layerID = layerIDs[l];

if (params.layerIds && params.layerIds.indexOf(layer) < 0)
if (params.layerIds && params.layerIds.indexOf(layerID) < 0)
continue;

result.push(util.extend({layer: layer}, geoJSON));
result.push(util.extend({layer: layerID}, geoJSON));
}
}
callback(null, result);
};

function geometryIntersectsBox(rings, type, bounds) {
Expand Down
21 changes: 9 additions & 12 deletions js/data/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,14 @@ SymbolBucket.prototype.addFeatures = function(collisionTile, stacks, icons) {
}

if (shapedText || shapedIcon) {
this.addFeature(geometries[k], shapedText, shapedIcon);
this.addFeature(geometries[k], shapedText, shapedIcon, features[k]);
}
}

this.placeFeatures(collisionTile, this.buffers, this.collisionDebug);
};

SymbolBucket.prototype.addFeature = function(lines, shapedText, shapedIcon) {
SymbolBucket.prototype.addFeature = function(lines, shapedText, shapedIcon, feature) {
var layout = this.layer.layout;

var glyphSize = 24;
Expand Down Expand Up @@ -302,7 +302,8 @@ SymbolBucket.prototype.addFeature = function(lines, shapedText, shapedIcon) {
// the buffers for both tiles and clipped to tile boundaries at draw time.
var addToBuffers = inside || mayOverlap;

this.symbolInstances.push(new SymbolInstance(anchor, line, shapedText, shapedIcon, layout, addToBuffers, this.symbolInstances.length,
this.symbolInstances.push(new SymbolInstance(anchor, line, shapedText, shapedIcon, layout,
addToBuffers, this.symbolInstances.length, feature, this.layerIDs,
textBoxScale, textPadding, textAlongLine,
iconBoxScale, iconPadding, iconAlongLine));
}
Expand Down Expand Up @@ -408,18 +409,14 @@ SymbolBucket.prototype.placeFeatures = function(collisionTile, buffers, collisio
// Insert final placement into collision tree and add glyphs/icons to buffers

if (hasText) {
if (!layout['text-ignore-placement']) {
collisionTile.insertCollisionFeature(symbolInstance.textCollisionFeature, glyphScale);
}
collisionTile.insertCollisionFeature(symbolInstance.textCollisionFeature, glyphScale, layout['text-ignore-placement']);
if (glyphScale <= maxScale) {
this.addSymbols('glyph', symbolInstance.glyphQuads, glyphScale, layout['text-keep-upright'], textAlongLine, collisionTile.angle);
}
}

if (hasIcon) {
if (!layout['icon-ignore-placement']) {
collisionTile.insertCollisionFeature(symbolInstance.iconCollisionFeature, iconScale);
}
collisionTile.insertCollisionFeature(symbolInstance.iconCollisionFeature, iconScale, layout['icon-ignore-placement']);
if (iconScale <= maxScale) {
this.addSymbols('icon', symbolInstance.iconQuads, iconScale, layout['icon-keep-upright'], iconAlongLine, collisionTile.angle);
}
Expand Down Expand Up @@ -534,7 +531,7 @@ SymbolBucket.prototype.addToDebugBuffers = function(collisionTile) {
}
};

function SymbolInstance(anchor, line, shapedText, shapedIcon, layout, addToBuffers, index,
function SymbolInstance(anchor, line, shapedText, shapedIcon, layout, addToBuffers, index, feature, layerIDs,
textBoxScale, textPadding, textAlongLine,
iconBoxScale, iconPadding, iconAlongLine) {

Expand All @@ -546,11 +543,11 @@ function SymbolInstance(anchor, line, shapedText, shapedIcon, layout, addToBuffe

if (this.hasText) {
this.glyphQuads = addToBuffers ? getGlyphQuads(anchor, shapedText, textBoxScale, line, layout, textAlongLine) : [];
this.textCollisionFeature = new CollisionFeature(line, anchor, shapedText, textBoxScale, textPadding, textAlongLine, false);
this.textCollisionFeature = new CollisionFeature(line, anchor, feature, layerIDs, shapedText, textBoxScale, textPadding, textAlongLine, false);
}

if (this.hasIcon) {
this.iconQuads = addToBuffers ? getIconQuads(anchor, shapedIcon, iconBoxScale, line, layout, iconAlongLine) : [];
this.iconCollisionFeature = new CollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconAlongLine, true);
this.iconCollisionFeature = new CollisionFeature(line, anchor, feature, layerIDs, shapedIcon, iconBoxScale, iconPadding, iconAlongLine, true);
}
}
1 change: 1 addition & 0 deletions js/source/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ exports._vectorFeaturesAt = function(coord, params, callback) {
x: result.x,
y: result.y,
scale: result.scale,
tileSize: result.tileSize,
source: this.id,
params: params
}, callback, result.tile.workerID);
Expand Down
3 changes: 2 additions & 1 deletion js/source/tile_pyramid.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ TilePyramid.prototype = {
tile: tile,
x: pos.x,
y: pos.y,
scale: this.transform.worldSize / Math.pow(2, tile.coord.z)
scale: Math.pow(2, this.transform.zoom - tile.coord.z),
tileSize: this.tileSize
};
}
}
Expand Down
13 changes: 7 additions & 6 deletions js/source/worker_tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {

this.status = 'parsing';

this.featureTree = new FeatureTree(this.coord, this.overscaling);
var collisionTile = new CollisionTile(this.angle, this.pitch);
this.featureTree = new FeatureTree(this.coord, this.overscaling, collisionTile);

var stats = { _total: 0 };

Expand Down Expand Up @@ -63,7 +64,7 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {
for (i = 0; i < layers.length; i++) {
layer = layers[i];
if (layer.source === this.source && layer.ref && bucketsById[layer.ref]) {
bucketsById[layer.ref].layers.push(layer.id);
bucketsById[layer.ref].layerIDs.push(layer.id);
}
}

Expand Down Expand Up @@ -93,8 +94,6 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {
symbolBuckets = this.symbolBuckets = [],
otherBuckets = [];

var collisionTile = new CollisionTile(this.angle, this.pitch);

for (var id in bucketsById) {
bucket = bucketsById[id];
if (bucket.features.length === 0) continue;
Expand Down Expand Up @@ -165,10 +164,10 @@ WorkerTile.prototype.parse = function(data, layers, actor, callback) {
bucket.addFeatures(collisionTile, stacks, icons);
var time = Date.now() - now;

if (bucket.interactive) {
if (bucket.interactive && bucket.type !== 'symbol') {
for (var i = 0; i < bucket.features.length; i++) {
var feature = bucket.features[i];
tile.featureTree.insert(feature.bbox(), bucket.layers, feature);
tile.featureTree.insert(feature.bbox(), bucket.layerIDs, feature);
}
}

Expand Down Expand Up @@ -208,6 +207,8 @@ WorkerTile.prototype.redoPlacement = function(angle, pitch, collisionDebug) {
var buffers = {},
collisionTile = new CollisionTile(angle, pitch);

this.featureTree.setCollisionTile(collisionTile);

for (var i = this.symbolBuckets.length - 1; i >= 0; i--) {
this.symbolBuckets[i].placeFeatures(collisionTile, buffers, collisionDebug);
}
Expand Down
10 changes: 9 additions & 1 deletion js/symbol/collision_box.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ module.exports = CollisionBox;
* @param {number} x2 The distance from the anchor to the right edge.
* @param {number} y2 The distance from the anchor to the bottom edge.
* @param {number} maxScale The maximum scale this box can block other boxes at.
* @param {VectorTileFeature} feature The VectorTileFeature that this CollisionBox was created for.
* @param {Array<string>} layerIDs The IDs of the layers that this CollisionBox is a part of.
* @private
*/
function CollisionBox(anchorPoint, x1, y1, x2, y2, maxScale) {
function CollisionBox(anchorPoint, x1, y1, x2, y2, maxScale, feature, layerIDs) {
// the box is centered around the anchor point
this.anchorPoint = anchorPoint;

Expand All @@ -57,6 +59,12 @@ function CollisionBox(anchorPoint, x1, y1, x2, y2, maxScale) {
// The box does not block other boxes at scales >= maxScale;
this.maxScale = maxScale;

// the index of the feature in the original vectortile
this.feature = feature;

// the IDs of the layers this feature collision box appears in
this.layerIDs = layerIDs;

// the scale at which the label can first be shown
this.placementScale = 0;

Expand Down
16 changes: 10 additions & 6 deletions js/symbol/collision_feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ module.exports = CollisionFeature;
* @class CollisionFeature
* @param {Array<Point>} line The geometry the label is placed on.
* @param {Anchor} anchor The point along the line around which the label is anchored.
* @param {VectorTileFeature} feature The VectorTileFeature that this CollisionFeature was created for.
* @param {Array<string>} layerIDs The IDs of the layers that this CollisionFeature is a part of.
* @param {Object} shaped The text or icon shaping results.
* @param {number} boxScale A magic number used to convert from glyph metrics units to geometry units.
* @param {number} padding The amount of padding to add around the label edges.
* @param {boolean} alignLine Whether the label is aligned with the line or the viewport.
*
* @private
*/
function CollisionFeature(line, anchor, shaped, boxScale, padding, alignLine, straight) {
function CollisionFeature(line, anchor, feature, IDs, shaped, boxScale, padding, alignLine, straight) {

var y1 = shaped.top * boxScale - padding;
var y2 = shaped.bottom * boxScale + padding;
Expand All @@ -44,14 +46,14 @@ function CollisionFeature(line, anchor, shaped, boxScale, padding, alignLine, st
// used for icon labels that are aligned with the line, but don't curve along it
var vector = line[anchor.segment + 1].sub(line[anchor.segment])._unit()._mult(length);
var straightLine = [anchor.sub(vector), anchor.add(vector)];
this._addLineCollisionBoxes(straightLine, anchor, 0, length, height);
this._addLineCollisionBoxes(straightLine, anchor, 0, length, height, feature, IDs);
} else {
// used for text labels that curve along a line
this._addLineCollisionBoxes(line, anchor, anchor.segment, length, height);
this._addLineCollisionBoxes(line, anchor, anchor.segment, length, height, feature, IDs);
}

} else {
this.boxes.push(new CollisionBox(new Point(anchor.x, anchor.y), x1, y1, x2, y2, Infinity));
this.boxes.push(new CollisionBox(new Point(anchor.x, anchor.y), x1, y1, x2, y2, Infinity, feature, IDs));
}
}

Expand All @@ -61,11 +63,13 @@ function CollisionFeature(line, anchor, shaped, boxScale, padding, alignLine, st
* @param {Array<Point>} line
* @param {Anchor} anchor
* @param {number} labelLength The length of the label in geometry units.
* @param {Anchor} anchor The point along the line around which the label is anchored.
* @param {VectorTileFeature} feature The VectorTileFeature that this CollisionFeature was created for.
* @param {number} boxSize The size of the collision boxes that will be created.
*
* @private
*/
CollisionFeature.prototype._addLineCollisionBoxes = function(line, anchor, segment, labelLength, boxSize) {
CollisionFeature.prototype._addLineCollisionBoxes = function(line, anchor, segment, labelLength, boxSize, feature, IDs) {
var step = boxSize / 2;
var nBoxes = Math.floor(labelLength / step);

Expand Down Expand Up @@ -118,7 +122,7 @@ CollisionFeature.prototype._addLineCollisionBoxes = function(line, anchor, segme
var distanceToInnerEdge = Math.max(Math.abs(boxDistanceToAnchor - firstBoxOffset) - step / 2, 0);
var maxScale = labelLength / 2 / distanceToInnerEdge;

bboxes.push(new CollisionBox(boxAnchorPoint, -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale));
bboxes.push(new CollisionBox(boxAnchorPoint, -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale, feature, IDs));
}

return bboxes;
Expand Down
Loading

0 comments on commit 3a0b874

Please sign in to comment.