From 75316055b71a220aeeeb0267c8e10604d84c334a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 23 Mar 2016 18:09:21 -0700 Subject: [PATCH] fix box queries that cross the dateline --- js/data/feature_tree.js | 91 ++++++++++++++++++----------- js/source/tile.js | 15 ----- js/source/tile_pyramid.js | 47 +++++++++++---- js/ui/map.js | 2 +- package.json | 2 +- test/js/source/tile_pyramid.test.js | 12 ++-- test/js/ui/map.test.js | 4 +- 7 files changed, 103 insertions(+), 70 deletions(-) diff --git a/js/data/feature_tree.js b/js/data/feature_tree.js index ec14b3adbf3..125b466f1b3 100644 --- a/js/data/feature_tree.js +++ b/js/data/feature_tree.js @@ -123,8 +123,10 @@ FeatureTree.prototype.query = function(args, styleLayers) { additionalRadius = Math.max(additionalRadius, styleLayerDistance * pixelsToTileUnits); } - var queryGeometry = args.queryGeometry.map(function(p) { - return new Point(p.x, p.y); + var queryGeometry = args.queryGeometry.map(function(q) { + return q.map(function(p) { + return new Point(p.x, p.y); + }); }); var minX = Infinity; @@ -132,11 +134,14 @@ FeatureTree.prototype.query = function(args, styleLayers) { var maxX = -Infinity; var maxY = -Infinity; for (var i = 0; i < queryGeometry.length; i++) { - var p = queryGeometry[i]; - minX = Math.min(minX, p.x); - minY = Math.min(minY, p.y); - maxX = Math.max(maxX, p.x); - maxY = Math.max(maxY, p.y); + var ring = queryGeometry[i]; + for (var k = 0; k < ring.length; k++) { + var p = ring[k]; + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } } var matching = this.grid.query(minX - additionalRadius, minY - additionalRadius, maxX + additionalRadius, maxY + additionalRadius); @@ -210,20 +215,20 @@ FeatureTree.prototype.filterMatching = function(result, matching, array, queryGe if (paint['line-offset']) { geometry = offsetLine(geometry, paint['line-offset'] * pixelsToTileUnits); } - if (!polygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth)) continue; + if (!multiPolygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth)) continue; } else if (styleLayer.type === 'fill') { translatedPolygon = translate(queryGeometry, paint['fill-translate'], paint['fill-translate-anchor'], bearing, pixelsToTileUnits); - if (!polygonIntersectsMultiPolygon(translatedPolygon, geometry)) continue; + if (!multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry)) continue; } else if (styleLayer.type === 'circle') { translatedPolygon = translate(queryGeometry, paint['circle-translate'], paint['circle-translate-anchor'], bearing, pixelsToTileUnits); var circleRadius = paint['circle-radius'] * pixelsToTileUnits; - if (!polygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, circleRadius)) continue; + if (!multiPolygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, circleRadius)) continue; } } @@ -253,7 +258,12 @@ function translate(queryGeometry, translate, translateAnchor, bearing, pixelsToT var translated = []; for (var i = 0; i < queryGeometry.length; i++) { - translated.push(queryGeometry[i].sub(translate._mult(pixelsToTileUnits))); + var ring = queryGeometry[i]; + var translatedRing = []; + for (var k = 0; k < ring.length; k++) { + translatedRing.push(ring[k].sub(translate._mult(pixelsToTileUnits))); + } + translated.push(translatedRing); } return translated; } @@ -282,52 +292,63 @@ function offsetLine(rings, offset) { return newRings; } -function polygonIntersectsBufferedMultiPoint(polygon, rings, radius) { - 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 (polygonContainsPoint(polygon, point)) return true; - if (pointIntersectsBufferedLine(point, polygon, radius)) return true; +function multiPolygonIntersectsBufferedMultiPoint(multiPolygon, rings, radius) { + for (var j = 0; j < multiPolygon.length; j++) { + var polygon = multiPolygon[j]; + 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 (polygonContainsPoint(polygon, point)) return true; + if (pointIntersectsBufferedLine(point, polygon, radius)) return true; + } } } return false; } -function polygonIntersectsMultiPolygon(polygon, multiPolygon) { +function multiPolygonIntersectsMultiPolygon(multiPolygonA, multiPolygonB) { - if (polygon.length === 1) { - return multiPolygonContainsPoint(multiPolygon, polygon[0]); + if (multiPolygonA.length === 1 && multiPolygonA[0].length === 1) { + return multiPolygonContainsPoint(multiPolygonB, multiPolygonA[0][0]); } - for (var m = 0; m < multiPolygon.length; m++) { - var ring = multiPolygon[m]; + for (var m = 0; m < multiPolygonB.length; m++) { + var ring = multiPolygonB[m]; for (var n = 0; n < ring.length; n++) { - if (polygonContainsPoint(polygon, ring[n])) return true; + if (multiPolygonContainsPoint(multiPolygonA, ring[n])) return true; } } - for (var i = 0; i < polygon.length; i++) { - if (multiPolygonContainsPoint(multiPolygon, polygon[i])) return true; - } + for (var j = 0; j < multiPolygonA.length; j++) { + var polygon = multiPolygonA[j]; + for (var i = 0; i < polygon.length; i++) { + if (multiPolygonContainsPoint(multiPolygonB, polygon[i])) return true; + } - for (var k = 0; k < multiPolygon.length; k++) { - if (lineIntersectsLine(polygon, multiPolygon[k])) return true; + for (var k = 0; k < multiPolygonB.length; k++) { + if (lineIntersectsLine(polygon, multiPolygonB[k])) return true; + } } + return false; } -function polygonIntersectsBufferedMultiLine(polygon, multiLine, radius) { +function multiPolygonIntersectsBufferedMultiLine(multiPolygon, multiLine, radius) { for (var i = 0; i < multiLine.length; i++) { var line = multiLine[i]; - if (polygon.length >= 3) { - for (var k = 0; k < line.length; k++) { - if (polygonContainsPoint(polygon, line[k])) return true; + for (var j = 0; j < multiPolygon.length; j++) { + var polygon = multiPolygon[j]; + + if (polygon.length >= 3) { + for (var k = 0; k < line.length; k++) { + if (polygonContainsPoint(polygon, line[k])) return true; + } } - } - if (lineIntersectsBufferedLine(polygon, line, radius)) return true; + if (lineIntersectsBufferedLine(polygon, line, radius)) return true; + } } return false; } diff --git a/js/source/tile.js b/js/source/tile.js index c45a67c4f15..3facd9bbe5c 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -33,21 +33,6 @@ function Tile(coord, size, sourceMaxZoom) { Tile.prototype = { - /** - * Given a coordinate position, zoom that coordinate to my zoom and - * scale and return a position in x, y, scale - * @param {Coordinate} coord - * @returns {Object} position - * @private - */ - positionAt: function(coord) { - var zoomedCoord = coord.zoomTo(Math.min(this.coord.z, this.sourceMaxZoom)); - return { - x: (zoomedCoord.column - this.coord.x) * Bucket.EXTENT, - y: (zoomedCoord.row - this.coord.y) * Bucket.EXTENT - }; - }, - /** * Given a data object with a 'buffers' property, load it into * this tile's elementGroups and buffers properties and set loaded diff --git a/js/source/tile_pyramid.js b/js/source/tile_pyramid.js index b14fa75a424..b5768414cbb 100644 --- a/js/source/tile_pyramid.js +++ b/js/source/tile_pyramid.js @@ -386,7 +386,7 @@ TilePyramid.prototype = { * @private */ tilesIn: function(queryGeometry) { - var result = []; + var tileResults = {}; var ids = this.orderedIDs(); var minX = Infinity; @@ -405,10 +405,11 @@ TilePyramid.prototype = { for (var i = 0; i < ids.length; i++) { var tile = this._tiles[ids[i]]; + var coord = TileCoord.fromID(ids[i]); var tileSpaceBounds = [ - tile.positionAt(new Coordinate(minX, minY, z)), - tile.positionAt(new Coordinate(maxX, maxY, z)) + coordinateToTilePoint(coord, tile.sourceMaxZoom, new Coordinate(minX, minY, z)), + coordinateToTilePoint(coord, tile.sourceMaxZoom, new Coordinate(maxX, maxY, z)) ]; if (tileSpaceBounds[0].x < EXTENT && tileSpaceBounds[0].y < EXTENT && @@ -416,21 +417,47 @@ TilePyramid.prototype = { var tileSpaceQueryGeometry = []; for (var j = 0; j < queryGeometry.length; j++) { - tileSpaceQueryGeometry.push(tile.positionAt(queryGeometry[j])); + tileSpaceQueryGeometry.push(coordinateToTilePoint(coord, tile.sourceMaxZoom, queryGeometry[j])); } - result.push({ - tile: tile, - queryGeometry: tileSpaceQueryGeometry, - scale: Math.pow(2, this.transform.zoom - tile.coord.z) - }); + var tileResult = tileResults[tile.coord.id]; + if (tileResult === undefined) { + tileResult = tileResults[tile.coord.id] = { + tile: tile, + queryGeometry: [], + scale: Math.pow(2, this.transform.zoom - tile.coord.z) + }; + } + + // Wrapped tiles share one tileResult object but can have multiple queryGeometry parts + tileResult.queryGeometry.push(tileSpaceQueryGeometry); } } - return result; + var results = []; + for (var t in tileResults) { + results.push(tileResults[t]); + } + return results; } }; +/** + * Convert a coordinate to a point in a tile's coordinate space. + * @param {Coordinate} tileCoord + * @param {Coordinate} coord + * @returns {Object} position + * @private + */ +function coordinateToTilePoint(tileCoord, sourceMaxZoom, coord) { + var zoomedCoord = coord.zoomTo(Math.min(tileCoord.z, sourceMaxZoom)); + return { + x: (zoomedCoord.column - (tileCoord.x + tileCoord.w * Math.pow(2, tileCoord.z))) * EXTENT, + y: (zoomedCoord.row - tileCoord.y) * EXTENT + }; + +} + function compareKeyZoom(a, b) { return (a % 32) - (b % 32); } diff --git a/js/ui/map.js b/js/ui/map.js index 217f0e090e5..590b82dec9b 100644 --- a/js/ui/map.js +++ b/js/ui/map.js @@ -430,7 +430,7 @@ util.extend(Map.prototype, /** @lends Map.prototype */{ } queryGeometry = queryGeometry.map(function(p) { - return this.transform.locationCoordinate(this.transform.pointLocation(p).wrap()); + return this.transform.pointCoordinate(p); }.bind(this)); return queryGeometry; diff --git a/package.json b/package.json index 9deaf546e7c..e9066274175 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "express": "^4.13.4", "gl": "^2.1.5", "istanbul": "^0.4.2", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#13bfab2d5937b241259586cbf545713b251305e5", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#5e94c9d0f90aaa6fd557625a35b3c4c00aba901d", "nyc": "^6.1.1", "sinon": "^1.15.4", "st": "^1.0.0", diff --git a/test/js/source/tile_pyramid.test.js b/test/js/source/tile_pyramid.test.js index b9a0bf4d2ed..82431d41e7f 100644 --- a/test/js/source/tile_pyramid.test.js +++ b/test/js/source/tile_pyramid.test.js @@ -504,12 +504,12 @@ test('TilePyramid#tilesIn', function (t) { t.equal(tiles[0].tile.coord.id, 1); t.equal(tiles[0].tile.tileSize, 512); t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [{x: 4096, y: 2048}, {x:12288, y: 6144}]); + t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); t.equal(tiles[1].tile.coord.id, 33); t.equal(tiles[1].tile.tileSize, 512); t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [{x: -4096, y: 2048}, {x: 4096, y: 6144}]); + t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); t.end(); }); @@ -546,12 +546,12 @@ test('TilePyramid#tilesIn', function (t) { t.equal(tiles[0].tile.coord.id, 2); t.equal(tiles[0].tile.tileSize, 1024); t.equal(tiles[0].scale, 1); - t.deepEqual(tiles[0].queryGeometry, [{x: 4096, y: 2048}, {x:12288, y: 6144}]); + t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); t.equal(tiles[1].tile.coord.id, 34); t.equal(tiles[1].tile.tileSize, 1024); t.equal(tiles[1].scale, 1); - t.deepEqual(tiles[1].queryGeometry, [{x: -4096, y: 2048}, {x: 4096, y: 6144}]); + t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); t.end(); }); @@ -589,12 +589,12 @@ test('TilePyramid#tilesIn', function (t) { t.equal(tiles[0].tile.coord.id, 1); t.equal(tiles[0].tile.tileSize, 512); t.equal(tiles[0].scale, 2); - t.deepEqual(tiles[0].queryGeometry, [{x: 4096, y: 2048}, {x:12288, y: 6144}]); + t.deepEqual(tiles[0].queryGeometry, [[{x: 4096, y: 2048}, {x:12288, y: 6144}]]); t.equal(tiles[1].tile.coord.id, 33); t.equal(tiles[1].tile.tileSize, 512); t.equal(tiles[1].scale, 2); - t.deepEqual(tiles[1].queryGeometry, [{x: -4096, y: 2048}, {x: 4096, y: 6144}]); + t.deepEqual(tiles[1].queryGeometry, [[{x: -4096, y: 2048}, {x: 4096, y: 6144}]]); t.end(); }); diff --git a/test/js/ui/map.test.js b/test/js/ui/map.test.js index b3466746dea..fe6246e9044 100644 --- a/test/js/ui/map.test.js +++ b/test/js/ui/map.test.js @@ -508,10 +508,10 @@ test('Map', function(t) { map.queryRenderedFeatures(map.project(new LngLat(0, 0)), opts); }); - t.test('wraps coords', function(t) { + t.test('does not wrap coords', function(t) { map.style.queryRenderedFeatures = function (coords, o, classes, zoom, bearing) { // avoid floating point issues - t.equal(parseFloat(coords[0].column.toFixed(4)), 0.5); + t.equal(parseFloat(coords[0].column.toFixed(4)), 1.5); t.equal(coords[0].row, 0.5); t.equal(coords[0].zoom, 0);