Partition viewport by tiles, not by pixels

(closes #4297)

The previous approach split the viewport up by pixels, but each time the view
moved, the pixels would change, so it was not a stable selection of the
streetview data, and the markers would fight for position as the user moved
around.

This approach uses utilTiler to partition the view into stable tiles.
This commit is contained in:
Bryan Housel
2018-11-14 14:48:42 -05:00
parent 317a3be93b
commit 1731ce4651
7 changed files with 76 additions and 134 deletions
+20 -51
View File
@@ -1,12 +1,9 @@
/* global Mapillary:false */
import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
import _some from 'lodash-es/some';
import _union from 'lodash-es/union';
import { range as d3_range } from 'd3-array';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import {
@@ -213,57 +210,29 @@ function parsePagination(links) {
}
// partition viewport into `psize` x `psize` regions
function partitionViewport(psize, projection) {
var dimensions = projection.clipExtent()[1];
psize = psize || 16;
var cols = d3_range(0, dimensions[0], psize);
var rows = d3_range(0, dimensions[1], psize);
var partitions = [];
// partition viewport into higher zoom tiles
function partitionViewport(projection) {
var z = geoScaleToZoom(projection.scale());
var z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5
var tiler = utilTiler().zoomExtent([z2, z2]);
rows.forEach(function(y) {
cols.forEach(function(x) {
var min = [x, y + psize];
var max = [x + psize, y];
partitions.push(
geoExtent(projection.invert(min), projection.invert(max)));
});
});
return partitions;
return tiler.getTiles(projection)
.map(function(tile) { return tile.extent; });
}
// no more than `limit` results per partition.
function searchLimited(psize, limit, projection, rtree) {
limit = limit || 3;
function searchLimited(limit, projection, rtree) {
limit = limit || 5;
var partitions = partitionViewport(psize, projection);
var results;
return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
// console.time('previous');
results = _flatten(_map(partitions, function(extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
}));
// console.timeEnd('previous');
// console.time('new');
// results = partitions.reduce(function(result, extent) {
// var found = rtree.search(extent.bbox())
// .map(function(d) { return d.data; })
// .sort(function(a, b) {
// return a.loc[1] - b.loc[1];
// // return a.key.localeCompare(b.key);
// })
// .slice(0, limit);
// return (found.length ? result.concat(found) : result);
// }, []);
// console.timeEnd('new');
return results;
return (found.length ? result.concat(found) : result);
}, []);
}
@@ -309,14 +278,14 @@ export default {
images: function(projection) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, _mlyCache.images.rtree);
var limit = 5;
return searchLimited(limit, projection, _mlyCache.images.rtree);
},
signs: function(projection) {
var psize = 32, limit = 3;
return searchLimited(psize, limit, projection, _mlyCache.map_features.rtree);
var limit = 5;
return searchLimited(limit, projection, _mlyCache.map_features.rtree);
},
+18 -32
View File
@@ -1,10 +1,7 @@
import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
import _union from 'lodash-es/union';
import { range as d3_range } from 'd3-array';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
@@ -164,40 +161,29 @@ function loadNextTilePage(which, currZoom, url, tile) {
}
// partition viewport into `psize` x `psize` regions
function partitionViewport(psize, projection) {
var dimensions = projection.clipExtent()[1];
psize = psize || 16;
var cols = d3_range(0, dimensions[0], psize);
var rows = d3_range(0, dimensions[1], psize);
var partitions = [];
// partition viewport into higher zoom tiles
function partitionViewport(projection) {
var z = geoScaleToZoom(projection.scale());
var z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5
var tiler = utilTiler().zoomExtent([z2, z2]);
rows.forEach(function(y) {
cols.forEach(function(x) {
var min = [x, y + psize];
var max = [x + psize, y];
partitions.push(
geoExtent(projection.invert(min), projection.invert(max)));
});
});
return partitions;
return tiler.getTiles(projection)
.map(function(tile) { return tile.extent; });
}
// no more than `limit` results per partition.
function searchLimited(psize, limit, projection, rtree) {
limit = limit || 3;
function searchLimited(limit, projection, rtree) {
limit = limit || 5;
var partitions = partitionViewport(psize, projection);
var results;
return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
results = _flatten(_map(partitions, function(extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
}));
return results;
return (found.length ? result.concat(found) : result);
}, []);
}
@@ -237,8 +223,8 @@ export default {
images: function(projection) {
var psize = 16, limit = 3;
return searchLimited(psize, limit, projection, _oscCache.images.rtree);
var limit = 5;
return searchLimited(limit, projection, _oscCache.images.rtree);
},
+20 -37
View File
@@ -1,12 +1,9 @@
import _extend from 'lodash-es/extend';
import _find from 'lodash-es/find';
import _flatten from 'lodash-es/flatten';
import _forEach from 'lodash-es/forEach';
import _map from 'lodash-es/map';
import _union from 'lodash-es/union';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { range as d3_range } from 'd3-array';
import { timer as d3_timer } from 'd3-timer';
import {
@@ -25,6 +22,7 @@ import {
geoMetersToLon,
geoPointInPolygon,
geoRotate,
geoScaleToZoom,
geoVecLength
} from '../geo';
@@ -233,45 +231,30 @@ function getBubbles(url, tile, callback) {
});
}
/**
* partitionViewport() partition viewport into `psize` x `psize` regions.
*/
function partitionViewport(psize, projection) {
var dimensions = projection.clipExtent()[1];
psize = psize || 16;
var cols = d3_range(0, dimensions[0], psize);
var rows = d3_range(0, dimensions[1], psize);
var partitions = [];
// partition viewport into higher zoom tiles
function partitionViewport(projection) {
var z = geoScaleToZoom(projection.scale());
var z2 = (Math.ceil(z * 2) / 2) + 2.5; // round to next 0.5 and add 2.5
var tiler = utilTiler().zoomExtent([z2, z2]);
rows.forEach(function (y) {
cols.forEach(function (x) {
var min = [x, y + psize];
var max = [x + psize, y];
partitions.push(geoExtent(projection.invert(min), projection.invert(max)));
});
});
return partitions;
return tiler.getTiles(projection)
.map(function(tile) { return tile.extent; });
}
/**
* searchLimited().
*/
function searchLimited(psize, limit, projection, rtree) {
limit = limit || 3;
// no more than `limit` results per partition.
function searchLimited(limit, projection, rtree) {
limit = limit || 5;
var partitions = partitionViewport(psize, projection);
var results;
return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
results = _flatten(_map(partitions, function (extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function (d) { return d.data; });
}));
return results;
return (found.length ? result.concat(found) : result);
}, []);
}
@@ -485,8 +468,8 @@ export default {
* bubbles()
*/
bubbles: function (projection) {
var psize = 32, limit = 3;
return searchLimited(psize, limit, projection, _ssCache.bubbles.rtree);
var limit = 5;
return searchLimited(limit, projection, _ssCache.bubbles.rtree);
},
+2 -2
View File
@@ -217,8 +217,8 @@ export function svgMapillaryImages(projection, context, dispatch) {
function drawImages(selection) {
var enabled = svgMapillaryImages.enabled,
service = getService();
var enabled = svgMapillaryImages.enabled;
var service = getService();
layer = selection.selectAll('.layer-mapillary-images')
.data(service ? [0] : []);
+8 -6
View File
@@ -248,18 +248,19 @@ describe('iD.serviceMapillary', function() {
]);
});
it('limits results no more than 3 stacked images in one spot', function() {
it('limits results no more than 5 stacked images in one spot', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '5', loc: [10,0], ca: 90 } }
];
mapillary.cache().images.rtree.load(features);
var res = mapillary.images(context.projection);
expect(res).to.have.length.of.at.most(3);
expect(res).to.have.length.of.at.most(5);
});
});
@@ -284,7 +285,7 @@ describe('iD.serviceMapillary', function() {
]);
});
it('limits results no more than 3 stacked signs in one spot', function() {
it('limits results no more than 5 stacked signs in one spot', function() {
var detections = [{
detection_key: '78vqha63gs1upg15s823qckcmn',
image_key: 'bwYs-uXLDvm_meo_EC5Nzw'
@@ -294,12 +295,13 @@ describe('iD.serviceMapillary', function() {
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], detections: detections } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], detections: detections } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], detections: detections } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], detections: detections } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], detections: detections } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '5', loc: [10,0], detections: detections } }
];
mapillary.cache().map_features.rtree.load(features);
var res = mapillary.signs(context.projection);
expect(res).to.have.length.of.at.most(3);
expect(res).to.have.length.of.at.most(5);
});
});
+4 -3
View File
@@ -244,18 +244,19 @@ describe('iD.serviceOpenstreetcam', function() {
]);
});
it('limits results no more than 3 stacked images in one spot', function() {
it('limits results no more than 5 stacked images in one spot', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '0', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 0 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '1', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '2', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 2 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '3', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 3 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 4 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '4', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 4 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: '5', loc: [10,0], ca: 90, sequence_id: '100', sequence_index: 5 } }
];
openstreetcam.cache().images.rtree.load(features);
var res = openstreetcam.images(context.projection);
expect(res).to.have.length.of.at.most(3);
expect(res).to.have.length.of.at.most(5);
});
});
+4 -3
View File
@@ -132,18 +132,19 @@ describe('iD.serviceStreetside', function() {
]);
});
it('limits results no more than 3 stacked bubbles in one spot', function() {
it('limits results no more than 5 stacked bubbles in one spot', function() {
var features = [
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 1, loc: [10, 0], ca: 90, pr: undefined, ne: 2, pano: true, sequence_id: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 2, loc: [10, 0], ca: 90, pr: 1, ne: 3, pano: true, sequence_id: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 3, loc: [10, 0], ca: 90, pr: 2, ne: 4, pano: true, sequence_id: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 4, loc: [10, 0], ca: 90, pr: 3, ne: 5, pano: true, sequence_id: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 5, loc: [10, 0], ca: 90, pr: 4, ne: undefined, pano: true, sequence_id: 1 } }
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 5, loc: [10, 0], ca: 90, pr: 4, ne: 6, pano: true, sequence_id: 1 } },
{ minX: 10, minY: 0, maxX: 10, maxY: 0, data: { key: 6, loc: [10, 0], ca: 90, pr: 5, ne: undefined, pano: true, sequence_id: 1 } }
];
streetside.cache().bubbles.rtree.load(features);
var res = streetside.bubbles(context.projection);
expect(res).to.have.length.of.at.most(3);
expect(res).to.have.length.of.at.most(5);
});
});