Skip to content

Commit

Permalink
Subdivide tile request if requested tile has too many nodes (re: #1520)
Browse files Browse the repository at this point in the history
  • Loading branch information
quincylvania committed Apr 15, 2020
1 parent ef48fb0 commit 572fbc3
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 5 deletions.
27 changes: 22 additions & 5 deletions modules/services/osm.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { osmEntity, osmNode, osmNote, osmRelation, osmWay } from '../osm';
import { utilArrayChunk, utilArrayGroupBy, utilArrayUniq, utilRebind, utilTiler, utilQsString } from '../util';


var tiler = utilTiler();
var _tiler = utilTiler();
var dispatch = d3_dispatch('apiStatusChange', 'authLoading', 'authDone', 'change', 'loading', 'loaded', 'loadedNotes');
var urlroot = 'https://www.openstreetmap.org';
var oauth = osmAuth({
Expand All @@ -34,6 +34,7 @@ var _changeset = {};
var _deferred = new Set();
var _connectionID = 1;
var _tileZoom = 16;
var _maxRequestableZoom = 18;
var _noteZoom = 12;
var _rateLimitError;
var _userChangesets;
Expand Down Expand Up @@ -572,7 +573,7 @@ export default {
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
// Logout and retry the request..
if (isAuthenticated && err && err.status &&
(err.status === 400 || err.status === 401 || err.status === 403)) {
(err.status === 401 || err.status === 403)) {
that.logout();
that.loadFromAPI(path, callback, options);

Expand Down Expand Up @@ -960,7 +961,7 @@ export default {
if (_off) return;

// determine the needed tiles to cover the view
var tiles = tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);
var tiles = _tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);

// abort inflight requests that are no longer needed
var hadRequests = hasInflightRequests(_tileCache);
Expand Down Expand Up @@ -995,6 +996,8 @@ export default {
options
);

var that = this;

function tileCallback(err, parsed) {
delete _tileCache.inflight[tile.id];
if (!err) {
Expand All @@ -1003,6 +1006,20 @@ export default {
var bbox = tile.extent.bbox();
bbox.id = tile.id;
_tileCache.rtree.insert(bbox);

} else if (err.status === 400 && tile.xyz[2] < _maxRequestableZoom) {
// assume that this request exceeded the node limit,
// subdivide the tile

delete _tileCache.toLoad[tile.id];

var subtiles = utilTiler.subdivide(tile);

subtiles.forEach(function(tile) {
that.loadTile(tile, callback);
});

return;
}
if (callback) {
callback(err, Object.assign({ data: parsed }, tile));
Expand Down Expand Up @@ -1030,7 +1047,7 @@ export default {
var k = geoZoomToScale(_tileZoom + 1);
var offset = geoRawMercator().scale(k)(loc);
var projection = geoRawMercator().transform({ k: k, x: -offset[0], y: -offset[1] });
var tiles = tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);
var tiles = _tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);

tiles.forEach(function(tile) {
if (_tileCache.toLoad[tile.id] || _tileCache.loaded[tile.id] || _tileCache.inflight[tile.id]) return;
Expand All @@ -1056,7 +1073,7 @@ export default {
}, 750);

// determine the needed tiles to cover the view
var tiles = tiler.zoomExtent([_noteZoom, _noteZoom]).getTiles(projection);
var tiles = _tiler.zoomExtent([_noteZoom, _noteZoom]).getTiles(projection);

// abort inflight requests that are no longer needed
abortUnwantedRequests(_noteCache, tiles);
Expand Down
85 changes: 85 additions & 0 deletions modules/util/tiler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { range as d3_range } from 'd3-array';
import { geoMercatorRaw as d3_geoMercatorRaw } from 'd3-geo';
import { geoExtent, geoScaleToZoom } from '../geo';


Expand Down Expand Up @@ -188,3 +189,87 @@ export function utilTiler() {

return tiler;
}

// Returns an array of four tiles representing the given `tile` at the next higher
// zoom level. Note that tile rows and columns are >=0 from the top left while
// the extent is in geographic coordinates.
utilTiler.subdivide = function(tile) {

function geo2Proj(lon, lat) {
return d3_geoMercatorRaw(lon * Math.PI / 180, lat * Math.PI / 180);
}

function proj2Geo(x, y) {
var rad = d3_geoMercatorRaw.invert(x, y);
return [rad[0] * 180 / Math.PI, rad[1] * 180 / Math.PI];
}

// must use the projected bounding box
var pMin = geo2Proj(tile.extent[0][0], tile.extent[0][1]);
var pMax = geo2Proj(tile.extent[1][0], tile.extent[1][1]);

// width and height should be the same for square tiles
var w = pMax[0] - pMin[0];
var h = pMax[1] - pMin[1];

var x = tile.xyz[0];
var y = tile.xyz[1];
var z = tile.xyz[2];

var subtiles = [
// top left
{
xyz: [
x * 2,
y * 2,
z + 1
],
extent: geoExtent(
proj2Geo(pMin[0], pMin[1] + h/2),
proj2Geo(pMin[0] + w/2, pMin[1] + h)
)
},
// bottom left
{
xyz: [
x * 2,
y * 2 + 1,
z + 1
],
extent: geoExtent(
proj2Geo(pMin[0], pMin[1]),
proj2Geo(pMin[0] + w/2, pMin[1] + h/2)
)
},
// top right
{
xyz: [
x * 2 + 1,
y * 2,
z + 1
],
extent: geoExtent(
proj2Geo(pMin[0] + w/2, pMin[1] + h/2),
proj2Geo(pMin[0] + w, pMin[1] + h)
)
},
// bottom right
{
xyz: [
x * 2 + 1,
y * 2 + 1,
z + 1
],
extent: geoExtent(
proj2Geo(pMin[0] + w/2, pMin[1]),
proj2Geo(pMin[0] + w, pMin[1] + h/2)
)
}
];

subtiles.forEach(function(tile) {
tile.id = tile.xyz.toString();
});

return subtiles;
}

0 comments on commit 572fbc3

Please sign in to comment.