Skip to content

Commit

Permalink
Partition viewport by tiles, not by pixels
Browse files Browse the repository at this point in the history
(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.
  • Loading branch information
bhousel committed Nov 14, 2018
1 parent 317a3be commit 1731ce4
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 139 deletions.
75 changes: 22 additions & 53 deletions modules/services/mapillary.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 = [];

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)));
});
});
// 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]);

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;

var partitions = partitionViewport(psize, projection);
var results;

// 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;
function searchLimited(limit, projection, rtree) {
limit = limit || 5;

return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });

return (found.length ? result.concat(found) : result);
}, []);
}


Expand Down Expand Up @@ -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);
},


Expand Down
54 changes: 20 additions & 34 deletions modules/services/openstreetcam.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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 = [];

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)));
});
});
// 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]);

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;

var partitions = partitionViewport(psize, projection);
var results;

results = _flatten(_map(partitions, function(extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });
}));
return results;
function searchLimited(limit, projection, rtree) {
limit = limit || 5;

return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });

return (found.length ? result.concat(found) : result);
}, []);
}


Expand Down Expand Up @@ -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);
},


Expand Down
59 changes: 21 additions & 38 deletions modules/services/streetside.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -25,6 +22,7 @@ import {
geoMetersToLon,
geoPointInPolygon,
geoRotate,
geoScaleToZoom,
geoVecLength
} from '../geo';

Expand Down Expand Up @@ -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 = [];

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;
}
// 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]);

return tiler.getTiles(projection)
.map(function(tile) { return tile.extent; });
}

/**
* searchLimited().
*/
function searchLimited(psize, limit, projection, rtree) {
limit = limit || 3;

var partitions = partitionViewport(psize, projection);
var results;
// no more than `limit` results per partition.
function searchLimited(limit, projection, rtree) {
limit = limit || 5;

results = _flatten(_map(partitions, function (extent) {
return rtree.search(extent.bbox())
.slice(0, limit)
.map(function (d) { return d.data; });
}));
return partitionViewport(projection)
.reduce(function(result, extent) {
var found = rtree.search(extent.bbox())
.slice(0, limit)
.map(function(d) { return d.data; });

return results;
return (found.length ? result.concat(found) : result);
}, []);
}


Expand Down Expand Up @@ -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);
},


Expand Down
4 changes: 2 additions & 2 deletions modules/svg/mapillary_images.js
Original file line number Diff line number Diff line change
Expand Up @@ -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] : []);
Expand Down
14 changes: 8 additions & 6 deletions test/spec/services/mapillary.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand All @@ -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'
Expand All @@ -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);
});
});

Expand Down
7 changes: 4 additions & 3 deletions test/spec/services/openstreetcam.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down
7 changes: 4 additions & 3 deletions test/spec/services/streetside.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down

0 comments on commit 1731ce4

Please sign in to comment.