Skip to content

Commit

Permalink
Reuse last placement data in CollisionTile.queryRenderedSymbols()
Browse files Browse the repository at this point in the history
  • Loading branch information
brunoabinader committed Oct 25, 2016
1 parent f8d21a6 commit b1cb5a0
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 40 deletions.
2 changes: 1 addition & 1 deletion js/data/feature_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class FeatureIndex {
matching.sort(topDownFeatureComparator);
this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits);

const matchingSymbols = this.collisionTile.queryRenderedSymbols(minX, minY, maxX, maxY, args.scale);
const matchingSymbols = this.collisionTile.queryRenderedSymbols(queryGeometry, args.scale);
matchingSymbols.sort();
this.filterMatching(result, matchingSymbols, this.collisionTile.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits);

Expand Down
112 changes: 73 additions & 39 deletions js/symbol/collision_tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ const Point = require('point-geometry');
const EXTENT = require('../data/extent');
const Grid = require('grid-index');

const intersectionTests = require('../symbol/intersection_tests');

// Predicate for ruling out already seen features.
function seenFeature(sourceLayerFeatures, blocking) {
const sourceLayer = blocking.sourceLayerIndex;
const featureIndex = blocking.featureIndex;
if (sourceLayerFeatures[sourceLayer] === undefined) {
sourceLayerFeatures[sourceLayer] = {};
}
return sourceLayerFeatures[sourceLayer][featureIndex] === true;
}

// Check if feature is rendered (collision free) at current scale.
function visibleAtScale(blocking, roundedScale) {
return roundedScale >= blocking.placementScale && roundedScale <= blocking.maxScale;
}

// Check if query polygon intersects with the feature box at current
// scale.
function intersectsAtScale(rotationMatrix, rotatedQuery, blocking, scale, yStretch) {
const anchor = blocking.anchorPoint.matMult(rotationMatrix);
const x1 = anchor.x + blocking.x1 / scale;
const y1 = anchor.y + blocking.y1 / scale * yStretch;
const x2 = anchor.x + blocking.x2 / scale;
const y2 = anchor.y + blocking.y2 / scale * yStretch;
const bbox = [
new Point(x1, y1),
new Point(x2, y1),
new Point(x2, y2),
new Point(x1, y2)
];
return intersectionTests.polygonIntersectsPolygon(rotatedQuery, bbox);
}

/**
* A collision tile used to prevent symbols from overlapping. It keep tracks of
* where previous symbols have been placed and is used to check if a new
Expand All @@ -25,7 +59,7 @@ class CollisionTile {
this.ignoredGrid = new Grid(EXTENT, 12, 0);
}

this.minScale = 0.25;
this.minScale = 0.5;
this.maxScale = 2;

this.angle = angle;
Expand Down Expand Up @@ -173,56 +207,56 @@ class CollisionTile {
return minPlacementScale;
}

queryRenderedSymbols(minX, minY, maxX, maxY, scale) {
queryRenderedSymbols(queryGeometry, scale) {
const sourceLayerFeatures = {};
const result = [];

if (queryGeometry.length === 0 || (this.grid.length === 0 && this.ignoredGrid.length === 0)) {
return result;
}

const collisionBoxArray = this.collisionBoxArray;
const rotationMatrix = this.rotationMatrix;
const anchorPoint = new Point(minX, minY)._matMult(rotationMatrix);

const queryBox = this.tempCollisionBox;
queryBox.anchorX = anchorPoint.x;
queryBox.anchorY = anchorPoint.y;
queryBox.x1 = 0;
queryBox.y1 = 0;
queryBox.x2 = maxX - minX;
queryBox.y2 = maxY - minY;
queryBox.maxScale = scale;

// maxScale is stored using a Float32. Convert `scale` to the stored Float32 value.
scale = queryBox.maxScale;

const searchBox = [
anchorPoint.x + queryBox.x1 / scale,
anchorPoint.y + queryBox.y1 / scale * this.yStretch,
anchorPoint.x + queryBox.x2 / scale,
anchorPoint.y + queryBox.y2 / scale * this.yStretch
];

const blockingBoxKeys = this.grid.query(searchBox[0], searchBox[1], searchBox[2], searchBox[3]);
const blockingBoxKeys2 = this.ignoredGrid.query(searchBox[0], searchBox[1], searchBox[2], searchBox[3]);
for (let k = 0; k < blockingBoxKeys2.length; k++) {
blockingBoxKeys.push(blockingBoxKeys2[k]);
// Generate a rotated geometry out of the original query geometry.
// Scale has already been handled by the prior conversions.
const rotatedQuery = [];
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (let i = 0; i < queryGeometry.length; i++) {
const ring = queryGeometry[i];
for (let k = 0; k < ring.length; k++) {
const p = ring[k].matMult(rotationMatrix);
minX = Math.min(minX, p.x);
minY = Math.min(minY, p.y);
maxX = Math.max(maxX, p.x);
maxY = Math.max(maxY, p.y);
rotatedQuery.push(p);
}
}

const features = this.grid.query(minX, minY, maxX, maxY);
const ignoredFeatures = this.ignoredGrid.query(minX, minY, maxX, maxY);
for (let i = 0; i < ignoredFeatures.length; i++) {
features.push(ignoredFeatures[i]);
}

for (let i = 0; i < blockingBoxKeys.length; i++) {
const blocking = collisionBoxArray.get(blockingBoxKeys[i]);
// Account for the rounding done when updating symbol shader variables.
const roundedScale = Math.pow(2, Math.ceil(Math.log(scale) / Math.LN2 * 10) / 10);

for (let i = 0; i < features.length; i++) {
const blocking = collisionBoxArray.get(features[i]);
const sourceLayer = blocking.sourceLayerIndex;
const featureIndex = blocking.featureIndex;
if (sourceLayerFeatures[sourceLayer] === undefined) {
sourceLayerFeatures[sourceLayer] = {};
}

if (!sourceLayerFeatures[sourceLayer][featureIndex]) {
const blockingAnchorPoint = blocking.anchorPoint.matMult(rotationMatrix);
const minPlacementScale = this.getPlacementScale(this.minScale, anchorPoint, queryBox, blockingAnchorPoint, blocking);
if (minPlacementScale >= scale) {
sourceLayerFeatures[sourceLayer][featureIndex] = true;
result.push(blockingBoxKeys[i]);
}
}
if (seenFeature(sourceLayerFeatures, blocking)) continue;
if (!visibleAtScale(blocking, roundedScale)) continue;
if (!intersectsAtScale(rotatedQuery, blocking, scale, this.yStretch)) continue;

sourceLayerFeatures[sourceLayer][featureIndex] = true;
result.push(features[i]);
}

return result;
Expand Down

0 comments on commit b1cb5a0

Please sign in to comment.