Skip to content

Commit

Permalink
use style properties for featuresIn, remove radius
Browse files Browse the repository at this point in the history
  • Loading branch information
ansis committed Feb 23, 2016
1 parent 5accf48 commit 5529efd
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 121 deletions.
215 changes: 106 additions & 109 deletions js/data/feature_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ FeatureTree.prototype.query = function(args, styleLayersByID) {
var params = args.params || {},
x = args.x,
y = args.y,
p = new Point(x, y),
pixelsToTileUnits = EXTENT / args.tileSize / args.scale,
result = [];

Expand All @@ -74,19 +73,23 @@ FeatureTree.prototype.query = function(args, styleLayersByID) {
additionalRadius = Math.max(additionalRadius, styleLayerDistance * pixelsToTileUnits);
}

var radiusSearch = typeof x !== 'undefined' && typeof y !== 'undefined';

var radius, bounds, symbolQueryBox;
if (radiusSearch) {
// a point (or point+radius) query
radius = (params.radius || 0) * pixelsToTileUnits;
var searchRadius = radius + additionalRadius;
bounds = [x - searchRadius, y - searchRadius, x + searchRadius, y + searchRadius];
symbolQueryBox = new CollisionBox(new Point(x, y), -radius, -radius, radius, radius, args.scale, null);
var bounds, symbolQueryBox, queryPolygon;
if (x !== undefined && y !== undefined) {
// a point query
bounds = [x - additionalRadius, y - additionalRadius, x + additionalRadius, y + additionalRadius];
symbolQueryBox = new CollisionBox(new Point(x, y), 0, 0, 0, 0, args.scale, null);
queryPolygon = [new Point(x, y)];
} 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);
queryPolygon = [
new Point(args.minX, args.minY),
new Point(args.maxX, args.minY),
new Point(args.maxX, args.maxY),
new Point(args.minX, args.maxY),
new Point(args.minX, args.minY)
];
}

var matching = this.rtree.search(bounds).concat(this.collisionTile.getFeaturesAt(symbolQueryBox, args.scale));
Expand Down Expand Up @@ -115,52 +118,48 @@ FeatureTree.prototype.query = function(args, styleLayersByID) {
styleLayer = styleLayersByID[layerID];
var geometry = loadGeometry(feature);

var translatedPoint;
var translatedPolygon;
if (styleLayer.type === 'symbol') {
// all symbols already match the style

} else if (styleLayer.type === 'line') {
translatedPoint = translate(styleLayer.paint['line-translate'], styleLayer.paint['line-translate-anchor']);
translatedPolygon = translate(styleLayer.paint['line-translate'], styleLayer.paint['line-translate-anchor']);
var halfWidth = styleLayer.paint['line-width'] / 2 * pixelsToTileUnits;
if (styleLayer.paint['line-offset']) {
geometry = offsetLine(geometry, styleLayer.paint['line-offset'] * pixelsToTileUnits);
}
if (radiusSearch ?
!lineContainsPoint(geometry, translatedPoint, radius + halfWidth) :
!lineIntersectsBox(geometry, bounds)) {
continue;
}
if (!polygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth)) continue;

} else if (styleLayer.type === 'fill') {
translatedPoint = translate(styleLayer.paint['fill-translate'], styleLayer.paint['fill-translate-anchor']);
if (radiusSearch ?
!(polyContainsPoint(geometry, translatedPoint) || lineContainsPoint(geometry, translatedPoint, radius)) :
!polyIntersectsBox(geometry, bounds)) {
continue;
}
translatedPolygon = translate(styleLayer.paint['fill-translate'], styleLayer.paint['fill-translate-anchor']);
if (!polygonIntersectsMultiPolygon(translatedPolygon, geometry)) continue;

} else if (styleLayer.type === 'circle') {
translatedPoint = translate(styleLayer.paint['circle-translate'], styleLayer.paint['circle-translate-anchor']);
translatedPolygon = translate(styleLayer.paint['circle-translate'], styleLayer.paint['circle-translate-anchor']);
var circleRadius = styleLayer.paint['circle-radius'] * pixelsToTileUnits;
if (radiusSearch ?
!pointContainsPoint(geometry, translatedPoint, radius + circleRadius) :
!pointIntersectsBox(geometry, bounds)) {
continue;
}
if (!polygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, circleRadius)) continue;
}

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

function translate(translate, translateAnchor) {
if (!translate[0] && !translate[1]) {
return queryPolygon;
}

translate = Point.convert(translate);

if (translateAnchor === "viewport") {
translate._rotate(-args.bearing);
}

return p.sub(translate._mult(pixelsToTileUnits));
var translated = [];
for (var i = 0; i < queryPolygon.length; i++) {
translated.push(queryPolygon[i].sub(translate._mult(pixelsToTileUnits)));
}
return translated;
}

return result;
Expand Down Expand Up @@ -190,71 +189,96 @@ function offsetLine(rings, offset) {
return newRings;
}

// 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;
function polygonIntersectsBufferedMultiPoint(polygon, rings, radius) {
var multiPolygon = [polygon];
for (var i = 0; i < rings.length; i++) {
var ring = rings[i];
for (var k = 0; k < ring.length; k++) {
var point = ring[k];
if (multiPolygonContainsPoint(multiPolygon, point)) return true;
if (pointIntersectsBufferedLine(point, polygon, radius)) return true;
}
}
}

return lineIntersectsBox(rings, bounds);
function polygonIntersectsMultiPolygon(polygon, multiPolygon) {
for (var i = 0; i < polygon.length; i++) {
if (multiPolygonContainsPoint(multiPolygon, polygon[i])) return true;
}
for (var k = 0; k < multiPolygon.length; k++) {
if (lineIntersectsLine(polygon, multiPolygon[k])) return true;
}
return false;
}

// 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;
function polygonIntersectsBufferedMultiLine(polygon, multiLine, radius) {
var multiPolygon = [polygon];
for (var i = 0; i < multiLine.length; i++) {
var line = multiLine[i];

for (var k = 0; k < line.length; k++) {
if (multiPolygonContainsPoint(multiPolygon, line[k])) return true;
}
}

return pointIntersectsBox(rings, bounds);
if (lineIntersectsBufferedLine(polygon, line, radius)) return true;
}
return false;
}

/*
* 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 lineIntersectsBufferedLine(lineA, lineB, radius) {

if (lineIntersectsLine(lineA, lineB)) return true;

// Check whether any point in either line is within radius of the other line
for (var j = 0; j < lineB.length; j++) {
if (pointIntersectsBufferedLine(lineB[j], lineA, radius)) return true;
}

for (var k = 0; k < lineA.length; k++) {
if (pointIntersectsBufferedLine(lineA[k], lineB, radius)) return true;
}

return false;
}

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;
function lineIntersectsLine(lineA, lineB) {
for (var i = 0; i < lineA.length - 1; i++) {
var a0 = lineA[i];
var a1 = lineA[i + 1];
for (var j = 0; j < lineB.length - 1; j++) {
var b0 = lineB[j];
var b1 = lineB[j + 1];
if (lineSegmentIntersectsLineSegment(a0, a1, b0, b1)) return true;
}
}
return false;
}


// http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
function isCounterClockwise(a, b, c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}

function lineSegmentIntersectsLineSegment(a0, a1, b0, b1) {
return isCounterClockwise(a0, b0, b1) !== isCounterClockwise(a1, b0, b1) &&
isCounterClockwise(a0, a1, b0) !== isCounterClockwise(a0, a1, b1);
}

function pointIntersectsBufferedLine(p, line, radius) {
var radiusSquared = radius * radius;

if (line.length === 1) return p.distSqr(line[0]) < radiusSquared;

for (var i = 1; i < line.length; i++) {
// Find line segments that have a distance <= radius^2 to p
// In that case, we treat the line as "containing point p".
var v = line[i - 1], w = line[i];
if (distToSegmentSquared(p, v, w) < radiusSquared) return true;
}
return false;
}

// Code from http://stackoverflow.com/a/1501725/331379.
function distToSegmentSquared(p, v, w) {
var l2 = v.distSqr(w);
Expand All @@ -265,23 +289,8 @@ function distToSegmentSquared(p, v, 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) {
function multiPolygonContainsPoint(rings, p) {
var c = false,
ring, p1, p2;

Expand All @@ -297,15 +306,3 @@ function polyContainsPoint(rings, p) {
}
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;
}
1 change: 0 additions & 1 deletion js/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@ util.extend(Map.prototype, /** @lends Map.prototype */{
*
* @param {Array<number>} point [x, y] pixel coordinates
* @param {Object} params
* @param {number} [params.radius=0] Radius in pixels to search in
* @param {string|Array<string>} [params.layer] Only return features from a given layer or layers
* @param {string} [params.type] Either `raster` or `vector`
* @param {boolean} [params.includeGeometry=false] If `true`, geometry of features will be included in the results at the expense of a much slower query time.
Expand Down
17 changes: 6 additions & 11 deletions test/js/data/feature_tree.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ test('featuretree with args', function(t) {
t.ok(ft, 'can be created');
ft.insert(feature.bbox(), ['road'], feature);
t.deepEqual(ft.query({
params: {
radius: 5
},
params: {},
scale: 1,
tileSize: 512,
x: 0,
Expand All @@ -86,11 +84,10 @@ test('featuretree point query', function(t) {
scale: 1.4142135624,
tileSize: 512,
params: {
radius: 30,
includeGeometry: true
},
x: 1842,
y: 2014
x: -180,
y: 1780
}, styleLayers);
t.notEqual(features.length, 0, 'non-empty results for queryFeatures');
features.forEach(function(f) {
Expand Down Expand Up @@ -165,21 +162,19 @@ test('featuretree query with layerIds', function(t) {
scale: 1.4142135624,
tileSize: 512,
params: {
radius: 30,
layerIds: ['water']
},
x: 1842,
y: 2014
x: -180,
y: 1780
}, styleLayers);

t.equal(features.length, 2);
t.equal(features.length, 1);

var features2 = ft.query({
source: "mapbox.mapbox-streets-v5",
scale: 1.4142135624,
tileSize: 512,
params: {
radius: 30,
layerIds: ['none']
},
x: 1842,
Expand Down

0 comments on commit 5529efd

Please sign in to comment.